{
  "id": "2sUwYje5ZEf8dXH9",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Track changes and approvals in Excel 365",
  "tags": [],
  "nodes": [
    {
      "id": "5b8a06be-2ef3-437c-8003-826d8e868c58",
      "name": "Get rows",
      "type": "n8n-nodes-base.microsoftExcel",
      "position": [
        112,
        128
      ],
      "parameters": {
        "table": {
          "__rl": true,
          "mode": "list",
          "value": "{C46945EA-111F-884A-9CE2-7890BA63B938}",
          "cachedResultUrl": "https://onedrive.live.com/personal/f1cfeff06c99e405/_layouts/15/doc.aspx?resid=1736a44f-46ee-4594-ac0c-ca2529c411a8&cid=f1cfeff06c99e405&activeCell=Sheet2!A1:E23",
          "cachedResultName": "Table_sheet2"
        },
        "filters": {},
        "resource": "table",
        "workbook": {
          "__rl": true,
          "mode": "list",
          "value": "F1CFEFF06C99E405!s1736a44f46ee4594ac0cca2529c411a8",
          "cachedResultUrl": "https://onedrive.live.com/personal/f1cfeff06c99e405/_layouts/15/doc.aspx?resid=1736a44f-46ee-4594-ac0c-ca2529c411a8&cid=f1cfeff06c99e405",
          "cachedResultName": "Contoh Excel N8N"
        },
        "operation": "getRows",
        "returnAll": true,
        "worksheet": {
          "__rl": true,
          "mode": "list",
          "value": "{9ECBF732-2A1B-49F1-AC81-3EB96DB24F0C}",
          "cachedResultUrl": "https://onedrive.live.com/personal/f1cfeff06c99e405/_layouts/15/doc.aspx?resid=1736a44f-46ee-4594-ac0c-ca2529c411a8&cid=f1cfeff06c99e405&activeCell=Sheet2!A1",
          "cachedResultName": "Sheet2"
        }
      },
      "credentials": {
        "microsoftExcelOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "c2076d05-31dd-49b0-8016-4c99ab7a2fed",
      "name": "Normalize Data",
      "type": "n8n-nodes-base.code",
      "position": [
        320,
        128
      ],
      "parameters": {
        "jsCode": "let items = $input.all();\nreturn items.map(item => {\n  return {\n    json: {\n      ...item.json,\n      ID: String(item.json.ID).trim()\n    }\n  };\n});"
      },
      "typeVersion": 2
    },
    {
      "id": "d8329b72-1d5c-43d8-8b71-8512f306cb9a",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -144,
        48
      ],
      "parameters": {
        "color": 7,
        "width": 1024,
        "height": 272,
        "content": "## Step 1 "
      },
      "typeVersion": 1
    },
    {
      "id": "ab56b1b5-ddba-4ea3-8c52-139a87f18974",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -128,
        384
      ],
      "parameters": {
        "color": 7,
        "width": 704,
        "height": 352,
        "content": "## Step 2 "
      },
      "typeVersion": 1
    },
    {
      "id": "8b277663-15b3-421e-bab9-effd20cfb42b",
      "name": "Append Log to Excel",
      "type": "n8n-nodes-base.microsoftExcel",
      "position": [
        384,
        832
      ],
      "parameters": {
        "options": {},
        "dataMode": "autoMap",
        "resource": "worksheet",
        "workbook": {
          "__rl": true,
          "mode": "list",
          "value": "F1CFEFF06C99E405!s1736a44f46ee4594ac0cca2529c411a8",
          "cachedResultUrl": "https://onedrive.live.com/personal/f1cfeff06c99e405/_layouts/15/doc.aspx?resid=1736a44f-46ee-4594-ac0c-ca2529c411a8&cid=f1cfeff06c99e405",
          "cachedResultName": "Contoh Excel N8N"
        },
        "operation": "append",
        "worksheet": {
          "__rl": true,
          "mode": "list",
          "value": "{82742EB8-C5C4-7049-B489-EC3070316882}",
          "cachedResultUrl": "https://onedrive.live.com/personal/f1cfeff06c99e405/_layouts/15/doc.aspx?resid=1736a44f-46ee-4594-ac0c-ca2529c411a8&cid=f1cfeff06c99e405&activeCell=Audit_Log!A1",
          "cachedResultName": "Audit_Log"
        }
      },
      "credentials": {
        "microsoftExcelOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "0f3f7587-3db5-4bf8-a1a9-6999b69b4f28",
      "name": "Append Log to Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        384,
        992
      ],
      "parameters": {
        "columns": {
          "value": {},
          "schema": [
            {
              "id": "No",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "No",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Timestamp",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Timestamp",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "ChangeType",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "ChangeType",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "RowID",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "RowID",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Field",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Field",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "OldValue",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "OldValue",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "NewValue",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "NewValue",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "autoMapInputData",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1H5P7YgBkyFgOSKZE4CMd8bu4L1b4ki7YSSaCpNMaL9E/edit#gid=0",
          "cachedResultName": "Audit_Log"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1H5P7YgBkyFgOSKZE4CMd8bu4L1b4ki7YSSaCpNMaL9E",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1H5P7YgBkyFgOSKZE4CMd8bu4L1b4ki7YSSaCpNMaL9E/edit?usp=drivesdk",
          "cachedResultName": "Contoh n8n"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "f5ed1189-72e8-4c7b-a2d7-27ff4c215b89",
      "name": "Environment Config",
      "type": "n8n-nodes-base.code",
      "position": [
        512,
        128
      ],
      "parameters": {
        "jsCode": "return [{\n  json: {\n    CONFIG: {\n      idField: \"ID\",\n      ignoreFields: [\"UpdatedAt\", \"LastModified\"],\n      monitorOnly: null, \n      firstRunSilent: true,\n      enableAuditLog: true\n    }\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "ad209afb-740f-47ad-8e76-a08e0e1e1e73",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -128,
        800
      ],
      "parameters": {
        "color": 7,
        "width": 704,
        "height": 368,
        "content": "## Step 4 \u2013 Audit Log"
      },
      "typeVersion": 1
    },
    {
      "id": "c0955ae1-fa4d-4e29-a0eb-1e52806b9e72",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1568,
        -592
      ],
      "parameters": {
        "width": 832,
        "height": 3552,
        "content": "# Track changes and approvals in Excel 365\n\n## \ud83d\udccc Overview\n\nThis workflow monitors an Excel 365 sheet every minute and detects new, updated, and deleted rows using a unique ID column. It compares the current dataset with the previous snapshot and identifies field-level differences.\n\nWhen changes are detected, the workflow filters rows that require approval (Status = \u201cWaiting Approval\u201d), sends structured notifications, and optionally logs every field-level change into an audit sheet (Excel or Google Sheets).\n\nThe configuration layer allows you to define the ID column, ignored fields, and audit logging behavior without modifying the comparison logic.\n\nThis template is suitable for approval tracking, operational monitoring, and lightweight compliance logging.\n\n\u2e3b\n\n## \ud83d\ude80 Features\n\n### \u23f1 Scheduled Monitoring\n- Runs automatically every 1 minute\n- Near real-time Excel monitoring\n- Prevents unnecessary execution when no changes are detected\n\n### \ud83d\udd0d Row-Level Change Detection\n\n### Detects:\n- \u2705 New rows\n- \u270f\ufe0f Updated rows\n- \u274c Deleted rows\n\nUses a unique ID field per row for accurate tracking.\n\n\u2e3b\n\n### \ud83e\udde0 Field-Level Comparison\n- Compares previous vs current values\n- Identifies exactly which fields changed\n- Outputs structured change data\n- Prevents false positives via data normalization\n\n\u2e3b\n\n## \u2699\ufe0f Environment Configuration Layer\n\nCentralized configuration node allows easy customization without modifying core logic.\n\n## Configurable options include:\n- idField\n- ignoreFields\n- monitorOnly\n- firstRunSilent\n- enableAuditLog\n\nNo hardcoded logic required.\n\n\u2e3b\n\n## \ud83d\uded1 Approval Validation Layer\n- Filters rows where Status = \"Waiting Approval\"\n- Sends notifications only for relevant approval cases\n- Prevents unnecessary alerts\n\n\u2e3b\n\n## \ud83d\udd14 Smart Notification System\n- Sends formatted change notifications\n- Includes:\n- Change Type (NEW / UPDATED / DELETED)\n- Row ID\n- Field-level old \u2192 new values\n\nFully customizable message formatting.\n\u2e3b\n## \ud83d\udcca Optional Audit Logging\n\nIf enabled in the Environment Config:\n- Converts each field-level change into structured audit rows\n- Appends logs to:\n- Excel 365 (Audit Sheet)\n- Google Sheets (External Log)\n\nAudit Log Structure\n\n| Timestamp | ChangeType | RowID | Field | OldValue | New Value |\n|-------------|--------------|--------|------|----------|------------|\n\nDesigned for compliance and tracking purposes.\n\n## \ud83d\udce6 Use Cases\n- Internal approval tracking\n- Financial data monitoring\n- Sales pipeline control\n- Procurement workflows\n- Excel-based compliance systems\n- SME automation systems\n\n## \ud83e\udde9 Requirements\n- Microsoft 365 (Excel Online \u2013 Business)\n- n8n (Cloud or Self-hosted)\n- Microsoft credentials configured in n8n\n- Telegram Bot\n- (Optional) Google Sheets credentials for audit logging\n\n## \ud83d\udd27 Configuration Guide\nAll system behavior is controlled from the Environment Config node.\n\nExample configuration structure:\n\n```\n{\n  CONFIG: {\n    idField: \"ID\",\n    ignoreFields: [\"UpdatedAt\", \"LastModified\"],\n    monitorOnly: null,\n    firstRunSilent: true,\n    enableAuditLog: true\n  }\n}\n```\n\nYou can customize:\n- Which column acts as unique ID\n- Which fields to ignore\n- Which fields to monitor exclusively\n- Whether to enable audit logging\n- Whether first run should be silent\n\n## \ud83d\udfe2 First Run Behavior\nOn first execution:\n- The workflow initializes internal snapshot storage\n- No mass notification is sent (if firstRunSilent = true)\n\nThis prevents false \u201cNEW row\u201d alerts during setup.\n## \ud83c\udfe2 Who Is This For?\n- Operations teams\n- Finance departments\n- SMEs using Excel as core system\n- Automation consultants\n- Businesses requiring lightweight audit tracking\n\n\u2e3b\n\n## \ud83d\udca1 Why This Workflow?\n\nUnlike simple Excel polling workflows, this solution:\n- Tracks changes at field level\n- Supports approval-based filtering\n- Includes structured audit logging\n- Avoids duplicate alerts\n- Is fully configurable\n- Designed for production usage\n\nThis is not just an Excel notifier \u2014 it is a structured Change Tracking & Approval Monitoring System built on n8n."
      },
      "typeVersion": 1
    },
    {
      "id": "6d5867c7-9ce0-493d-af68-d4b921f04c39",
      "name": "Notification Approval",
      "type": "n8n-nodes-base.telegram",
      "position": [
        864,
        544
      ],
      "parameters": {
        "text": "=Payment Request Need Approval\n\nRequester :  {{ $json.Requester }}\nKeterangan : {{ $json.Name }}\nNominal : {{ $json.Amount }}",
        "chatId": "123456789",
        "replyMarkup": "inlineKeyboard",
        "inlineKeyboard": {
          "rows": [
            {
              "row": {
                "buttons": [
                  {
                    "text": "Approve",
                    "additionalFields": {
                      "url": "https://n8n.entigi.co.id/webhook-test/2ed56b5e-5307-4b7c-bc17-c70da9814eba?status=1&ID={{ $json.ID }}"
                    }
                  },
                  {
                    "text": "Reject",
                    "additionalFields": {
                      "url": "=https://n8n.entigi.co.id/webhook-test/2ed56b5e-5307-4b7c-bc17-c70da9814eba?status=2&ID={{ $json.ID }}"
                    }
                  }
                ]
              }
            }
          ]
        },
        "additionalFields": {}
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "bb0e40aa-f1cb-4d05-a858-fc3431e77f66",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        592,
        464
      ],
      "parameters": {
        "color": 7,
        "width": 496,
        "height": 272,
        "content": " ## Step 3 Approval\n"
      },
      "typeVersion": 1
    },
    {
      "id": "d02a76ce-a5ca-4a3d-bae4-eb1909316a4a",
      "name": "Run every 1 minute",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -96,
        128
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 1
            }
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "36fa3e89-7789-4562-9c1a-abf13e222da6",
      "name": "Check if changes exist",
      "type": "n8n-nodes-base.if",
      "position": [
        -96,
        560
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "a79d003d-7f26-41d5-b722-aec295b87f31",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.hasChanges }}",
              "rightValue": false
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "97834431-1892-4201-9fef-3807fb020835",
      "name": "Check if audit logging is enabled",
      "type": "n8n-nodes-base.switch",
      "position": [
        -96,
        896
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "36f6286b-c585-495a-b282-c4bf3cad1c5f",
                    "operator": {
                      "type": "boolean",
                      "operation": "true",
                      "singleValue": true
                    },
                    "leftValue": "={{ $('Environment Config').item.json.CONFIG.enableAuditLog }}",
                    "rightValue": "true"
                  }
                ]
              }
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 3.3
    },
    {
      "id": "949e7846-c8a3-46fd-9ed0-f459ec5c6398",
      "name": "Compare previous and current snapshot",
      "type": "n8n-nodes-base.code",
      "position": [
        720,
        128
      ],
      "parameters": {
        "jsCode": "// 1. Gunakan sintaks baru untuk Static Data\nconst staticData = $getWorkflowStaticData('global');\n\nif (!staticData.previousData) {\n  staticData.previousData = {};\n}\n\n// 2. Ambil data input menggunakan $input.all()\n//const inputItems = $input.all();\nconst inputItems = $('Get rows').all();\n\n\n// Convert current data to object keyed by ID\nconst currentData = {};\nfor (const item of inputItems) {\n  // Pastikan field 'ID' ada di data kamu\n  if (item.json.ID) {\n    currentData[item.json.ID] = item.json;\n  }\n}\n\nconst changes = [];\n\n// Detect New and Updated rows\nfor (const id in currentData) {\n  const currentRow = currentData[id];\n  const previousRow = staticData.previousData[id];\n\n  if (!previousRow) {  \n    const newFields = [];\n\n    for (const key in currentRow) {\n      newFields.push({\n        source: currentRow,\n        field: key,\n        old: null,\n        new: currentRow[key]\n      });\n    }\n    \n    changes.push({\n      type: \"NEW\",\n      id,\n      changes: newFields\n    });\n    continue;\n  }\n\n  const rowChanges = [];\n  let hasChanged = false;\n  const config = $input.first().json.CONFIG;\n  \n  for (const key in currentRow) {\n    \n    if (key === config.idField) continue;\n    if (config.ignoreFields.includes(key)) continue;\n    if (config.monitorOnly && !config.monitorOnly.includes(key)) continue;\n    \n    if (JSON.stringify(currentRow[key]) !== JSON.stringify(previousRow[key])) {\n      rowChanges.push({\n        source: currentRow,\n        field: key,\n        old: previousRow[key],\n        new: currentRow[key]\n      });\n      hasChanged = true;\n    }\n  }\n  \n  if (hasChanged) {\n    changes.push({\n      type: \"UPDATED\",\n      id,\n      changes: rowChanges\n    });\n  }\n  \n}\n\n// Detect Deleted rows\nfor (const id in staticData.previousData) {\n  if (!currentData[id]) {\n    changes.push({\n      type: \"DELETED\",\n      id\n    });\n  }\n}\n\n// 3. Simpan snapshot untuk run berikutnya\nstaticData.previousData = currentData;\n\n// Kembalikan hasil sebagai array item n8n\nreturn [{\n  json: {\n    hasChanges: changes.length > 0,\n    totalChanges: changes.length,\n    changes\n  }\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "c78ddfa5-c614-4f68-bb49-ce4f9d57b6bd",
      "name": "Transform changes for notification",
      "type": "n8n-nodes-base.code",
      "position": [
        176,
        432
      ],
      "parameters": {
        "jsCode": "let message = \"\ud83d\udcca Excel Update Detected\\n\\n\";\nlet sendMessage = false;\nfor (const change of $json.changes) {\n\n  if (change.type === \"NEW\") {\n    message += `\ud83c\udd95 New Row Added (ID: ${change.id})\\n`;\n    \n    if (Array.isArray(change.changes)) {\n      for (const fieldChange of change.changes) {\n        message += `- ${fieldChange.field}: ${fieldChange.new}\\n`;\n      }\n    }\n    sendMessage=true;\n    message += \"\\n\";\n  }\n\n  if (change.type === \"UPDATED\") {\n    message += `\u270f\ufe0f Row Updated (ID: ${change.id})\\n`;\n\n    if (Array.isArray(change.changes)) {\n      for (const fieldChange of change.changes) {\n        message += `- ${fieldChange.field}: ${fieldChange.old} \u2192 ${fieldChange.new}\\n`;\n      }\n    }\n\n    message += \"\\n\";\n    sendMessage=true;\n  }\n\n  if (change.type === \"DELETED\") {\n    message += `\u274c Row Deleted (ID: ${change.id})\\n\\n`;\n    sendMessage=true;\n  }\n}\n\nreturn [{\n  json: {\n    message,\n    sendMessage\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "c467f737-037f-4e4b-9a07-7b7e31244b52",
      "name": "Build audit log rows",
      "type": "n8n-nodes-base.code",
      "position": [
        144,
        896
      ],
      "parameters": {
        "jsCode": "const auditRows = [];\nfor (const change of $json.changes) {\n  if (change.type === \"DELETED\") {\n    auditRows.push({\n      Timestamp: new Date().toISOString(),\n      ChangeType: \"DELETED\",\n      RowID: change.id,\n      Field: null,\n      OldValue: null,\n      NewValue: null\n    });\n    continue;\n  }\n\n  for (const fieldChange of change.changes) {\n    auditRows.push({\n      Timestamp: new Date().toISOString(),\n      ChangeType: change.type,\n      RowID: change.id,\n      Field: fieldChange.field,\n      OldValue: fieldChange.old ?? null,\n      NewValue: fieldChange.new ?? null\n    });\n  }\n}\n\nreturn auditRows.map(r => ({ json: r }));"
      },
      "typeVersion": 2
    },
    {
      "id": "4ccfc3ae-f8de-4eb2-afd1-d46b52a6d2a2",
      "name": "Filter rows with waiting approval",
      "type": "n8n-nodes-base.code",
      "position": [
        672,
        544
      ],
      "parameters": {
        "jsCode": "// check apakah ada yang waiting approval\nlet change_message = [];\nfor (const change of $json.changes) {\n  for (const fieldChange of change.changes) {\n    if(fieldChange.field=='Status' && fieldChange.new == 'Waiting Approval'){\n      change_message.push(fieldChange.source); \n    }\n  }\n}\n\n// Kembalikan setiap item di dalam 'changes' sebagai item n8n yang terpisah\nreturn change_message.map(change => {\n  return {\n    // json: change\n    json : change\n  };\n});\n"
      },
      "typeVersion": 2,
      "alwaysOutputData": true
    },
    {
      "id": "28a56211-bfc7-49bc-b431-a4bb0fab7e30",
      "name": "Send notification",
      "type": "n8n-nodes-base.telegram",
      "position": [
        384,
        432
      ],
      "parameters": {
        "text": "={{ $json.message }}",
        "chatId": "123456789",
        "forceReply": {},
        "replyMarkup": "forceReply",
        "additionalFields": {}
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "c8c5eb0f-d58a-47c6-898f-fe462713a0be",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -720,
        -592
      ],
      "parameters": {
        "color": 3,
        "width": 576,
        "height": 384,
        "content": "## How it works\n1. Runs every minute using a schedule trigger\n2. Reads rows from Excel 365\n3. Normalizes and stores a snapshot\n4. Compares with the previous state\n5. Detects new, updated, and deleted rows\n6. Filters rows with \u201cWaiting Approval\u201d status\n7. Sends structured notifications\n8. Logs changes if audit logging is enabled\n"
      },
      "typeVersion": 1
    },
    {
      "id": "f7606576-0cec-4113-90e0-6c76c259d904",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -720,
        -176
      ],
      "parameters": {
        "color": 5,
        "width": 560,
        "height": 288,
        "content": "## Setup steps\n1. Configure Microsoft Excel credentials\n2. Ensure your sheet contains a unique ID column\n3. Update the Environment Config node\n4.(Optional) Configure Google Sheets credentials for audit logging\n5. Activate the workflow"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "19874e82-7560-42af-b0c1-9fa47f43cbeb",
  "connections": {
    "Get rows": {
      "main": [
        [
          {
            "node": "Normalize Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Data": {
      "main": [
        [
          {
            "node": "Environment Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send notification": {
      "main": [
        []
      ]
    },
    "Environment Config": {
      "main": [
        [
          {
            "node": "Compare previous and current snapshot",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Run every 1 minute": {
      "main": [
        [
          {
            "node": "Get rows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build audit log rows": {
      "main": [
        [
          {
            "node": "Append Log to Excel",
            "type": "main",
            "index": 0
          },
          {
            "node": "Append Log to Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check if changes exist": {
      "main": [
        [
          {
            "node": "Transform changes for notification",
            "type": "main",
            "index": 0
          },
          {
            "node": "Check if audit logging is enabled",
            "type": "main",
            "index": 0
          },
          {
            "node": "Filter rows with waiting approval",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check if audit logging is enabled": {
      "main": [
        [
          {
            "node": "Build audit log rows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter rows with waiting approval": {
      "main": [
        [
          {
            "node": "Notification Approval",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Transform changes for notification": {
      "main": [
        [
          {
            "node": "Send notification",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Compare previous and current snapshot": {
      "main": [
        [
          {
            "node": "Check if changes exist",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}