|
29 | 29 | }, |
30 | 30 | { |
31 | 31 | "parameters": { |
32 | | - "jsCode": "const staticData = $getWorkflowStaticData('global');\nif (!staticData.mods) staticData.mods = {};\n\nconst changed = [];\nfor (const item of $input.all()) {\n const name = item.json.table_name;\n const mods = item.json.mod_count;\n if (staticData.mods[name] !== mods) {\n staticData.mods[name] = mods;\n changed.push({ json: { table_name: name } });\n }\n}\n\nif (changed.length === 0) {\n return [{ json: { table_name: '__none__', skipped: true } }];\n}\nreturn changed;" |
| 32 | + "jsCode": "const staticData = $getWorkflowStaticData('global');\nif (!staticData.mods) staticData.mods = {};\n\nconst changed = [];\nfor (const item of $input.all()) {\n const name = item.json.table_name;\n const mods = item.json.mod_count;\n if (staticData.mods[name] !== mods) {\n staticData.mods[name] = mods;\n changed.push({ json: { table_name: name } });\n }\n}\n\nif (changed.length === 0) return [{ json: { _skip: true } }];\nreturn changed;" |
33 | 33 | }, |
34 | 34 | "id": "filter-changed", |
35 | 35 | "name": "Filter Changed Tables", |
|
41 | 41 | "parameters": { |
42 | 42 | "conditions": { |
43 | 43 | "options": {"caseSensitive": true, "leftValue": "", "typeValidation": "strict"}, |
44 | | - "conditions": [ |
45 | | - { |
46 | | - "id": "not-skipped", |
47 | | - "leftValue": "={{ $json.skipped }}", |
48 | | - "rightValue": true, |
49 | | - "operator": {"type": "boolean", "operation": "notEquals"} |
50 | | - } |
51 | | - ], |
| 44 | + "conditions": [{"id": "c1", "leftValue": "={{ $json._skip }}", "rightValue": true, "operator": {"type": "boolean", "operation": "notEquals"}}], |
52 | 45 | "combinator": "and" |
53 | 46 | }, |
54 | 47 | "options": {} |
55 | 48 | }, |
56 | | - "id": "if-has-changes", |
| 49 | + "id": "if-changed", |
57 | 50 | "name": "Any Changes?", |
58 | 51 | "type": "n8n-nodes-base.if", |
59 | 52 | "typeVersion": 2.3, |
60 | 53 | "position": [660, 0] |
61 | 54 | }, |
| 55 | + { |
| 56 | + "parameters": { |
| 57 | + "method": "GET", |
| 58 | + "url": "=https://sheets.googleapis.com/v4/spreadsheets/{{ $env.GOOGLE_SHEETS_ID }}?fields=sheets.properties", |
| 59 | + "authentication": "predefinedCredentialType", |
| 60 | + "nodeCredentialType": "googleSheetsApi", |
| 61 | + "options": {} |
| 62 | + }, |
| 63 | + "id": "get-sheet-ids", |
| 64 | + "name": "Get Sheet IDs", |
| 65 | + "type": "n8n-nodes-base.httpRequest", |
| 66 | + "typeVersion": 4.2, |
| 67 | + "position": [880, -100] |
| 68 | + }, |
| 69 | + { |
| 70 | + "parameters": { |
| 71 | + "jsCode": "const sheetsResponse = $('Get Sheet IDs').first().json;\nconst sheetMap = {};\nfor (const s of sheetsResponse.sheets) {\n sheetMap[s.properties.title] = s.properties.sheetId;\n}\n\nconst tables = $('Any Changes?').all().map(i => i.json.table_name);\nconst result = tables.map(t => ({\n json: { table_name: t, sheet_id: sheetMap[t] ?? null, exists: sheetMap[t] != null }\n}));\nreturn result;" |
| 72 | + }, |
| 73 | + "id": "map-sheet-ids", |
| 74 | + "name": "Map Sheet IDs", |
| 75 | + "type": "n8n-nodes-base.code", |
| 76 | + "typeVersion": 2, |
| 77 | + "position": [1100, -100] |
| 78 | + }, |
62 | 79 | { |
63 | 80 | "parameters": {"options": {}}, |
64 | 81 | "id": "split-batches", |
65 | 82 | "name": "Loop Over Tables", |
66 | 83 | "type": "n8n-nodes-base.splitInBatches", |
67 | 84 | "typeVersion": 3, |
68 | | - "position": [880, -100] |
| 85 | + "position": [1320, -100] |
69 | 86 | }, |
70 | 87 | { |
71 | 88 | "parameters": { |
|
74 | 91 | "title": "={{ $json.table_name }}" |
75 | 92 | }, |
76 | 93 | "id": "sheets-create", |
77 | | - "name": "Ensure Sheet Exists", |
| 94 | + "name": "Ensure Sheet", |
78 | 95 | "type": "n8n-nodes-base.googleSheets", |
79 | 96 | "typeVersion": 4.7, |
80 | | - "position": [1100, -100], |
| 97 | + "position": [1540, -100], |
81 | 98 | "onError": "continueRegularOutput" |
82 | 99 | }, |
83 | | - { |
84 | | - "parameters": { |
85 | | - "operation": "clear", |
86 | | - "documentId": {"__rl": true, "mode": "id", "value": "={{ $env.GOOGLE_SHEETS_ID }}"}, |
87 | | - "sheetName": {"__rl": true, "mode": "name", "value": "={{ $('Loop Over Tables').item.json.table_name }}"} |
88 | | - }, |
89 | | - "id": "sheets-clear", |
90 | | - "name": "Clear Sheet", |
91 | | - "type": "n8n-nodes-base.googleSheets", |
92 | | - "typeVersion": 4.7, |
93 | | - "position": [1320, -100] |
94 | | - }, |
95 | 100 | { |
96 | 101 | "parameters": { |
97 | 102 | "operation": "executeQuery", |
|
102 | 107 | "name": "Dump Table", |
103 | 108 | "type": "n8n-nodes-base.postgres", |
104 | 109 | "typeVersion": 2.6, |
105 | | - "position": [1540, -100] |
| 110 | + "position": [1760, -100] |
106 | 111 | }, |
107 | 112 | { |
108 | 113 | "parameters": { |
109 | | - "operation": "append", |
110 | | - "documentId": {"__rl": true, "mode": "id", "value": "={{ $env.GOOGLE_SHEETS_ID }}"}, |
111 | | - "sheetName": {"__rl": true, "mode": "name", "value": "={{ $('Loop Over Tables').item.json.table_name }}"}, |
112 | | - "columns": { |
113 | | - "mappingMode": "autoMapInputData", |
114 | | - "value": {} |
| 114 | + "jsCode": "const rows = $input.all().map(i => i.json);\nconst tableName = $('Loop Over Tables').item.json.table_name;\nlet sheetId = $('Loop Over Tables').item.json.sheet_id;\n\nif (rows.length === 0) {\n return [{ json: { _empty: true, table_name: tableName } }];\n}\n\nconst headers = Object.keys(rows[0]);\nconst csvLines = [headers.join(',')];\nfor (const row of rows) {\n const line = headers.map(h => {\n const v = String(row[h] ?? '');\n if (v.includes(',') || v.includes('\"') || v.includes('\\n')) {\n return '\"' + v.replace(/\"/g, '\"\"') + '\"';\n }\n return v;\n }).join(',');\n csvLines.push(line);\n}\n\nconst requests = [];\nif (sheetId != null) {\n requests.push({ updateCells: { range: { sheetId: sheetId }, fields: 'userEnteredValue' } });\n}\nrequests.push({\n pasteData: {\n coordinate: { sheetId: sheetId ?? 0, rowIndex: 0, columnIndex: 0 },\n data: csvLines.join('\\n'),\n type: 'PASTE_NORMAL',\n delimiter: ','\n }\n});\n\nreturn [{ json: { _empty: false, table_name: tableName, body: { requests } } }];" |
| 115 | + }, |
| 116 | + "id": "to-csv", |
| 117 | + "name": "Build CSV + Payload", |
| 118 | + "type": "n8n-nodes-base.code", |
| 119 | + "typeVersion": 2, |
| 120 | + "position": [1980, -100] |
| 121 | + }, |
| 122 | + { |
| 123 | + "parameters": { |
| 124 | + "conditions": { |
| 125 | + "options": {"caseSensitive": true, "leftValue": "", "typeValidation": "strict"}, |
| 126 | + "conditions": [{"id": "c1", "leftValue": "={{ $json._empty }}", "rightValue": true, "operator": {"type": "boolean", "operation": "notEquals"}}], |
| 127 | + "combinator": "and" |
115 | 128 | }, |
116 | 129 | "options": {} |
117 | 130 | }, |
118 | | - "id": "sheets-append", |
119 | | - "name": "Write to Sheet", |
120 | | - "type": "n8n-nodes-base.googleSheets", |
121 | | - "typeVersion": 4.7, |
122 | | - "position": [1760, -100] |
| 131 | + "id": "if-not-empty", |
| 132 | + "name": "Has Rows?", |
| 133 | + "type": "n8n-nodes-base.if", |
| 134 | + "typeVersion": 2.3, |
| 135 | + "position": [2200, -100] |
| 136 | + }, |
| 137 | + { |
| 138 | + "parameters": { |
| 139 | + "method": "POST", |
| 140 | + "url": "=https://sheets.googleapis.com/v4/spreadsheets/{{ $env.GOOGLE_SHEETS_ID }}:batchUpdate", |
| 141 | + "authentication": "predefinedCredentialType", |
| 142 | + "nodeCredentialType": "googleSheetsApi", |
| 143 | + "sendBody": true, |
| 144 | + "specifyBody": "json", |
| 145 | + "jsonBody": "={{ JSON.stringify($json.body) }}", |
| 146 | + "options": {} |
| 147 | + }, |
| 148 | + "id": "paste-data", |
| 149 | + "name": "Paste to Sheet", |
| 150 | + "type": "n8n-nodes-base.httpRequest", |
| 151 | + "typeVersion": 4.2, |
| 152 | + "position": [2420, -200] |
123 | 153 | } |
124 | 154 | ], |
125 | 155 | "connections": { |
126 | 156 | "Every 5 Minutes": {"main": [[{"node": "List Tables + Mod Counts", "type": "main", "index": 0}]]}, |
127 | 157 | "List Tables + Mod Counts": {"main": [[{"node": "Filter Changed Tables", "type": "main", "index": 0}]]}, |
128 | 158 | "Filter Changed Tables": {"main": [[{"node": "Any Changes?", "type": "main", "index": 0}]]}, |
129 | | - "Any Changes?": {"main": [[{"node": "Loop Over Tables", "type": "main", "index": 0}], []]}, |
130 | | - "Loop Over Tables": {"main": [[], [{"node": "Ensure Sheet Exists", "type": "main", "index": 0}]]}, |
131 | | - "Ensure Sheet Exists": {"main": [[{"node": "Clear Sheet", "type": "main", "index": 0}]]}, |
132 | | - "Clear Sheet": {"main": [[{"node": "Dump Table", "type": "main", "index": 0}]]}, |
133 | | - "Dump Table": {"main": [[{"node": "Write to Sheet", "type": "main", "index": 0}]]}, |
134 | | - "Write to Sheet": {"main": [[{"node": "Loop Over Tables", "type": "main", "index": 0}]]} |
| 159 | + "Any Changes?": {"main": [[{"node": "Get Sheet IDs", "type": "main", "index": 0}], []]}, |
| 160 | + "Get Sheet IDs": {"main": [[{"node": "Map Sheet IDs", "type": "main", "index": 0}]]}, |
| 161 | + "Map Sheet IDs": {"main": [[{"node": "Loop Over Tables", "type": "main", "index": 0}]]}, |
| 162 | + "Loop Over Tables": {"main": [[], [{"node": "Ensure Sheet", "type": "main", "index": 0}]]}, |
| 163 | + "Ensure Sheet": {"main": [[{"node": "Dump Table", "type": "main", "index": 0}]]}, |
| 164 | + "Dump Table": {"main": [[{"node": "Build CSV + Payload", "type": "main", "index": 0}]]}, |
| 165 | + "Build CSV + Payload": {"main": [[{"node": "Has Rows?", "type": "main", "index": 0}]]}, |
| 166 | + "Has Rows?": {"main": [[{"node": "Paste to Sheet", "type": "main", "index": 0}], [{"node": "Loop Over Tables", "type": "main", "index": 0}]]}, |
| 167 | + "Paste to Sheet": {"main": [[{"node": "Loop Over Tables", "type": "main", "index": 0}]]} |
135 | 168 | }, |
136 | 169 | "settings": {"executionOrder": "v1"} |
137 | 170 | } |
0 commit comments