{ "kind": "open-flow-workflow", "version": 1, "name": "Remind Notion Assignees in Slack", "content": { "modelVersion": 14, "document": { "bindings": {}, "graph": { "nodes": { "n_2457934eb0887cdd": { "name": "Weekdays at 09:00 UTC", "kind": "cron", "cronTimes": [ { "type": "cron", "expression": "0 9 * * 1-5", "timezone": "UTC" } ] }, "n_ab7805ed9214f0cd": { "inputs": { "previous": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_ab7805ed9214f0cd", "output": "state" } ] }, "dataSourceId": { "kind": "value", "value": "YOUR_NOTION_DATA_SOURCE_ID" } }, "name": "Read all tasks and Slack users", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": true, "handle": "previous" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "dataSourceId" } ], "outputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "state" }, { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "more" } ], "name": "Read all tasks and Slack users", "moduleId": "n_6c9c4c3e760e4ffc", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [ { "action": "notion.query_data_source" }, { "action": "slack.list_users" } ] } ] } }, "n_846ea2107b8020b8": { "inputs": {}, "name": "More pages to read?", "kind": "condition", "matchMode": "first", "cases": [ { "output": "yes", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_ab7805ed9214f0cd", "output": "more" } }, "operator": "isTrue" } ] } ] } ] }, "n_cee6277450eea564": { "inputs": { "state": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_ab7805ed9214f0cd", "output": "state" } ] }, "user1": { "kind": "value", "value": "YOUR_NOTION_USER_ID_1" }, "user2": { "kind": "value", "value": "YOUR_NOTION_USER_ID_2" }, "name1": { "kind": "value", "value": "YOUR_SLACK_FULL_NAME_1" }, "name2": { "kind": "value", "value": "YOUR_SLACK_FULL_NAME_2" } }, "name": "Match assignees and tasks", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "state" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "user1" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "user2" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "name1" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "name2" } ], "outputs": [ { "jsonSchema": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "nullable": false, "handle": "items" } ], "name": "Match assignees and tasks", "moduleId": "n_32536cdf93a49251", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_1bc651a9aae08a12": { "inputs": { "items": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_cee6277450eea564", "output": "items" } ] }, "previous": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_1bc651a9aae08a12", "output": "state" } ] }, "confirmation": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_8b2de6b462416bb2", "output": "ts" } ] } }, "name": "Prepare each reminder", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "nullable": false, "handle": "items" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": true, "handle": "previous" }, { "jsonSchema": { "type": "string", "description": "The timestamp identifier of the posted message." }, "nullable": true, "handle": "confirmation" } ], "outputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "state" }, { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "more" }, { "jsonSchema": { "type": "string", "minLength": 1, "description": "The Slack conversation or channel ID." }, "nullable": false, "handle": "channelId" }, { "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" } ], "name": "Prepare each reminder", "moduleId": "n_c12c695ffadd34bb", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_f7075f98301c4ec9": { "inputs": {}, "name": "More reminders?", "kind": "condition", "matchMode": "first", "cases": [ { "output": "yes", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_1bc651a9aae08a12", "output": "more" } }, "operator": "isTrue" } ] } ] } ] }, "n_8b2de6b462416bb2": { "inputs": { "channelId": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_1bc651a9aae08a12", "output": "channelId" } ] }, "text": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_1bc651a9aae08a12", "output": "text" } ] } }, "name": "Send a Slack reminder", "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": "Send a Slack reminder", "executor": { "kind": "connector", "action": "slack.post_message" } } } }, "edges": [ { "source": "n_2457934eb0887cdd", "target": "n_ab7805ed9214f0cd" }, { "source": "n_ab7805ed9214f0cd", "target": "n_846ea2107b8020b8" }, { "source": "n_846ea2107b8020b8", "target": "n_ab7805ed9214f0cd", "sourceHandle": "yes" }, { "source": "n_846ea2107b8020b8", "target": "n_cee6277450eea564", "sourceHandle": "otherwise" }, { "source": "n_cee6277450eea564", "target": "n_1bc651a9aae08a12" }, { "source": "n_1bc651a9aae08a12", "target": "n_f7075f98301c4ec9" }, { "source": "n_f7075f98301c4ec9", "target": "n_8b2de6b462416bb2", "sourceHandle": "yes" }, { "source": "n_8b2de6b462416bb2", "target": "n_1bc651a9aae08a12" } ] } }, "modules": { "n_6c9c4c3e760e4ffc": { "name": "Read all tasks and Slack users", "imports": [], "source": "export default async function collect({previous,dataSourceId},context){\n const s=previous??{phase:'notion',cursor:null,seen:[],tasks:[],users:[]};\n if(!dataSourceId || dataSourceId.startsWith('YOUR_')) throw Error('Configure the Notion data source ID');\n if(s.phase==='notion'){\n const r=await context.actions.call('notion.query_data_source',{dataSourceId,pageSize:100,...(s.cursor?{startCursor:s.cursor}:{})});\n if(!Array.isArray(r?.results)||typeof r.has_more!=='boolean') throw Error('Invalid Notion response');\n const next=r.next_cursor;\n if(r.has_more&&(typeof next!=='string'||!next||s.seen.includes(next)))throw Error('Invalid Notion cursor');\n if(!r.has_more&&next!==null)throw Error('Inconsistent Notion cursor');\n return {state:{...s,tasks:[...s.tasks,...r.results],phase:r.has_more?'notion':'slack',cursor:r.has_more?next:null,seen:r.has_more?[...s.seen,next]:[]},more:true};\n }\n const r=await context.actions.call('slack.list_users',{limit:200,...(s.cursor?{cursor:s.cursor}:{})});\n if(!Array.isArray(r?.users))throw Error('Invalid Slack user response');\n const next=r.nextCursor;\n if(next!==null&&(typeof next!=='string'||!next||s.seen.includes(next)))throw Error('Invalid Slack cursor');\n return {state:{...s,users:[...s.users,...r.users],cursor:next,seen:next?[...s.seen,next]:s.seen},more:next!==null};\n}\n" }, "n_32536cdf93a49251": { "name": "Match assignees and tasks", "imports": [], "source": "export default function plan({state,user1,user2,name1,name2}){\n const pairs=[[user1,name1],[user2,name2]];\n if(pairs.some(([a,b])=>typeof a!=='string'||!a.trim()||typeof b!=='string'||!b.trim()||a.startsWith('YOUR_')||b.startsWith('YOUR_'))||user1===user2||name1===name2)throw Error('Configure two distinct Notion user IDs and Slack names');\n if(!Array.isArray(state?.users)||!Array.isArray(state?.tasks))throw Error('Incomplete read state');\n const users=pairs.map(([,name])=>{const matches=state.users.filter(u=>!u.isDeleted&&u.realName===name);if(matches.length!==1||!matches[0].userId)throw Error('Slack name must identify exactly one user');return matches[0].userId});\n const out=[];\n for(const page of state.tasks){\n const p=page.properties;if(!p||typeof p!=='object')throw Error('Missing task properties');\n const status=p.Status?.status?.name??p.Status?.select?.name;if(typeof status!=='string')throw Error('Missing Status');if(status==='Done')continue;\n const title=p.Task?.title;if(!Array.isArray(title))throw Error('Missing Task title');const task=title.map(x=>x.plain_text??x.text?.content??'').join('');if(!task)throw Error('Empty task title');\n const people=p['Notion User']?.people;if(!Array.isArray(people)||people.length>=25)throw Error('Missing or possibly truncated Notion User people');\n const ids=people.map(u=>{if(typeof u.id!=='string'||!u.id)throw Error('Assignee ID unavailable');return u.id});\n const due=p.Due?.date?.start;if(typeof due!=='string'||!due)throw Error('Missing task due date');\n const i=pairs.findIndex(([id])=>ids.includes(id));if(i>=0)out.push({channelId:users[i],text:'# TO DO\\n☑️ '+task+' (Due: '+due+')'});\n }\n return {items:out};\n}\n" }, "n_c12c695ffadd34bb": { "name": "Prepare each reminder", "imports": [], "source": "export default function next({items,previous,confirmation}){const i=previous?.index??0;if(i>0&&(!confirmation||typeof confirmation!=='string'))throw Error('Slack send not confirmed');if(i>=items.length)return{state:{index:i},more:false,channelId:'unused',text:'unused'};const item=items[i];return{state:{index:i+1},more:true,channelId:item.channelId,text:item.text};}\n" } } }, "presentation": { "designer": { "version": 1, "flow": { "direction": "vertical" } } } }