diff --git a/.github/workflows/platform-collect-code-references.yml b/.github/workflows/platform-collect-code-references.yml
new file mode 100644
index 000000000000..68bac08dc776
--- /dev/null
+++ b/.github/workflows/platform-collect-code-references.yml
@@ -0,0 +1,19 @@
+name: Code references
+
+on:
+ push:
+ branches:
+ - main
+ workflow_dispatch:
+
+jobs:
+ collect-code-references:
+ name: Collect
+ uses: Flagsmith/ci/.github/workflows/collect-code-references.yml@v1.0.0
+ permissions:
+ contents: read # For scanning feature flag code references
+ with:
+ flagsmith_project_id: ${{ fromJSON(vars.FLAGSMITH_PROJECT_ID) }}
+ flagsmith_admin_api_url: https://api.flagsmith.com
+ secrets:
+ flagsmith_admin_api_key: ${{ secrets.FLAGSMITH_CODE_REFERENCES_API_KEY }}
diff --git a/.github/workflows/poc-github-code-references.yml b/.github/workflows/poc-github-code-references.yml
deleted file mode 100644
index a9990e0f65b1..000000000000
--- a/.github/workflows/poc-github-code-references.yml
+++ /dev/null
@@ -1,153 +0,0 @@
-name: 'PoC: GitHub Code References'
-permissions:
- contents: read
-
-on:
- schedule:
- - cron: '0 0 * * *' # Runs daily at midnight UTC
- workflow_dispatch:
-
-env:
- EXCLUDE_PATTERNS: node_modules,venv,.git,cache,build,htmlcov,docs,.json,tests
- FLAGSMITH_ADMIN_API_URL: https://api.flagsmith.com
- FLAGSMITH_ADMIN_API_KEY: ${{ secrets.FLAGSMITH_CODE_REFERENCES_API_KEY }}
- FLAGSMITH_PROJECT_ID: 12
- PYTHON_VERSION: '3.13'
-
-jobs:
- collect-code-references:
- runs-on: depot-ubuntu-latest
- steps:
- - name: Checkout code
- uses: actions/checkout@v5
-
- - name: Set up Python ${{ env.PYTHON_VERSION }}
- uses: astral-sh/setup-uv@v6
- with:
- python-version: ${{ env.PYTHON_VERSION }}
- enable-cache: true
-
- - name: Collect code references
- id: collect
- run: |
- uv run - < bool:
- """Whether to skip a file based on its size or content"""
- file_size = file_path.stat().st_size
- if file_size == 0: # Empty files are irrelevant
- return True
- if file_size > 1024 * 1024: # Large files are likely binary
- return True
- with file_path.open("rb") as file:
- chunk = file.read(4096) # A text file rarely contains null bytes
- if b'\0' in chunk:
- return True
- try:
- chunk.decode('utf-8')
- except UnicodeDecodeError: # Decoding likely fails for binary files
- return True
- return False
-
- def find_references(feature_names: list[str]) -> Generator[tuple[str, str, int], None, None]:
- """Search for references to a feature name in the codebase."""
- all_files = Path('.').glob("**/*")
- for path in all_files:
- if any(pattern in str(path).lower() for pattern in EXCLUDE_PATTERNS):
- continue
- if not path.is_file():
- continue
- if should_skip_file(path):
- continue
- context: deque[str] = deque(maxlen=2)
- with path.open("r", encoding="utf-8", errors="ignore") as file:
- for line_number, line in enumerate(file, start=1):
- context.append(line)
- for feature_name in feature_names:
- if feature_name not in line: # Match feature name
- continue
- re_function_calls = rf"""(?i:(?:feature|flag)\w*\(\s*(["']){re.escape(feature_name)})\1"""
- if re.search(re_function_calls, "".join(context)):
- yield feature_name, str(path), line_number
- # TODO: Add more sophisticated matching, e.g. feature names defined as constants
-
- def retrieve_feature_names() -> list[str]:
- """Fetch feature names from the Flagsmith API."""
- response = requests.get( # TODO: Make better use of pagination
- f"${{ env.FLAGSMITH_ADMIN_API_URL }}/api/v1/projects/${{ env.FLAGSMITH_PROJECT_ID }}/features/?page_size=1000",
- headers={"Authorization": f"Api-Key ${{ env.FLAGSMITH_ADMIN_API_KEY }}"},
- )
- response.raise_for_status()
- return [feature["name"] for feature in response.json()["results"]]
-
- # Fetch visible features
- feature_names = retrieve_feature_names()
-
- # Find code references
- code_references = [
- {"feature_name": feature_name, "file_path": file_path, "line_number": line_number}
- for feature_name, file_path, line_number in find_references(feature_names)
- ]
-
- # Output to GHA
- json_references = json.dumps(code_references)
- with open(os.environ["GITHUB_OUTPUT"], "a") as gh_output:
- print(f"code_references={json_references}", file=gh_output)
-
- if not code_references:
- print("No code references found.")
- exit(0)
-
- references_by_feature = defaultdict(list)
- sorted_code_references = sorted(code_references, key=lambda x: (x["feature_name"], x["file_path"], x["line_number"]))
- for reference in sorted_code_references:
- references_by_feature[reference["feature_name"]].append((reference["file_path"], reference["line_number"]))
-
- print("Code References:")
- for feature_name, references in references_by_feature.items():
- print(f"\nFeature: {feature_name}")
- for file_path, line_number in references:
- print(f" - {file_path}:{line_number}")
- EOF
-
- - name: Upload code references
- run: |
- uv run - <Code Reference counts displayed in Features list
+
+:::info
+
+We currently only offer integrating with GitHub. Support to other VCS platforms is coming soon.
+
+:::
+
+---
+
+## Integrate with GitHub
+
+### Simple configuration
+
+For most cases, our [reusable workflow](https://github.com/Flagsmith/ci/blob/main/.github/workflows/collect-code-references.yml) is enough.
+
+Add a new GitHub Actions workflow to **each repository** to integrate with Code References:
+
+```yaml
+# .github/workflows/flagsmith-code-references.yml
+name: Flagsmith Code references
+on:
+ push:
+ branches:
+ - main # Update references on every update to the default branch
+jobs:
+ collect-code-references:
+ name: Collect
+ uses: Flagsmith/ci/.github/workflows/collect-code-references.yml@v1.0.0
+ permissions:
+ contents: read # For scanning feature flag code references
+ with:
+ flagsmith_project_id: ${{ fromJSON(vars.FLAGSMITH_PROJECT_ID) }}
+ flagsmith_admin_api_url: https://api.flagsmith.com # Or your Flagsmith instance URL
+ secrets:
+ flagsmith_admin_api_key: ${{ secrets.FLAGSMITH_CODE_REFERENCES_API_KEY }}
+```
+
+This workflow needs the following added to [_Settings > Secrets and variables > Actions_](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions) in GitHub:
+
+- `FLAGSMITH_PROJECT_ID` (variable): obtain from the Flagsmith dashboard URL, e.g. `/project//...`
+- `FLAGSMITH_CODE_REFERENCES_API_KEY` (secret): obtain from _Organisation Settings > API Keys_ in Flagsmith
+
+### Advanced configuration
+
+If you need a customised workflow, integrating with individual actions is also possible. They can be useful to certain use cases, for example:
+
+- Customising cloning source code for reference scanning
+- Combining multiple repositories into a single workflow (umbrella repositories)
+- Any other customisation of the integration steps
+
+```yaml
+name: Flagsmith Code References
+on:
+ push:
+ branches:
+ - main
+jobs:
+ collect-code-references:
+ name: Collect code references
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read # For scanning feature flag code references
+ env:
+ FLAGSMITH_ADMIN_API_URL: https://api.flagsmith.com # Or your Flagsmith instance URL
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Fetch feature names
+ id: fetch-feature-names
+ uses: Flagsmith/ci/.github/actions/fetch-feature-names@v1.0.0
+ with:
+ flagsmith_project_id: ${{ vars.FLAGSMITH_PROJECT_ID }}
+ flagsmith_admin_api_url: ${{ env.FLAGSMITH_ADMIN_API_URL }}
+ flagsmith_admin_api_key: ${{ secrets.FLAGSMITH_CODE_REFERENCES_API_KEY }}
+
+ - name: Scan code references
+ id: scan-code-references
+ uses: Flagsmith/ci/.github/actions/scan-code-references@v1.0.0
+ with:
+ feature_names: ${{ steps.fetch-feature-names.outputs.feature_names }}
+
+ - name: Upload code references
+ uses: Flagsmith/ci/.github/actions/upload-code-references@v1.0.0
+ with:
+ code_references: ${{ steps.scan-code-references.outputs.code_references }}
+ flagsmith_project_id: ${{ vars.FLAGSMITH_PROJECT_ID }}
+ flagsmith_admin_api_url: ${{ env.FLAGSMITH_ADMIN_API_URL }}
+ flagsmith_admin_api_key: ${{ secrets.FLAGSMITH_CODE_REFERENCES_API_KEY }}
+ repository_url: ${{ github.server_url }}/${{ github.repository }}
+ revision: ${{ github.sha }}
+```
+
+This workflow needs the following added to [_Settings > Secrets and variables > Actions_](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions) in GitHub:
+
+- `FLAGSMITH_PROJECT_ID` (variable): obtain from the Flagsmith dashboard URL, e.g. `/project//...`
+- `FLAGSMITH_CODE_REFERENCES_API_KEY` (secret): obtain from _Organisation Settings > API Keys_ in Flagsmith
+
+---
+
+## How it works
+
+Code References rely on CI scripts isolated to three steps:
+
+1. Fetch feature names from Flagsmith (communicates with Flagsmith API)
+2. Scan code references for the project's features (**does not** communicate with Flagsmith API)
+3. Upload code references to Flagsmith (communicates with Flagsmith API)
+
+:::important
+
+Integrating with Code References **does not** expose your source code to Flagsmith. Our API only collects file paths, and line numbers, of code locations likely containing a feature flag evaluation. Code scanning is performed locally on the CI runner.
+
+:::
+
+Feature details will expand on the _Code References_ tab, and list locations of each code reference:
+
+
+
+## Related
+
+- [Admin API Authentication](/integrating-with-flagsmith/flagsmith-api-overview/admin-api/authentication): Generate API keys for Code References
+- [Flag Lifecycle](/best-practices/flag-lifecycle): Learn when to remove short-lived flags from your code
diff --git a/docs/docs/managing-flags/code-references/feature-details.png b/docs/docs/managing-flags/code-references/feature-details.png
new file mode 100644
index 000000000000..e2c0461c689d
Binary files /dev/null and b/docs/docs/managing-flags/code-references/feature-details.png differ
diff --git a/docs/docs/managing-flags/code-references/feature-list.png b/docs/docs/managing-flags/code-references/feature-list.png
new file mode 100644
index 000000000000..8510e0915255
Binary files /dev/null and b/docs/docs/managing-flags/code-references/feature-list.png differ