Extending your first integration: Iterating over a collection
Iterating over a collection is a simple, yet powerful logic in workflows. A ZIS Flow supports iterating over an array of objects using the Map state. The state executes a certain piece of logic for each item in the array.
In this tutorial, you'll learn how the Map state can be used in a flow to redact (or remove) all attachments from a ticket when the ticket's status is changed to Solved. You’ll apply concepts explained in previous tutorials, such as using jq to manipulate JSON and making authenticated API calls using named connections.
To complete this tutorial, you’ll need a ZIS connection for Zendesk. See Creating a ZIS connection for Zendesk.
First, let's look at the Map state. It has the following format:
{
"Type": "Map",
"ItemsPath": "{json_path_to_the_array_of_objects}",
"ResultPath": "{json_path_to_store_results}",
"Iterator": {
"StartAt": "",
"States": {}
}
}
where:
ItemsPath
: A JSON path syntax pointing to an array of objectsResultPath
: A JSON path syntax to store the results from the Map stateIterator
: This contains the sequence of states that’s executed for each object inItemsPath
state machine, much like a flow
The state machine inside the Iterator
object only has access to the object it’s iterating on. For example, consider this input
JSON object to the Flow with a Map state:
{
"ticket": {
"id": 123,
"comments": [
{
"id": "334324",
"added_by": "foo",
"text": "my printer is on fire"
},
{
"id": "334325",
"added_by": "foo",
"text": "pls help"
}
]
}
}
When the ItemsPath
property is set to the array $.input.ticket.comments
, the states inside Iterator
only sees the $.input.ticket.comments
object. That is, one of the two comments at any given time. During an iteration, you would need access to items that are present outside of the array such as ticket.id
.
To make ticket.id
available for the Iterator
, add ticket.id
to each object in the ItemsPath
array. You can do this using a jq expression in a Transform action.
Example:
{
"Type": "Action",
"ActionName": "zis:common:transform:Jq",
"Parameters": {
"expr.$": "[.comments[] | (.ticket_id={{$.input.ticket.id}})]",
"data.$": "$.input"
},
"ResultPath": "$.input.ticket.comments"
}
The ResultPath
now contains the ticket.id
in each object:
[
{
"id": "334324",
"added_by": "foo",
"text": "my printer is on fire",
"ticket_id": 123
},
{
"id": "334324",
"added_by": "foo",
"text": "pls help",
"ticket_id": 123
}
]
Note: In future, we intend to enhance the Map
state so the Iterator
will have access to other items in the input
JSON object. This will no longer require a Transform action to add items of interest to each object as in the above example.
Creating and uploading a ZIS Bundle
-
In your text editor, create a
delete_attachments.json
file and paste the following information:{
"zis_template_version": "2019-10-14",
"name": "Redact attachments",
"description": "Redact attachments when ticket status change to Solved",
"resources": {
"JobSpec": {
"type": "ZIS::JobSpec",
"properties": {
"name": "RedactAttachmentsOnSolved",
"event_source": "support",
"event_type": "ticket.StatusChanged",
"flow_name": "zis:{integration_name}:flow:RedactAttachmentsOnSolved"
}
},
"Example_flow": {
"type": "ZIS::Flow",
"properties": {
"name": "RedactAttachmentsOnSolved",
"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_name}:action:GetComments",
"Parameters": {
"ticket_id.$": "$.input.ticket_event.ticket.id",
"access_token.$": "$.connections.zendesk.access_token"
},
"ResultPath": "$.input.ticket_comments",
"Next": "SanitiseTicketComments"
},
"SanitiseTicketComments": {
"Type": "Action",
"ActionName": "zis:common:transform:Jq",
"Parameters": {
"expr": "[.comments[] | (.attachments[] | (.id? // (.[] | .id))) as $id | {\"comment_id\": \"\\(.id)\", \"attachment_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": "IncludeAccessToken"
},
"IncludeAccessToken": {
"Type": "Action",
"ActionName": "zis:common:transform:Jq",
"Parameters": {
"expr.$": "[.ticket_comments[] | (.access_token=\"{{$.connections.zendesk.access_token}}\")]",
"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_name}:action:RedactAttachment",
"Parameters": {
"attachment_id.$": "$.attachment_id",
"comment_id.$": "$.comment_id",
"ticket_id.$": "$.ticket_id",
"access_token.$": "$.access_token"
},
"Next": "Done"
},
"Done": {
"Type": "Succeed"
}
}
},
"Next": "Finish"
},
"Finish": {
"Type": "Succeed"
}
}
}
}
},
"GetComments": {
"type": "ZIS::Action::Http",
"properties": {
"name": "GetComments",
"definition": {
"method": "GET",
"path.$": "/api/v2/tickets/{{$.ticket_id}}/comments.json",
"headers": [
{
"key": "Authorization",
"value.$": "Bearer {{$.access_token}}"
},
{
"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",
"headers": [
{
"key": "Authorization",
"value.$": "Bearer {{$.access_token}}"
},
{
"key": "Content-Type",
"value": "application/json"
}
]
}
}
}
}
}
The bundle uses a Transform action to sanitize and add items to each object in the array.
SanitiseTicketComments
: This state will prepare the array for looping. Theexpr.$
transforms the input to the desired outputIncludeTicketId
: This state addsinput.ticket_event.ticket.id
to each object in the arrayIncludeAccessToken
: This state addsconnections.zendesk.access_token
to each object in the array. This access token is used to make a call to the Redact Attachment API.
Make sure you replace
{integration_name}
and{subdomain}
with the appropriate values and save the bundle. -
In your command line tool, make a cURL request to the Upload or Update Bundle endpoint to update the bundle:
curl \
--request POST \
--url https://{subdomain}.zendesk.com/api/services/zis/registry/{integration_name}/bundles \
--user '{email}:{password}' \
--header 'content-type: application/json' \
-d @delete_attachments.json
-
From the command line, make a cURL request to the Registry API to enable the JobSpec:
curl \
--request POST \
--url "https://{subdomain}.zendesk.com/api/services/zis/registry/job_specs/install?job_spec_name=zis:{integration_name}:job_spec:RedactAttachmentsOnSolved" \
--user '{email}:{password}'
Testing the integration
You can test the integration in Zendesk Support by adding attachments to a ticket and then setting the ticket status as Solved. When you refresh the page, you should see the comments in the ticket are deleted and replaced with redacted.txt
.