{ "kind": "open-flow-workflow", "version": 1, "name": "Unassigned Zendesk Tickets to Slack", "content": { "modelVersion": 14, "document": { "bindings": {}, "graph": { "nodes": { "n_8ef4b68e2c4ab82a": { "name": "Run on demand", "kind": "start", "inputDefs": [] }, "n_fe666db1cda087a0": { "name": "Every day at 16:30 UTC", "kind": "cron", "cronTimes": [ { "type": "cron", "expression": "30 16 * * *", "timezone": "UTC" } ] }, "n_7e5542eeeb137ae8": { "inputs": { "previousState": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_7e5542eeeb137ae8", "output": "state" } ] }, "body": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_cb25f2ecfff81bcd", "output": "body" } ] }, "bodyEncoding": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_cb25f2ecfff81bcd", "output": "bodyEncoding" } ] }, "statusCode": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_cb25f2ecfff81bcd", "output": "statusCode" } ] } }, "name": "Collect matching tickets", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": true, "handle": "previousState" }, { "jsonSchema": {}, "nullable": true, "handle": "body" }, { "jsonSchema": { "const": "base64" }, "nullable": true, "handle": "bodyEncoding" }, { "jsonSchema": { "type": "integer" }, "nullable": true, "handle": "statusCode" } ], "outputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "state" }, { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "more" }, { "jsonSchema": { "type": "object", "additionalProperties": { "type": [ "string", "number", "boolean", "null" ] } }, "nullable": false, "handle": "query" }, { "jsonSchema": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "nullable": false, "handle": "tickets" } ], "name": "Collect matching tickets", "moduleId": "n_f2376d24c6f8a953", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_61042c2fe2100d17": { "inputs": {}, "name": "More search pages", "kind": "condition", "matchMode": "first", "cases": [ { "output": "more", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_7e5542eeeb137ae8", "output": "more" } }, "operator": "isTrue" } ] } ] } ] }, "n_cb25f2ecfff81bcd": { "inputs": { "endpoint": { "kind": "value", "value": "/api/v2/search.json" }, "query": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_7e5542eeeb137ae8", "output": "query" } ] } }, "name": "Search Zendesk tickets", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "endpoint" }, { "jsonSchema": { "type": "object", "additionalProperties": { "type": [ "string", "number", "boolean", "null" ] } }, "nullable": true, "handle": "query" } ], "outputs": [ { "jsonSchema": {}, "nullable": true, "handle": "body" }, { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "statusCode" }, { "jsonSchema": { "type": "object", "additionalProperties": { "type": "string" } }, "nullable": false, "handle": "headers" }, { "jsonSchema": { "const": "base64" }, "nullable": true, "handle": "bodyEncoding" } ], "name": "Search Zendesk tickets", "executor": { "providerId": "zendesk", "method": "GET", "query": { "mode": "object", "names": [] }, "headers": { "mode": "fields", "names": [] }, "bodyFormat": "none", "kind": "connector-proxy" } } }, "n_c6108d6ba9612373": { "inputs": { "tickets": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_7e5542eeeb137ae8", "output": "tickets" } ] } }, "name": "Prepare ticket summary", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "nullable": false, "handle": "tickets" } ], "outputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "message" }, { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "hasTickets" } ], "name": "Prepare ticket summary", "moduleId": "n_e10bd2c891b11baa", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_0bf9a218a37351fc": { "inputs": {}, "name": "Tickets found", "kind": "condition", "matchMode": "first", "cases": [ { "output": "send", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_c6108d6ba9612373", "output": "hasTickets" } }, "operator": "isTrue" } ] } ] } ] }, "n_b48a995594eff007": { "inputs": { "channelId": { "kind": "value", "value": "YOUR_SLACK_CHANNEL_ID" }, "text": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_c6108d6ba9612373", "output": "message" } ] } }, "name": "Post the summary", "kind": "task", "task": { "inputs": [ { "description": "The Slack conversation or channel ID.", "jsonSchema": { "type": "string", "minLength": 1, "description": "The Slack conversation or channel ID." }, "nullable": false, "handle": "channelId" }, { "description": "Plain text message content. When blocks are provided, Slack uses this as notification and accessibility fallback text.", "jsonSchema": { "type": "string", "description": "Plain text message content. When blocks are provided, Slack uses this as notification and accessibility fallback text." }, "nullable": true, "handle": "text", "value": null }, { "description": "Slack Block Kit blocks to render in the message.", "jsonSchema": { "type": "array", "items": { "type": "object", "properties": {}, "additionalProperties": true, "description": "A Slack Block Kit block object. Pass the block exactly as Slack documents it." }, "minItems": 1, "description": "Slack Block Kit blocks to render in the message." }, "nullable": true, "handle": "blocks", "value": null }, { "description": "Slack legacy attachments to include in the message.", "jsonSchema": { "type": "array", "items": { "type": "object", "properties": {}, "additionalProperties": true, "description": "A Slack legacy attachment object. Prefer blocks for new messages when possible." }, "minItems": 1, "description": "Slack legacy attachments to include in the message." }, "nullable": true, "handle": "attachments", "value": null }, { "description": "Whether Slack should unfurl links in the message.", "jsonSchema": { "type": "boolean", "description": "Whether Slack should unfurl links in the message." }, "nullable": true, "handle": "unfurlLinks", "value": null }, { "description": "Whether Slack should unfurl media in the message.", "jsonSchema": { "type": "boolean", "description": "Whether Slack should unfurl media in the message." }, "nullable": true, "handle": "unfurlMedia", "value": null }, { "description": "Slack message metadata to attach to the message.", "jsonSchema": { "type": "object", "properties": {}, "additionalProperties": true, "description": "Slack message metadata to attach to the message." }, "nullable": true, "handle": "metadata", "value": null } ], "outputs": [ { "description": "The timestamp identifier of the posted message.", "jsonSchema": { "type": "string", "description": "The timestamp identifier of the posted message." }, "nullable": false, "handle": "ts" }, { "description": "The channel ID where the message was posted.", "jsonSchema": { "type": "string", "description": "The channel ID where the message was posted." }, "nullable": false, "handle": "channelId" } ], "name": "Post the summary", "executor": { "kind": "connector", "action": "slack.post_message" } } } }, "edges": [ { "source": "n_8ef4b68e2c4ab82a", "target": "n_7e5542eeeb137ae8" }, { "source": "n_fe666db1cda087a0", "target": "n_7e5542eeeb137ae8" }, { "source": "n_7e5542eeeb137ae8", "target": "n_61042c2fe2100d17" }, { "source": "n_61042c2fe2100d17", "target": "n_cb25f2ecfff81bcd", "sourceHandle": "more" }, { "source": "n_cb25f2ecfff81bcd", "target": "n_7e5542eeeb137ae8" }, { "source": "n_61042c2fe2100d17", "target": "n_c6108d6ba9612373", "sourceHandle": "otherwise" }, { "source": "n_c6108d6ba9612373", "target": "n_0bf9a218a37351fc" }, { "source": "n_0bf9a218a37351fc", "target": "n_b48a995594eff007", "sourceHandle": "send" } ] } }, "modules": { "n_f2376d24c6f8a953": { "name": "Collect matching tickets", "imports": [], "source": "export default function collect({previousState,body,statusCode,bodyEncoding}) {\n const state = previousState === null ? {page:1,tickets:[],count:null} : previousState;\n if (!state || !Number.isInteger(state.page) || !Array.isArray(state.tickets)) throw Error('Invalid search state');\n if (previousState !== null) {\n if (statusCode < 200 || statusCode >= 300 || bodyEncoding !== null || !body || !Array.isArray(body.results) || !Number.isInteger(body.count) || body.count < 0) throw Error('Zendesk search failed or returned an invalid page');\n if (body.count > 1000) throw Error('Zendesk search exceeds the 1000-result limit');\n if (state.count !== null && state.count !== body.count) throw Error('Zendesk search changed while paging');\n const ids = new Set(state.tickets.map(t=>t.id));\n for (const ticket of body.results) {\n if (!ticket || !Number.isSafeInteger(ticket.id) || ticket.id < 1 || typeof ticket.url !== 'string' || typeof ticket.status !== 'string' || typeof ticket.subject !== 'string' || ids.has(ticket.id)) throw Error('Invalid or duplicate Zendesk ticket');\n ids.add(ticket.id); state.tickets.push({id:ticket.id,url:ticket.url,status:ticket.status,subject:ticket.subject});\n }\n state.count=body.count;\n if (state.tickets.length > state.count || (body.next_page == null && state.tickets.length !== state.count) || (body.next_page != null && body.results.length === 0)) throw Error('Incomplete Zendesk search');\n if (body.next_page == null) return {state,more:false,query:{query:'type:ticket assignee:none status= 10) throw Error('Zendesk search exceeds the 1000-result limit');\n state.page++;\n }\n return {state,more:true,query:{query:'type:ticket assignee:none status value.replaceAll('&','&').replaceAll('<','<').replaceAll('>','>').replace(/[\\r\\n]+/g,' ').replace(/[*_~`]/g, char => ({'*':'*','_':'_','~':'~','`':'`'})[char]);\n let message='*Unassigned Tickets*\\n\\n';\n for (const t of tickets) {\n if (!t || !Number.isSafeInteger(t.id) || t.id < 1 || typeof t.url !== 'string' || typeof t.status !== 'string' || typeof t.subject !== 'string') throw Error('Invalid Zendesk ticket');\n const match = /^https:\\/\\/([a-z0-9-]+\\.zendesk\\.com)\\/api\\/v2\\/tickets\\/(\\d+)\\.json$/i.exec(t.url);\n if (!match || Number(match[2]) !== t.id) throw Error('Invalid ticket URL');\n message += `** [${escape(t.status.toUpperCase())}] - ${escape(t.subject)}\\n`;\n }\n if (message.length > 40000) throw Error('Slack message exceeds 40000 characters');\n return {message,hasTickets:tickets.length>0};\n}\n" } } }, "presentation": { "designer": { "version": 1, "flow": { "direction": "vertical" } } } }