{
  "id": "38DYe2Tgzx3bDAuh",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Android Feature Flag Cleanup Automation",
  "tags": [
    {
      "id": "F5TgwgrB2LTO3oA7",
      "name": "completed",
      "createdAt": "2026-01-02T08:54:40.571Z",
      "updatedAt": "2026-01-02T08:54:40.571Z"
    }
  ],
  "nodes": [
    {
      "id": "a1a80a8e-cfd4-405c-b89b-bf1821d35c9c",
      "name": "Code: Compare Flags",
      "type": "n8n-nodes-base.code",
      "position": [
        1616,
        -1520
      ],
      "parameters": {
        "jsCode": "// TEMP: flags found in Android code\nconst codeFlags = [\"Admin\"];\n\n// Firebase flags from Set node\nconst firebaseFlags = items[0].json.flags || [];\n\nconst codeSet = new Set(codeFlags);\n\nconst usedFirebaseFlags = firebaseFlags.filter(f => codeSet.has(f));\nconst unusedFirebaseFlags = firebaseFlags.filter(f => !codeSet.has(f));\n\nreturn [\n  {\n    json: {\n      codeFlags,\n      usedFirebaseFlags,\n      unusedFirebaseFlags\n    }\n  }\n];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "6b951151-0e84-4cfa-bf79-f5356538eeb7",
      "name": "Extercet key flags",
      "type": "n8n-nodes-base.code",
      "position": [
        720,
        -1520
      ],
      "parameters": {
        "jsCode": "return items.map(item => {\n  const code = item.json.code;\n\n  // Simple regex examples\n  const stringKeys = [...code.matchAll(/\"([^\"]+)\"/g)].map(m => m[1]);\n\n  return {\n    json: {\n      fileName: item.json.fileName,\n      filePath: item.json.filePath,\n      stringLiterals: [...new Set(stringKeys)]\n    }\n  };\n});\n"
      },
      "typeVersion": 2
    },
    {
      "id": "9a087f52-47dd-4c75-9c16-85b623c45495",
      "name": "Filter keys",
      "type": "n8n-nodes-base.code",
      "position": [
        944,
        -1520
      ],
      "parameters": {
        "jsCode": "return items.map(item => {\n  const cleanKeys = item.json.stringLiterals.filter(key =>\n    key.length <= 30 &&           // avoid long UI text\n    !key.includes('\\n') &&        // remove multiline\n    !key.includes('$')            // remove template strings\n  );\n\n  return {\n    json: {\n      fileName: item.json.fileName,\n      filePath: item.json.filePath,\n      keys: [...new Set(cleanKeys)]\n    }\n  };\n});\n"
      },
      "typeVersion": 2
    },
    {
      "id": "3a3700e8-3f06-4ca2-9de9-6d70654ef938",
      "name": "classify key",
      "type": "n8n-nodes-base.code",
      "position": [
        1168,
        -1520
      ],
      "parameters": {
        "jsCode": "return items.map(item => {\n  const featureFlags = [];\n  const configKeys = [];\n  const uiKeys = [];\n\n  for (const key of item.json.keys) {\n    if (/on\\s|called/i.test(key)) {\n      uiKeys.push(key);\n    } else if (/admin|feature|enable|disable|flag/i.test(key)) {\n      featureFlags.push(key);\n    } else {\n      configKeys.push(key);\n    }\n  }\n\n  return {\n    json: {\n      fileName: item.json.fileName,\n      filePath: item.json.filePath,\n      featureFlags,\n      configKeys,\n      uiKeys\n    }\n  };\n});\n"
      },
      "typeVersion": 2
    },
    {
      "id": "e13b992d-1a51-4534-82d4-063c2c9ca6f2",
      "name": "Weekly Schedule Trigger",
      "type": "n8n-nodes-base.cron",
      "position": [
        -1296,
        -1520
      ],
      "parameters": {
        "triggerTimes": {
          "item": [
            {
              "hour": 9,
              "mode": "everyWeek",
              "minute": 30
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "e999844b-f908-4591-be73-4a07ab991680",
      "name": "Set: Repository Configuration",
      "type": "n8n-nodes-base.set",
      "position": [
        -1072,
        -1520
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "f2eb9f20-ceda-4486-aeee-b7a7d653a1fe",
              "name": "GITHUB_OWNER",
              "type": "string",
              "value": "Github_ownername_here"
            },
            {
              "id": "9a1ae444-27eb-4444-981c-ca4d58c8d8f1",
              "name": "GITHUB_REPO",
              "type": "string",
              "value": "Github_repo_here"
            },
            {
              "id": "10855b23-e2ea-4a5b-9b8d-b027b6289667",
              "name": "BASE_BRANCH",
              "type": "string",
              "value": "master"
            },
            {
              "id": "1a585ec8-3012-45fe-a0cc-1315dd96fe36",
              "name": "FILE_EXTS_CSV",
              "type": "string",
              "value": ".kt,.java"
            },
            {
              "id": "4ba18209-19b7-45ad-995c-2bfc4831461a",
              "name": "MAX_FILES",
              "type": "number",
              "value": 500
            },
            {
              "id": "b07ef56f-df61-4c6a-8aff-9bccf3b5ef8c",
              "name": "THRESHOLD_DAYS",
              "type": "number",
              "value": 30
            },
            {
              "id": "16edf04d-63c5-4166-8c6b-2ba8b953bf60",
              "name": "FIREBASE_PROJECT_ID",
              "type": "string",
              "value": "Your_project_id_here"
            },
            {
              "id": "8906d1d9-129d-413c-8aab-8cee2d4630e0",
              "name": "ENABLE_PR",
              "type": "boolean",
              "value": true
            },
            {
              "id": "426e8238-ee9a-47da-9744-11573f339ae6",
              "name": "ENABLE_JIRA",
              "type": "boolean",
              "value": true
            },
            {
              "id": "d1938a7d-defb-44c7-82b2-8a38b9468556",
              "name": "SHEET_ID",
              "type": "string",
              "value": ""
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "a34a9a42-f760-4b9b-946c-ba1a4aabefa9",
      "name": "Get Branch Details",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -848,
        -1520
      ],
      "parameters": {
        "url": "=https://api.github.com/repos/{{$json.GITHUB_OWNER}}/{{$json.GITHUB_REPO}}/branches/master",
        "options": {
          "response": {
            "response": {}
          }
        },
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "githubApi"
      },
      "credentials": {
        "githubApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "6a3c92e2-f586-4553-8160-ae06d82b4bd0",
      "name": "Get Repository File List",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -624,
        -1520
      ],
      "parameters": {
        "url": "=https://api.github.com/repos/{{$('Set: Repository Configuration').item.json.GITHUB_OWNER}}/{{ $('Set: Repository Configuration').item.json.GITHUB_REPO }}/git/trees/{{$json.commit.sha}}?recursive=1",
        "options": {},
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "githubApi"
      },
      "credentials": {
        "githubApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "646d31dd-e01a-4e72-911d-2e684c989151",
      "name": "Process Each File Separately",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        -400,
        -1520
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "tree"
      },
      "typeVersion": 1
    },
    {
      "id": "a76757b9-70aa-40d3-a4af-d6a2785bf97c",
      "name": "check file IF: .kt or .java",
      "type": "n8n-nodes-base.if",
      "position": [
        -208,
        -1520
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "ea4135b2-5c35-41b6-8b4f-77cd27b62993",
              "operator": {
                "type": "string",
                "operation": "regex"
              },
              "leftValue": "={{ $json.path }}",
              "rightValue": ".kt"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "67a9e0bc-cf1a-4f95-acb7-5b95c13a4789",
      "name": "Download File Content",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        48,
        -1520
      ],
      "parameters": {
        "url": "=https://api.github.com/repos/{{ $('Set: Repository Configuration').item.json.GITHUB_OWNER }}/{{ $('Set: Repository Configuration').item.json.GITHUB_REPO }}/contents/{{$json.path}}?ref=master",
        "options": {}
      },
      "typeVersion": 4.3
    },
    {
      "id": "71e964a1-2dde-4504-9616-482da51395eb",
      "name": "Read Files One by One",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        272,
        -1520
      ],
      "parameters": {
        "options": {},
        "batchSize": "=1"
      },
      "typeVersion": 3
    },
    {
      "id": "8eff63c7-fe89-433c-b0f0-b049de6a7d19",
      "name": "Decode File Content",
      "type": "n8n-nodes-base.code",
      "position": [
        496,
        -1520
      ],
      "parameters": {
        "jsCode": "return items.map(item => {\n  const base64Content = item.json.content;\n\n  if (!base64Content) {\n    // Skip items without content\n    return null;\n  }\n\n  const buffer = Buffer.from(base64Content, 'base64');\n  const text = buffer.toString('utf8');\n\n  return {\n    json: {\n      code: text,\n      filePath: item.json.path,\n      fileName: item.json.name\n    }\n  };\n}).filter(Boolean);\n"
      },
      "typeVersion": 2
    },
    {
      "id": "78d07e93-fe93-489f-9919-9ab9f96d1119",
      "name": "Mock Firebase Flags Data",
      "type": "n8n-nodes-base.set",
      "position": [
        1392,
        -1520
      ],
      "parameters": {
        "mode": "raw",
        "options": {},
        "jsonOutput": "{\n  \"source\": \"firebase\",\n  \"flags\": [\n    \"feature_login_v2\",\n    \"enable_new_ui\",\n    \"admin_panel_enabled\",\n    \"paywall_experiment\",\n    \"unused_old_flag\"\n  ]\n}\n"
      },
      "typeVersion": 3.4
    },
    {
      "id": "5fcb7efb-29dd-4ef6-926a-3f433673fab4",
      "name": "Create Final Summary Report",
      "type": "n8n-nodes-base.code",
      "position": [
        1840,
        -1520
      ],
      "parameters": {
        "jsCode": "return [\n  {\n    json: {\n      summary: {\n        totalCodeFlags: items[0].json.codeFlags.length,\n        totalFirebaseFlags:\n          items[0].json.usedFirebaseFlags.length +\n          items[0].json.unusedFirebaseFlags.length,\n        unusedFirebaseFlagsCount:\n          items[0].json.unusedFirebaseFlags.length\n      },\n      details: items[0].json\n    }\n  }\n];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "e8211969-805b-441c-877b-e2c2c314d73b",
      "name": "Send Slack Report",
      "type": "n8n-nodes-base.slack",
      "position": [
        2080,
        -1520
      ],
      "parameters": {
        "text": "=*Android Feature Flag Weekly Report*  \n*Summary* \n  \u2022 Total code flags found: {{$json.summary.totalCodeFlags}} \n  \u2022 Total Firebase flags: {{$json.summary.totalFirebaseFlags}}    \n  \u2022 Unused Firebase flags:{{$json.summary.unusedFirebaseFlagsCount}} \n\n*Code Flags* \n\u2022 {{$json.details.codeFlags.join(', ')}}  \n\n*Unused Firebase Flags (Cleanup Candidates)* \n{{$json.details.unusedFirebaseFlags.map(f => '\u2022 ' + f).join('\\n')}}  \n\nAction: Please review these flags and confirm if they can be safely removed from Firebase Remote Config.",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C09S57E2JQ2",
          "cachedResultName": "n8n"
        },
        "otherOptions": {
          "includeLinkToWorkflow": false
        }
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "1398a3b9-b84d-4c7b-a073-441de81e4ae5",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1376,
        -1712
      ],
      "parameters": {
        "color": 7,
        "width": 1312,
        "height": 432,
        "content": "## GitHub File Collection & Android File FilterI'm a note \nThis part starts the workflow automatically every week at the scheduled time.\nIt loads all required settings like GitHub repository name, branch and scan options.\nThen it connects to GitHub and gets the complete list of project files.\nAfter that, files are checked one by one and only Android files are selected for scanning."
      },
      "typeVersion": 1
    },
    {
      "id": "bb22e22d-d531-4d4c-be8a-f9c5fa3cc217",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        -1712
      ],
      "parameters": {
        "color": 7,
        "width": 1312,
        "height": 432,
        "content": "## Code Reading & Flag Detection\n\nThis process downloads the content of each selected Android file from GitHub.\nIt reads files one by one and converts the encoded content into readable code.\nThen it extracts text values used inside the code and removes unwanted or duplicate data.\nFinally, it identifies possible feature flags, config keys and normal UI text."
      },
      "typeVersion": 1
    },
    {
      "id": "8dc27c8f-dbbd-438c-9928-ffef6783b907",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1344,
        -1712
      ],
      "parameters": {
        "color": 7,
        "width": 928,
        "height": 432,
        "content": "## Firebase Comparison & Final Report\n\nThis part uses sample Firebase flags for testing until the real Firebase API is connected.\nIt compares Firebase flags with flags found in the Android code.\nThen it prepares a summary report with totals and unused flags.\nFinally, the report is sent to the Slack channel for review."
      },
      "typeVersion": 1
    },
    {
      "id": "e0fd2e66-039d-4216-b614-6cfb24d9f101",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2064,
        -2240
      ],
      "parameters": {
        "width": 512,
        "height": 576,
        "content": "## How it works\n\nThis workflow runs automatically every week to check feature flags used in an Android project. It first connects to GitHub and scans Android source files (.kt and .java) from the selected repository. Then it reads the code, finds possible feature flags, config keys and other related values.\n\nNext, it compares those code flags with Firebase Remote Config flags. This helps identify flags that still exist in Firebase but are no longer used in the app code.\n\nAfter comparison, the workflow creates a short summary report showing total flags found, active flags and unused Firebase flags that may be removed. Finally, the report is automatically sent to a Slack channel so the team can review cleanup candidates.\n\n## Setup steps\n\n**1.** Add your GitHub credential in n8n.\n**2.** Update Repository configuration with your GitHub owner, repository name and branch.\n**3.** Add your Slack credential and choose the channel for reports.\n**4.** Replace test Firebase flags with real Firebase Remote Config API credentials when ready.\n**5.** Set weekly schedule time in the Cron node if needed.\n**6.** Run once manually to test the workflow output.\n**7.** Enable the workflow for automatic weekly reporting."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "14b5d09e-a66c-4580-bb65-6b0b912b1455",
  "connections": {
    "Filter keys": {
      "main": [
        [
          {
            "node": "classify key",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "classify key": {
      "main": [
        [
          {
            "node": "Mock Firebase Flags Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extercet key flags": {
      "main": [
        [
          {
            "node": "Filter keys",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Branch Details": {
      "main": [
        [
          {
            "node": "Get Repository File List",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: Compare Flags": {
      "main": [
        [
          {
            "node": "Create Final Summary Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Decode File Content": {
      "main": [
        [
          {
            "node": "Extercet key flags",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download File Content": {
      "main": [
        [
          {
            "node": "Read Files One by One",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Files One by One": {
      "main": [
        [],
        [
          {
            "node": "Decode File Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Weekly Schedule Trigger": {
      "main": [
        [
          {
            "node": "Set: Repository Configuration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Repository File List": {
      "main": [
        [
          {
            "node": "Process Each File Separately",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mock Firebase Flags Data": {
      "main": [
        [
          {
            "node": "Code: Compare Flags",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Final Summary Report": {
      "main": [
        [
          {
            "node": "Send Slack Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "check file IF: .kt or .java": {
      "main": [
        [
          {
            "node": "Download File Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Each File Separately": {
      "main": [
        [
          {
            "node": "check file IF: .kt or .java",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set: Repository Configuration": {
      "main": [
        [
          {
            "node": "Get Branch Details",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}