{ "kind": "open-flow-workflow", "version": 1, "name": "Negative Feedback Alert", "content": { "modelVersion": 14, "document": { "bindings": {}, "graph": { "nodes": { "n_51de3f59cfd1787d": { "name": "Completed Typeform response", "kind": "integration", "config": { "formId": { "kind": "value", "value": "YOUR_TYPEFORM_FORM_ID" } }, "definition": { "configInputs": [ { "jsonSchema": { "type": "string", "minLength": 1, "maxLength": 128, "title": "Form ID" }, "nullable": false, "handle": "formId" } ], "definitionVersion": 2, "description": "Receives complete Typeform submissions through an automatically managed signed webhook.", "displayName": "Completed Form Response", "key": "typeform.on_form_response", "name": "on_form_response", "outputs": [ { "jsonSchema": { "type": "object", "properties": { "eventId": { "type": "string" }, "form_id": { "type": "string" }, "token": { "type": "string" }, "submitted_at": { "type": "string" }, "answers": { "type": "array", "items": { "type": "object" } }, "definition": { "type": "object" } }, "required": [ "eventId", "form_id", "token", "submitted_at", "answers" ] }, "nullable": false, "handle": "response" } ], "provider": "typeform", "type": "integration", "endpoint": { "body": { "allowArray": false, "allowEmpty": false, "formats": [ "json" ] }, "methods": [ "POST" ], "successStatus": 200 } } }, "n_b2881f9d44d5edc9": { "inputs": { "response": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_51de3f59cfd1787d", "output": "response" } ] }, "feedbackTitle": { "kind": "value", "value": "What did you think about the event?" }, "nameTitle": { "kind": "value", "value": "" } }, "name": "Read feedback answers", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "object" }, "nullable": false, "handle": "response" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "feedbackTitle" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "nameTitle" } ], "outputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "feedback" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "name" }, { "jsonSchema": { "type": "object" }, "nullable": false, "handle": "request" } ], "name": "Read feedback answers", "moduleId": "n_5ef30902b18d1217", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_b0a6292144f20392": { "inputs": { "url": { "kind": "value", "value": "https://language.googleapis.com/v1/documents:analyzeSentiment" }, "body": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_b2881f9d44d5edc9", "output": "request" } ] }, "auth.token": { "kind": "unset" } }, "name": "Analyze feedback sentiment", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "url" }, { "jsonSchema": {}, "nullable": true, "handle": "body" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "auth.token" } ], "outputs": [ { "jsonSchema": {}, "nullable": true, "handle": "body" }, { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "statusCode" }, { "jsonSchema": { "type": "object", "additionalProperties": { "type": "string" } }, "nullable": false, "handle": "headers" } ], "name": "Analyze feedback sentiment", "executor": { "method": "POST", "query": { "mode": "fields", "names": [] }, "headers": { "mode": "fields", "names": [] }, "bodyFormat": "json", "authentication": { "type": "apiKey", "name": "x-goog-api-key", "in": "header" }, "responseFormat": "json", "statusPolicy": "success", "kind": "http-request" } } }, "n_497c26683bbed9ce": { "inputs": { "body": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_b0a6292144f20392", "output": "body" } ] } }, "name": "Validate sentiment score", "kind": "task", "task": { "inputs": [ { "jsonSchema": {}, "nullable": true, "handle": "body" } ], "outputs": [ { "jsonSchema": { "type": "number", "minimum": -1, "maximum": 1 }, "nullable": false, "handle": "score" } ], "name": "Validate sentiment score", "moduleId": "n_c2872abf4505904c", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_908566c9af2f31d8": { "inputs": { "feedback": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_b2881f9d44d5edc9", "output": "feedback" } ] }, "name": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_b2881f9d44d5edc9", "output": "name" } ] }, "score": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_497c26683bbed9ce", "output": "score" } ] } }, "name": "Prepare feedback message", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "feedback" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "name" }, { "jsonSchema": { "type": "number", "minimum": -1, "maximum": 1 }, "nullable": false, "handle": "score" } ], "outputs": [ { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "message" }, { "jsonSchema": { "type": "object" }, "nullable": false, "handle": "properties" }, { "jsonSchema": { "type": "array", "items": { "type": "object" }, "minItems": 1 }, "nullable": false, "handle": "attachments" }, { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "cardName" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "cardDescription" } ], "name": "Prepare feedback message", "moduleId": "n_89d4ee535e66d85a", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_dace328edeca24bd": { "inputs": {}, "name": "Negative feedback", "kind": "condition", "matchMode": "first", "cases": [ { "output": "negative", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_497c26683bbed9ce", "output": "score" } }, "operator": "<", "right": { "kind": "value", "value": 0 } } ] } ] } ] }, "n_6e1fe070ff0f4c02": { "inputs": { "channelId": { "kind": "value", "value": "YOUR_MATTERMOST_CHANNEL_ID" }, "message": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_908566c9af2f31d8", "output": "message" } ] } }, "name": "Alert Mattermost", "kind": "task", "task": { "inputs": [ { "description": "A Mattermost object ID.", "jsonSchema": { "type": "string", "minLength": 1, "description": "A Mattermost object ID." }, "nullable": false, "handle": "channelId" }, { "description": "The Markdown message body to post.", "jsonSchema": { "type": "string", "minLength": 1, "description": "The Markdown message body to post." }, "nullable": false, "handle": "message" }, { "description": "Optional root post ID for replying in a thread.", "jsonSchema": { "type": "string", "minLength": 1, "description": "Optional root post ID for replying in a thread." }, "nullable": true, "handle": "rootId", "value": null }, { "description": "Optional Mattermost post props object.", "jsonSchema": { "type": "object", "properties": {}, "additionalProperties": true, "description": "Optional Mattermost post props object." }, "nullable": true, "handle": "props", "value": null } ], "outputs": [ { "description": "A Mattermost post object.", "jsonSchema": { "type": "object", "properties": {}, "additionalProperties": true, "description": "A Mattermost post object." }, "nullable": false, "handle": "post" }, { "description": "The raw Mattermost API JSON response.", "jsonSchema": { "description": "The raw Mattermost API JSON response." }, "nullable": false, "handle": "raw" } ], "name": "Alert Mattermost", "executor": { "kind": "connector", "action": "mattermost.create_post" } } }, "n_f69e61a8481d0f18": { "name": "Analyzed feedback", "kind": "outputs", "outputDefs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "feedback" }, { "jsonSchema": { "type": "number", "minimum": -1, "maximum": 1 }, "nullable": false, "handle": "score" } ], "outputMappings": { "feedback": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_b2881f9d44d5edc9", "output": "feedback" } ] }, "score": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_497c26683bbed9ce", "output": "score" } ] } } } }, "edges": [ { "source": "n_51de3f59cfd1787d", "target": "n_b2881f9d44d5edc9" }, { "source": "n_b2881f9d44d5edc9", "target": "n_b0a6292144f20392" }, { "source": "n_b0a6292144f20392", "target": "n_497c26683bbed9ce" }, { "source": "n_497c26683bbed9ce", "target": "n_908566c9af2f31d8" }, { "source": "n_908566c9af2f31d8", "target": "n_dace328edeca24bd" }, { "source": "n_dace328edeca24bd", "target": "n_6e1fe070ff0f4c02", "sourceHandle": "negative" }, { "source": "n_6e1fe070ff0f4c02", "target": "n_f69e61a8481d0f18" }, { "source": "n_dace328edeca24bd", "target": "n_f69e61a8481d0f18", "sourceHandle": "otherwise" } ] } }, "modules": { "n_5ef30902b18d1217": { "name": "Read feedback answers", "imports": [], "source": "export default function prepareFeedback(inputs) {\n const root = inputs.response;\n const response = root?.form_response ?? root;\n if (!response || typeof response !== 'object' || Array.isArray(response)) throw new Error('Expected a completed Typeform response.');\n if (!Array.isArray(response.answers) || !Array.isArray(response.definition?.fields)) throw new Error('The Typeform response must include answer and field definitions.');\n const requiredTitles = [inputs.feedbackTitle, inputs.nameTitle].filter(Boolean);\n const fields = new Map();\n for (const field of response.definition.fields) {\n if (typeof field?.id !== 'string' || typeof field.title !== 'string') continue;\n const title = field.title.trim();\n if (!requiredTitles.includes(title)) continue;\n if (fields.has(title)) throw new Error('Required Typeform question titles must be unique: ' + title);\n fields.set(title, field.id);\n }\n const readText = (title) => {\n const id = fields.get(title);\n if (!id) throw new Error('Missing Typeform question: ' + title);\n const answers = response.answers.filter((answer) => answer?.field?.id === id);\n if (answers.length !== 1 || answers[0].type !== 'text' || typeof answers[0].text !== 'string' || !answers[0].text.trim()) throw new Error('Expected one nonempty text answer for: ' + title);\n return answers[0].text;\n };\n const feedback = readText(inputs.feedbackTitle);\n const name = inputs.nameTitle ? readText(inputs.nameTitle) : '';\n return { feedback, name, request: { document: { type: 'PLAIN_TEXT', content: feedback }, encodingType: 'UTF16' } };\n}\n" }, "n_c2872abf4505904c": { "name": "Validate sentiment score", "imports": [], "source": "export default function readSentiment(inputs) {\n const body = inputs.body;\n const score = body?.documentSentiment?.score;\n if (!body || typeof body !== 'object' || Array.isArray(body) || body.error || typeof score !== 'number' || !Number.isFinite(score) || score < -1 || score > 1) throw new Error('The sentiment service did not return a valid document score.');\n return { score };\n}\n" }, "n_89d4ee535e66d85a": { "name": "Prepare feedback message", "imports": [], "source": "export default function prepareDestinations(inputs) {\n const { feedback, name, score } = inputs;\n if (typeof feedback !== 'string' || typeof name !== 'string' || typeof score !== 'number' || !Number.isFinite(score)) throw new Error('Expected feedback, name, and a sentiment score.');\n return {\n message: `You got a new feedback with a score of ${score}. Here is what it says:${feedback}`,\n properties: { Name: { title: [{ text: { content: name } }] }, Feedback: { rich_text: [{ text: { content: feedback } }] } },\n attachments: [{ text: feedback, title: `${name} ${score}` }],\n cardName: `Score: ${score}`,\n cardDescription: `Score: ${score}\\nFeedback: ${feedback}\\nUser: ${name}`,\n };\n}\n" } } }, "presentation": { "designer": { "version": 1, "flow": { "direction": "vertical" } } } }