{ "kind": "open-flow-workflow", "version": 1, "name": "Email a Baserow Investment Report", "content": { "modelVersion": 14, "document": { "bindings": {}, "graph": { "nodes": { "n_34e9b9e41a1de237": { "name": "Run the report now", "kind": "start", "inputDefs": [] }, "n_c7e97455ec11d9d7": { "name": "Monday to Saturday at 07:15 UTC", "kind": "cron", "cronTimes": [ { "type": "cron", "expression": "15 7 * * 1-6", "timezone": "UTC" } ] }, "n_6b05dc950da53f7c": { "inputs": { "tableId": { "kind": "unset" }, "previous": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_6b05dc950da53f7c", "output": "state" } ] }, "quote": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_8fccc991e23768c9", "output": "body" } ] } }, "name": "Prepare the holdings report", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "integer", "minimum": 1 }, "nullable": false, "handle": "tableId" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": true, "handle": "previous" }, { "jsonSchema": { "type": "string" }, "nullable": true, "handle": "quote" } ], "outputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "state" }, { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "more" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "url" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "html" } ], "name": "Prepare the holdings report", "moduleId": "n_243afe2729b3e6fb", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [ { "action": "baserow.list_table_rows" } ] } ] } }, "n_2887ceccaf0595ab": { "inputs": {}, "name": "Another holding?", "kind": "condition", "matchMode": "first", "cases": [ { "output": "yes", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_6b05dc950da53f7c", "output": "more" } }, "operator": "isTrue" } ] } ] } ] }, "n_8fccc991e23768c9": { "inputs": { "url": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_6b05dc950da53f7c", "output": "url" } ] } }, "name": "Get the Tradegate quote", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "url" } ], "outputs": [ { "jsonSchema": { "type": "string" }, "nullable": true, "handle": "body" }, { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "statusCode" }, { "jsonSchema": { "type": "object", "additionalProperties": { "type": "string" } }, "nullable": false, "handle": "headers" } ], "name": "Get the Tradegate quote", "executor": { "method": "GET", "query": { "mode": "fields", "names": [] }, "headers": { "mode": "fields", "names": [] }, "bodyFormat": "none", "authentication": { "type": "none" }, "responseFormat": "text", "statusPolicy": "success", "kind": "http-request" } } }, "n_7ae1c68ae16d99b2": { "inputs": { "from": { "kind": "value", "value": { "email": "sender@example.com" } }, "to": { "kind": "value", "value": [ { "email": "recipient@example.com" } ] }, "subject": { "kind": "value", "value": "Investment report" }, "htmlContent": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_6b05dc950da53f7c", "output": "html" } ] } }, "name": "Email the investment report", "kind": "task", "task": { "inputs": [ { "description": "The verified sender used for this email.", "jsonSchema": { "type": "object", "properties": { "email": { "type": "string", "format": "email", "description": "The sender email address." }, "name": { "type": "string", "minLength": 1, "description": "The optional sender display name." } }, "required": [ "email" ], "additionalProperties": false, "description": "The verified sender used for this email." }, "nullable": false, "handle": "from" }, { "description": "The primary recipients for this email.", "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "email": { "type": "string", "format": "email", "description": "The recipient email address." }, "name": { "type": "string", "minLength": 1, "description": "The optional recipient display name." } }, "required": [ "email" ], "additionalProperties": false, "description": "One SendGrid email recipient." }, "minItems": 1, "description": "The primary recipients for this email." }, "nullable": false, "handle": "to" }, { "description": "The optional Cc recipients.", "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "email": { "type": "string", "format": "email", "description": "The recipient email address." }, "name": { "type": "string", "minLength": 1, "description": "The optional recipient display name." } }, "required": [ "email" ], "additionalProperties": false, "description": "One SendGrid email recipient." }, "minItems": 1, "description": "The optional Cc recipients." }, "nullable": true, "handle": "cc", "value": null }, { "description": "The optional Bcc recipients.", "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "email": { "type": "string", "format": "email", "description": "The recipient email address." }, "name": { "type": "string", "minLength": 1, "description": "The optional recipient display name." } }, "required": [ "email" ], "additionalProperties": false, "description": "One SendGrid email recipient." }, "minItems": 1, "description": "The optional Bcc recipients." }, "nullable": true, "handle": "bcc", "value": null }, { "description": "The optional Reply-To override.", "jsonSchema": { "type": "object", "properties": { "email": { "type": "string", "format": "email", "description": "The sender email address." }, "name": { "type": "string", "minLength": 1, "description": "The optional sender display name." } }, "required": [ "email" ], "additionalProperties": false, "description": "The optional Reply-To override." }, "nullable": true, "handle": "replyTo", "value": null }, { "description": "The email subject line.", "jsonSchema": { "type": "string", "minLength": 1, "description": "The email subject line." }, "nullable": true, "handle": "subject", "value": null }, { "description": "The optional HTML body content.", "jsonSchema": { "type": "string", "description": "The optional HTML body content." }, "nullable": true, "handle": "htmlContent", "value": null }, { "description": "The optional plain-text body content.", "jsonSchema": { "type": "string", "description": "The optional plain-text body content." }, "nullable": true, "handle": "textContent", "value": null }, { "description": "The optional SendGrid template ID.", "jsonSchema": { "type": "string", "minLength": 1, "description": "The optional SendGrid template ID." }, "nullable": true, "handle": "templateId", "value": null }, { "description": "The optional dynamic template data sent with the email.", "jsonSchema": { "type": "object", "properties": {}, "additionalProperties": true, "description": "The optional dynamic template data sent with the email." }, "nullable": true, "handle": "dynamicTemplateData", "value": null }, { "description": "The optional SendGrid categories attached to this email.", "jsonSchema": { "type": "array", "items": { "type": "string", "minLength": 1, "description": "One SendGrid category name." }, "maxItems": 10, "description": "The optional SendGrid categories attached to this email." }, "nullable": true, "handle": "categories", "value": null }, { "description": "The optional custom arguments attached to this email.", "jsonSchema": { "type": "object", "additionalProperties": { "type": "string", "description": "A record value." }, "description": "The optional custom arguments attached to this email." }, "nullable": true, "handle": "customArgs", "value": null }, { "description": "The optional Unix timestamp that schedules this email.", "jsonSchema": { "type": "integer", "exclusiveMinimum": 0, "description": "The optional Unix timestamp that schedules this email." }, "nullable": true, "handle": "sendAt", "value": null }, { "description": "The optional attachments included with this email.", "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "contentBase64": { "type": "string", "minLength": 1, "description": "The Base64-encoded attachment content." }, "filename": { "type": "string", "minLength": 1, "description": "The attachment file name." }, "type": { "type": "string", "minLength": 1, "description": "The optional attachment MIME type." }, "disposition": { "type": "string", "enum": [ "attachment", "inline" ], "description": "The optional attachment disposition." }, "contentId": { "type": "string", "minLength": 1, "description": "The optional content ID used for inline attachments." } }, "required": [ "contentBase64", "filename" ], "additionalProperties": false, "description": "One attachment accepted by the SendGrid Mail Send API." }, "description": "The optional attachments included with this email." }, "nullable": true, "handle": "attachments", "value": null } ], "outputs": [ { "description": "Whether SendGrid accepted the email for delivery.", "jsonSchema": { "type": "boolean", "description": "Whether SendGrid accepted the email for delivery." }, "nullable": false, "handle": "accepted" }, { "description": "The optional SendGrid message ID response header.", "jsonSchema": { "type": [ "string", "null" ], "description": "The optional SendGrid message ID response header." }, "nullable": true, "handle": "messageId" } ], "name": "Email the investment report", "executor": { "kind": "connector", "action": "sendgrid.send_email" } } } }, "edges": [ { "source": "n_34e9b9e41a1de237", "target": "n_6b05dc950da53f7c" }, { "source": "n_c7e97455ec11d9d7", "target": "n_6b05dc950da53f7c" }, { "source": "n_6b05dc950da53f7c", "target": "n_2887ceccaf0595ab" }, { "source": "n_2887ceccaf0595ab", "target": "n_8fccc991e23768c9", "sourceHandle": "yes" }, { "source": "n_8fccc991e23768c9", "target": "n_6b05dc950da53f7c" }, { "source": "n_2887ceccaf0595ab", "target": "n_7ae1c68ae16d99b2", "sourceHandle": "otherwise" } ] } }, "modules": { "n_243afe2729b3e6fb": { "name": "Prepare the holdings report", "imports": [], "source": "export default async function portfolio({tableId,previous,quote},context){\n if(!Number.isSafeInteger(tableId)||tableId<1)throw Error('Configure a Baserow table ID');\n const s=previous??{page:1,morePages:true,rows:[],entries:[],pending:null};\n if(s.pending){if(typeof quote!=='string')throw Error('Missing quote page');const row=s.pending,isin=String(row.ISIN??'').trim().toUpperCase();const matches=[...quote.matchAll(/]*>\\s*([A-Z]{2}[A-Z0-9]{10})\\s*<\\/td>/gi)].map(x=>x[1].toUpperCase());if(!matches.includes(isin))throw Error('Quote ISIN mismatch');const m=quote.match(/<[^>]+\\bid=[\"']bid[\"'][^>]*>\\s*([\\d.,]+)\\s*<\\//i);if(!m)throw Error('Missing bid');const bid=Number(m[1].replace(/\\./g,'').replace(',','.')),count=Number(row.Count),purchase=Number(row['Purchase Price']);if(!Number.isFinite(bid)||!Number.isFinite(count)||!Number.isFinite(purchase)||purchase===0)throw Error('Invalid amount');const current=bid*count,change=current-purchase;s.entries.push({Name:String(row.Name??''),ISIN:isin,Count:count,'Purchase Price':purchase,'Current Value':current.toFixed(2),Change:change.toFixed(2),'Change (%)':(change/purchase*100).toFixed(2)});s.pending=null;}\n if(!s.rows.length&&s.morePages){const r=await context.actions.call('baserow.list_table_rows',{tableId,userFieldNames:true,page:s.page,size:100});if(!r||!Array.isArray(r.rows)||(r.next!==null&&typeof r.next!=='string'))throw Error('Invalid Baserow page');s.rows=r.rows;s.morePages=r.next!==null;s.page++;if(!s.rows.length&&s.morePages)throw Error('Empty Baserow page with next link');}\n if(s.rows.length){s.pending=s.rows.shift();const isin=String(s.pending.ISIN??'').trim().toUpperCase();if(!/^[A-Z]{2}[A-Z0-9]{10}$/.test(isin))throw Error('Invalid ISIN');return{state:s,more:true,url:'https://www.tradegatebsx.com/orderbuch.php?isin='+encodeURIComponent(isin),html:''};}\n if(s.morePages)return{state:s,more:true,url:'',html:''};\n if(!s.entries.length)throw Error('No holdings to report');const esc=x=>String(x).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const keys=['Name','ISIN','Count','Purchase Price','Current Value','Change','Change (%)'],total=s.entries.reduce((a,x)=>a+Number(x['Current Value']),0),html='

Investments as of '+esc(new Date().toLocaleString('en-IE',{timeZone:'Europe/Dublin'}))+'

'+keys.map(x=>'').join('')+''+s.entries.map(r=>''+keys.map(k=>'').join('')+'').join('')+'
'+esc(x)+'
'+esc(r[k])+'

Total: '+total.toFixed(2)+'

';return{state:s,more:false,url:'',html};\n}\n" } } }, "presentation": { "designer": { "version": 1, "flow": { "direction": "vertical" } } } }