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: + +![Code References listed for each repository within Feature details](./code-references/feature-details.png) + +## 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