Iterating over a collection in a ZIS flow
In this tutorial, you'll create a ZIS integration that iterates over an array of JSON objects. To do this, you'll include a Map state in the integration's ZIS flow. The Map state runs each item in the array through a sequence of flow states that you specify.
What you'll need
To complete this tutorial, you'll need the following:
-
Familiarity with ZIS. Before you start, complete the Building your first ZIS integration tutorial.
-
A registered ZIS integration. You can use the same integration you created in Building your first ZIS integration or register a new one. To register a new integration, see Registering the integration name.
-
A ZIS OAuth token for the integration. See Obtaining a ZIS OAuth token.
Creating the integration
The integration you create listens for Ticket Status Changed events in Zendesk. If the event's current ticket status is "solved", the integration redacts any attachments in the ticket's comments. If the event has another current ticket status, the integration takes no action.
-
Create an OAuth connection named "zendesk". You'll use the connection to authenticate Zendesk API requests in the integration's ZIS flow.
In a typical setup, an admin uses a private Zendesk app to create OAuth connections. For this tutorial, you'll create the "zendesk" connection without an app.
To create the "zendesk" connection, follow the steps in Creating an OAuth connection for Zendesk. Then return here.
-
Create a JSON file named my_zis_bundle.json.
-
Add the following bundle skeleton to my_zis_bundle.json:
{
"name": "Example ZIS integration with Map state",
"description": "Redact attachments when ticket status changes to solved",
"zis_template_version": "2019-10-14",
"resources": {
"GetComments": {
"_placeholder_": "ZIS custom action definition goes here"
},
"RedactAttachment": {
"_placeholder_": "ZIS custom action definition goes here"
},
"RedactAttachmentsOnSolvedFlow": {
"_placeholder_": "ZIS flow goes here"
},
"RedactAttachmentsOnSolvedJobSpec": {
"_placeholder_": "ZIS job spec goes here"
}
}
}
You'll define the custom actions, ZIS flow, and job spec in the next steps.
-
In my_zis_bundle.json, replace the
GetComments
placeholder with the following custom action definition."GetComments": {
"type": "ZIS::Action::Http",
"properties": {
"name": "GetComments",
"definition": {
"method": "GET",
"path.$": "/api/v2/tickets/{{$.ticket_id}}/comments.json",
"connectionName": "zendesk",
"headers": [
{
"key": "Content-Type",
"value": "application/json"
}
]
}
}
},
When called, the custom action makes a request to the Zendesk Support API's List Comments endpoint. The request requires a Zendesk ticket id. The request's response contains data for the ticket's comments, including any attachments.
-
Replace the
RedactAttachment
placeholder with the following custom action definition."RedactAttachment": {
"type": "ZIS::Action::Http",
"properties": {
"name": "RedactAttachment",
"definition": {
"method": "PUT",
"path.$": "/api/v2/tickets/{{$.ticket_id}}/comments/{{$.comment_id}}/attachments/{{$.attachment_id}}/redact",
"connectionName": "zendesk",
"headers": [
{
"key": "Content-Type",
"value": "application/json"
}
]
}
}
},
When called, the custom action makes a request to the Zendesk Support API's Redact Comment Attachment endpoint. The request removes an attachment from a ticket comment. It replaces the attachment with an empty "redacted.txt" file.
-
Replace the
RedactAttachmentsOnSolvedFlow
placeholder with the following ZIS flow definition. In the definition, replace "INTEGRATION" with your integration key.The flow contains a Map state, which is highlighted.
"RedactAttachmentsOnSolvedFlow": {
"type": "ZIS::Flow",
"properties": {
"name": "RedactAttachmentsOnSolvedFlow",
"definition": {
"StartAt": "CheckStatus",
"States": {
"CheckStatus": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.input.ticket_event.current",
"StringEquals": "solved",
"Next": "GetComments"
}
],
"Default": "Finish"
},
"GetComments": {
"Type": "Action",
"ActionName": "zis:INTEGRATION:action:GetComments",
"Parameters": {
"ticket_id.$": "$.input.ticket_event.ticket.id"
},
"ResultPath": "$.input.ticket_comments",
"Next": "SanitizeTicketComments"
},
"SanitizeTicketComments": {
"Type": "Action",
"ActionName": "zis:common:transform:Jq",
"Parameters": {
"expr": "[.comments[] | (.attachments[] | (.id? // (.[] | .id))) as $id | {\"attachment_id\": \"\\($id)\", \"comment_id\": \"\\(.id)\"}]",
"data.$": "$.input.ticket_comments"
},
"ResultPath": "$.input.ticket_comments",
"Next": "IncludeTicketId"
},
"IncludeTicketId": {
"Type": "Action",
"ActionName": "zis:common:transform:Jq",
"Parameters": {
"expr.$": "[.ticket_comments[] | (.ticket_id={{$.input.ticket_event.ticket.id}})]",
"data.$": "$.input"
},
"ResultPath": "$.input.ticket_comments",
"Next": "RedactAttachment"
},
"RedactAttachment": {
"Type": "Map",
"ItemsPath": "$.input.ticket_comments",
"ResultPath": "$.redact_attachment_output",
"Iterator": {
"StartAt": "Redact",
"States": {
"Redact": {
"Type": "Action",
"ActionName": "zis:INTEGRATION:action:RedactAttachment",
"Parameters": {
"attachment_id.$": "$.attachment_id",
"comment_id.$": "$.comment_id",
"ticket_id.$": "$.ticket_id"
},
"Next": "Done"
},
"Done": {
"Type": "Succeed"
}
}
},
"Next": "Finish"
},
"Finish": {
"Type": "Succeed"
}
}
}
}
},
Note: In the above example, the Done state is a Succeed type, so an
End
property is not required. See Common state properties for more information.A Map state requires an array of JSON objects as input. The state can only access data in this input array. Before the Map state, the flow checks whether the event's current ticket status is "solved". If so, the flow gets data for the ticket's comments. The flow then uses Transform actions to change the comment data into an array of JSON objects with the following structure:
[
{
"attachment_id": 1234567890123,
"comment_id": 123456,
"ticket_id": 1
},
{
"attachment_id": 4567890123456,
"comment_id": 789012,
"ticket_id": 1
}
]
Each object in the array represents an attachment in the ticket's comments. If a comment doesn't contain an attachment, the flow omits the comment from the array.
The flow's Map state runs each object in the array through an Action state. The Action state uses the
RedactAttachment
custom action to redact the object's attachment. -
Replace the
RedactAttachmentsOnSolvedJobSpec
placeholder with the following job spec definition. In the definition, replace "INTEGRATION" with your integration key."RedactAttachmentsOnSolvedJobSpec": {
"type": "ZIS::JobSpec",
"properties": {
"name": "RedactAttachmentsOnSolvedJobSpec",
"event_source": "support",
"event_type": "ticket.StatusChanged",
"flow_name": "zis:INTEGRATION:flow:RedactAttachmentsOnSolvedFlow"
}
}
The job spec tells ZIS to run the ZIS flow when it detects a Ticket Status Changed event.
-
Save my_zis_bundle.json. The file should now look like this:
{
"name": "Example ZIS integration with Map state",
"description": "Redact attachments when ticket status changes to solved",
"zis_template_version": "2019-10-14",
"resources": {
"GetComments": {
"type": "ZIS::Action::Http",
"properties": {
"name": "GetComments",
"definition": {
"method": "GET",
"path.$": "/api/v2/tickets/{{$.ticket_id}}/comments.json",
"connectionName": "zendesk",
"headers": [
{
"key": "Content-Type",
"value": "application/json"
}
]
}
}
},
"RedactAttachment": {
"type": "ZIS::Action::Http",
"properties": {
"name": "RedactAttachment",
"definition": {
"method": "PUT",
"path.$": "/api/v2/tickets/{{$.ticket_id}}/comments/{{$.comment_id}}/attachments/{{$.attachment_id}}/redact",
"connectionName": "zendesk",
"headers": [
{
"key": "Content-Type",
"value": "application/json"
}
]
}
}
},
"RedactAttachmentsOnSolvedFlow": {
"type": "ZIS::Flow",
"properties": {
"name": "RedactAttachmentsOnSolvedFlow",
"definition": {
"StartAt": "CheckStatus",
"States": {
"CheckStatus": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.input.ticket_event.current",
"StringEquals": "solved",
"Next": "GetComments"
}
],
"Default": "Finish"
},
"GetComments": {
"Type": "Action",
"ActionName": "zis:INTEGRATION:action:GetComments",
"Parameters": {
"ticket_id.$": "$.input.ticket_event.ticket.id"
},
"ResultPath": "$.input.ticket_comments",
"Next": "SanitizeTicketComments"
},
"SanitizeTicketComments": {
"Type": "Action",
"ActionName": "zis:common:transform:Jq",
"Parameters": {
"expr": "[.comments[] | (.attachments[] | (.id? // (.[] | .id))) as $id | {\"attachment_id\": \"\\($id)\", \"comment_id\": \"\\(.id)\"}]",
"data.$": "$.input.ticket_comments"
},
"ResultPath": "$.input.ticket_comments",
"Next": "IncludeTicketId"
},
"IncludeTicketId": {
"Type": "Action",
"ActionName": "zis:common:transform:Jq",
"Parameters": {
"expr.$": "[.ticket_comments[] | (.ticket_id={{$.input.ticket_event.ticket.id}})]",
"data.$": "$.input"
},
"ResultPath": "$.input.ticket_comments",
"Next": "RedactAttachment"
},
"RedactAttachment": {
"Type": "Map",
"ItemsPath": "$.input.ticket_comments",
"ResultPath": "$.redact_attachment_output",
"Iterator": {
"StartAt": "Redact",
"States": {
"Redact": {
"Type": "Action",
"ActionName": "zis:INTEGRATION:action:RedactAttachment",
"Parameters": {
"attachment_id.$": "$.attachment_id",
"comment_id.$": "$.comment_id",
"ticket_id.$": "$.ticket_id"
},
"Next": "Done"
},
"Done": {
"Type": "Succeed"
}
}
},
"Next": "Finish"
},
"Finish": {
"Type": "Succeed"
}
}
}
}
},
"RedactAttachmentsOnSolvedJobSpec": {
"type": "ZIS::JobSpec",
"properties": {
"name": "RedactAttachmentsOnSolvedJobSpec",
"event_source": "support",
"event_type": "ticket.StatusChanged",
"flow_name": "zis:INTEGRATION:flow:RedactAttachmentsOnSolvedFlow"
}
}
}
}
-
Upload the bundle to ZIS.
curl -X POST https://{subdomain}.zendesk.com/api/services/zis/registry/{integration}/bundles \
-u {email}/token:{api_token} \
-H "Content-Type: application/json" \
-d @my_zis_bundle.json
-
Install the job spec to enable the integration.
curl -X POST "https://{subdomain}.zendesk.com/api/services/zis/registry/job_specs/install?job_spec_name=zis:{integration}:job_spec:RedactAttachmentsOnSolvedJobSpec" \
-u {email}/token:{api_token}
Testing the integration
To test the integration, create an open ticket with an attachment. Then verify the integration redacts the attachment when you change the ticket's status to "solved".
-
Right-click the following image link and save it to your computer as camera-pieces.png:
-
In your shell, navigate to the folder where you saved the image. For example:
$ cd ~/Downloads
-
Use the following request to upload the image as an attachment.
curl -X POST https://{subdomain}.zendesk.com/api/v2/uploads?filename=camera-pieces.png \
-u {email}/token:{api_token} \
-H "Content-Type: application/binary" \
--data-binary @camera-pieces.png
Save the
upload.token
value from the response. You'll use the token in the next step.{
"upload": {
"token": "abcxyz",
"expires_at": "2099-05-06T00:00:00Z",
...
}
}
-
Use the following request to create an open ticket with the attachment. Replace "TOKEN" with the token you saved in the previous step.
curl -X POST https://{subdomain}.zendesk.com/api/v2/tickets \
-u {email}/token:{api_token} \
-H "Content-Type: application/json" \
-d '{
"ticket": {
"subject": "My homemade camera fell apart!",
"status": "open",
"comment": {
"body": "See attached.",
"uploads": ["TOKEN"]
}
}
}'
Save the
ticket.id
value from the response. You'll use the id in the following steps.{
"ticket": {
"url": "https://SUBDOMAIN.zendesk.com/api/v2/tickets/30.json",
"id": 34,
"external_id": null,
...
},
...
}
-
Use the following request to fetch comments for the ticket you created. Replace "{ticket_id}" with the id you saved in the previous step.
curl -X GET https://{subdomain}.zendesk.com/api/v2/tickets/{ticket_id}/comments.json \
-u {email}/token:{api_token}
The response should contain an object in the
comments.attachments
property. The object'sfile_name
property should contain the "camera-pieces.png" string.{
"comments": [
{
"id": 4567890123456,
...
"attachments": [
{
"url": "https://SUBDOMAIN.zendesk.com/api/v2/attachments/1234567890123.json",
"id": 1234567890123,
"file_name": "camera-pieces.png",
...
}
],
...
}
],
...
}
You also can verify the attachment in the Zendesk Support agent interface.
-
Use the following request to change the ticket's status to "solved." Replace "{ticket_id}" with the id you saved in step 4.
curl -X PUT https://{subdomain}.zendesk.com/api/v2/tickets/{ticket_id}.json \
-u {email}/token:{api_token} \
-H "Content-Type: application/json" \
-d '{
"ticket": {
"status": "solved",
"comment": {
"body": "Thanks for choosing Acme Co.",
"public": true
}
}
}'
-
Fetch the ticket's comments again. Replace "{ticket_id}" with the id you saved in step 4.
curl -X GET https://{subdomain}.zendesk.com/api/v2/tickets/{ticket_id}.json \
-u {email}/token:{api_token}
The
file_name
property should now contain "redacted.txt".{
"comments": [
{
"id": 4567890123456,
...
"attachments": [
{
"url": "https://SUBDOMAIN.zendesk.com/api/v2/attachments/1234567890123.json",
"id": 1234567890123,
"file_name": "redacted.txt",
...
}
],
...
}
],
...
}
You also can verify the redaction in the Zendesk Support agent interface.
Congratulations! You've created a ZIS integration that uses a Map state to iterate through a collection. For another example of a ZIS integration, see the Zendesk app as an admin interface tutorial series.