Use case: Sending a survey on job completion
Important
This is a closed beta release program.
Beta features should be considered “in development” and may not be fully supported or complete.
Beta programs are invaluable for helping to validate the value and impact of features through feedback and insights. If you would like to participate in beta programs and access this feature, please contact your Customer Success representative.
Example scenario
When a job is complete, we wish to send the customer a survey response form and then receive and store the responses.
The workflow we want to acheive is:
- When a job is completed, a triggered action fires and calls a webhook URL with a secret header.
- The webhook generates a form ID (which is stored against the job record), and sends an SMS to the customer with a URL containing the job ID and form ID.
- The customer navigates to the URL in a web browser and fills in the form.
- The response is sent to a form handler, which is another public function.
- The form handler validates that the form ID matches the ID stored with the job, and if it does, then the results are stored against the job.
How to use public content to achieve the desired workflow
To create a publicly-available form that is linked to a specific job and for from which the responses can be received and stored, the following items must be created:
- Custom fields on the
Job
object for the form ID and survey responses. - Custom user roles for the survey webhook and survey form handler.
- Public functions for the webhook and form handler.
- A public page for the survey form.
- Configuration variables in Skedulo for the webhook to work.
- A triggered action that calls the webhook.
Create custom fields on the Job
object for the form ID and survey responses
Run sked artifacts custom-field upsert -f Jobs-formId.custom-field.json
where
Jobs-formId.custom-field.json contains the following:
Jobs-formId.custom-field.json
{
"metadata": {
"type": "CustomField"
},
"objectName": "Jobs",
"name": "formId",
"field": {
"type": "String",
"description": "a hard-to-guess id to ensure form is only usable by recipient",
"display": {
"label": "FormResponseID",
"order": 0,
"isAlert": false,
"showIf": null,
"showOnDesktop": false,
"showOnMobile": false,
"editableOnMobile": false,
"requiredOnMobile": false
},
"constraints": {
"required": false,
"accessMode": "ReadWrite",
"maxLength": 64
}
}
}
Repeat for Jobs-q1.custom-field.json, Jobs-q2.custom-field.json, and Jobs-q3.custom-field.json, as detailed below.
Jobs-q1.custom-field.json
{
"metadata": {
"type": "CustomField"
},
"objectName": "Jobs",
"name": "q1",
"field": {
"type": "String",
"description": "question 1",
"display": {
"label": "Q1",
"order": 0,
"isAlert": false,
"showIf": "it.q1 !== null",
"showOnDesktop": true,
"showOnMobile": false,
"editableOnMobile": false,
"requiredOnMobile": false
},
"constraints": {
"required": false,
"accessMode": "ReadWrite",
"maxLength": 255
}
}
}
Jobs-q2.custom-field.json
{
"metadata": {
"type": "CustomField"
},
"objectName": "Jobs",
"name": "q2",
"field": {
"type": "Picklist",
"description": "",
"display": {
"label": "Q2",
"order": 0,
"isAlert": false,
"showIf": "it.q2 !== null",
"showOnDesktop": true,
"showOnMobile": false,
"editableOnMobile": false,
"requiredOnMobile": false
},
"constraints": {
"required": false,
"accessMode": "ReadWrite"
},
"multipleAllowed": false,
"allowedValues": [
{
"value": "Good",
"label": "Good",
"active": true,
"default": false
},
{
"value": "Bad",
"label": "Bad",
"active": true,
"default": false
},
{
"value": "Indifferent",
"label": "Indifferent",
"active": true,
"default": false
}
]
}
}
Jobs-q3.custom-field.json
{
"metadata": {
"type": "CustomField"
},
"objectName": "Jobs",
"name": "q3",
"field": {
"type": "Picklist",
"description": "",
"display": {
"label": "Q3",
"order": 0,
"isAlert": false,
"showIf": "it.q3 !== \"\"",
"showOnDesktop": true,
"showOnMobile": false,
"editableOnMobile": false,
"requiredOnMobile": false
},
"constraints": {
"required": false,
"accessMode": "ReadWrite"
},
"multipleAllowed": false,
"allowedValues": [
{
"value": "X1",
"label": "1",
"active": true,
"default": false
},
{
"value": "X2",
"label": "2",
"active": true,
"default": false
},
{
"value": "X3",
"label": "3",
"active": true,
"default": false
},
{
"value": "X4",
"label": "4",
"active": true,
"default": false
},
{
"value": "X5",
"label": "5",
"active": true,
"default": false
}
]
}
}
Create custom user roles for the survey webhook and survey form endpoint
It is best practice to minimise the permissions that public functions run with. The webhook only needs to store the form ID against the job and send an SMS. The form handler needs to be able to read the form ID and store the survey responses.
To create the webhook user role, do the following:
- Run the following command in the CLI:
sked artifacts user-role upsert -f user-role/survey-webhook.user-role.json
where user-role/survey-webhook.user-role.json contains the following:
{
"metadata": {
"type": "UserRole"
},
"description": "Role for survey-webhook",
"name": "SurveyWebhook",
"custom":true,
"permissionPatterns": [
"skedulo.tenant.data.modify",
"skedulo.tenant.notifications.sms.send",
"skedulo.tenant.extension.packages.view"
]
}
Then run:
sked artifacts user-role upsert -f user-role/survey-handler.user-role.json
where user-role/survey-handler.user-role.json is as follows:
{
"metadata": {
"type": "UserRole"
},
"description": "Role for survey-handler",
"name": "SurveyHandler",
"custom":true,
"permissionPatterns": [
"skedulo.tenant.data.view",
"skedulo.tenant.data.modify",
"skedulo.tenant.extension.packages.view"
]
}
Create public functions for the webhook
sked function generate --name=survey-webhook --outputdir=functions/survey-webhook
Update the state.json in functions/survey-webhook to add the public function configuration:
{
"metadata": {
"type": "Function"
},
"name": "survey-webhook",
"source": "./",
"unauthenticated": {
"executionRole": "survey-webhook"
}
}
Create public functions for the form handler
sked function generate --name=survey-handler --outputdir=functions/survey-handler
Update the state.json in functions/survey-handler to add the public function configuration:
{
"metadata": {
"type": "Function"
},
"name": "survey-handler",
"source": "./",
"unauthenticated": {
"executionRole": "survey-handler"
}
}
Create a public page for the survey form
sked artifacts public-content upsert -f state.json
where state.json
looks like:
{
"metadata": {
"type": "PublicContent"
},
"source": "./",
"name": "survey",
"compilationCommand": "yarn build"
}
Create configuration variables in Skedulo for the webhook to work
In the Skedulo app, create configuration variables for:
SKEDULO_WEB_APP_URL
- this will look something likehttps://my-team.my.skedulo.com
WEBHOOK_HASH
- this should be a hard to guess secret hash - e.g. the result ofopenssl rand -base64 40
Create a triggered action that calls the webhook
Run sked artifacts triggered-action upsert -f survey-questions.triggered-action.json
where survey-questions.triggered-action.json
is as follows:
{
"metadata": {
"type": "TriggeredAction"
},
"name": "survey-questions",
"enabled": true,
"trigger": {
"type": "object_modified",
"filter": "Operation == 'UPDATE' AND Current.JobStatus != Previous.JobStatus AND Current.JobStatus == 'Complete'",
"schemaName": "Jobs"
},
"action": {
"type": "call_url",
"url": "https://{{ TENANT_DOMAIN_NAME }}/function/survey-webhook/webhook",
"query": "{ UID Name Contact { Phone MobilePhone }}",
"headers": {
"sked-webhook-hash": "{{ WEBHOOK_HASH }}"
}
}
}
Feedback
Was this page helpful?