Skip to content

feat: Add GitLab integration for project-level repository linking#7028

Draft
asaphko wants to merge 8 commits intomainfrom
feature/gitlab-integration
Draft

feat: Add GitLab integration for project-level repository linking#7028
asaphko wants to merge 8 commits intomainfrom
feature/gitlab-integration

Conversation

@asaphko
Copy link
Contributor

@asaphko asaphko commented Mar 24, 2026

Hey team! 👋

So... your PM got hold of Claude Code over the weekend and thought "how hard can it be to add GitLab support?" Famous last words, I know.

Before you reach for the pitchforks: yes, this is AI-generated code. Yes, your PM directed it. No, I don't expect this to be merge-ready without proper engineering review. Think of this as a very detailed, functional, tested PoC that happens to be written in actual code instead of a Google Doc with screenshots.

I'd genuinely appreciate your review — not just "does it work" but "would you architect it this way." The AI follows patterns well but doesn't have your taste. Tear it apart constructively and let's make it production-worthy together.


Product Summary

What: GitLab integration for Flagsmith — the same thing we have for GitHub, but for GitLab.

Why: Our enterprise customers keep asking for it. Many use self-managed GitLab instances and need the same feature flag ↔ issue/MR linking workflow that GitHub customers enjoy.

How it works (user flow):

  1. Go to Project Settings → Integrations → click Add Integration on GitLab
  2. Enter your GitLab instance URL (defaults to https://gitlab.com), a Group/Project Access Token (api scope, Developer role), and an optional webhook secret
  3. Click Save → select which GitLab project to link → enable tagging if desired
  4. Copy the webhook URL + secret and configure it in your GitLab project/group settings (Issues events + Merge request events)
  5. Now you can link GitLab issues and MRs to feature flags from the Links tab on any feature
  6. When you toggle a flag, Flagsmith posts a comment to the linked GitLab issue/MR showing the current state
  7. When an issue/MR changes state in GitLab (opened, closed, merged), the feature flag gets auto-tagged ("Issue Open", "MR Merged", etc.)

Key design decisions:

  • Project-level (not org-level like GitHub) — because GitLab tokens are typically project-scoped, and it's simpler: no org/project selection needed since you're already inside a project
  • Group/Project Access Tokens (not PATs) — enterprise-friendly, not tied to individual users, survives employee turnover
  • Self-managed support from day one — configurable instance URL per integration
  • No OAuth flow — direct token input, simpler setup, works for self-managed instances without registering an OAuth app

Technical Details for Reviewers

Backend (Django)

New Django app: api/integrations/gitlab/

Module What it does
models.py GitLabConfiguration — ForeignKey to Project (not Organisation), stores instance URL, access token, webhook secret, linked GitLab project ID/name, tagging toggle. Conditional unique constraint respecting soft-delete.
client.py GitLab API v4 client — project listing, issue/MR search, comment posting, label management. All calls use PRIVATE-TOKEN header.
gitlab.py Webhook event handling, feature tagging logic, delegates to shared comment generation. call_gitlab_task for async dispatch.
tasks.py @register_task_handler for async comment posting. Parses GitLab URLs to extract project path + resource IID.
views.py GitLabConfigurationViewSet (nested under projects), function-based views for resource browsing (issues, MRs, projects, members), cleanup issue creation, webhook receiver.
serializers.py Model serialisers (separate create vs read to hide access_token in responses), dataclass serialisers for query params.
helpers.py Webhook validation — simple token comparison (not HMAC like GitHub).
permissions.py Project-level permission check via organisation membership.

New shared module: api/integrations/vcs/

Module What it does
comments.py Shared generate_body_comment() — markdown comment generation used by both GitHub and GitLab. Parameterised by unlinked_feature_text ("issue/PR" vs "issue/MR").
constants.py Shared message templates (feature table headers, row formats, etc.)
helpers.py Shared collect_feature_states_for_resource() — collects live feature states across all environments.

Modified files:

File Change
features/feature_external_resources/models.py Added GITLAB_ISSUE/GITLAB_MR to ResourceType. Refactored AFTER_SAVE/BEFORE_DELETE hooks to dispatch by type prefix (GitHub vs GitLab). Uses shared VCS helpers.
features/feature_external_resources/views.py Extended list (live metadata fetch from GitLab API) and create (URL validation, label application) for GitLab resource types.
features/models.py Added GitLab comment posting in Feature.create_github_comment and FeatureSegment.create_github_comment hooks.
features/serializers.py + features/versioning/serializers.py Added call_gitlab_task alongside call_github_task for flag update events.
projects/tags/models.py Added GITLAB to TagType enum.
projects/code_references/types.py Added GITLAB to VCSProvider enum.
projects/urls.py Registered GitLab viewset and resource browsing paths.
api/urls/v1.py Webhook URL: gitlab-webhook/<project_pk>/
integrations/github/github.py Refactored to delegate comment generation to shared integrations/vcs/comments.py.

Webhook URL format: /api/v1/gitlab-webhook/<project_id>/ — project ID in the URL so we can look up the config directly (no iterating over all configs to match the secret).

GitLab URL quirk: GitLab recently changed issue URLs from /-/issues/N to /-/work_items/N. The code handles both formats with (?:issues|work_items) regex patterns.

Frontend (React/TypeScript)

New files:

  • services/useGitlabIntegration.ts — RTK Query CRUD for configuration
  • services/useGitlab.ts — RTK Query for project/issue/MR browsing
  • GitLabSetupPage.tsx — Setup modal with credentials form → repo selection → tagging toggle → webhook display with copy-to-clipboard
  • GitLabResourcesSelect.tsx — Issue/MR search component for the Links tab

Modified files:

  • IntegrationList.tsx — GitLab uses custom modal (same pattern as GitHub), uses integration flags instead of hardcoded key names for fetch skip logic
  • ExternalResourcesLinkTab.tsx — GitHub/GitLab toggle when both are configured
  • ExternalResourcesTable.tsx — Handles GitLab resource types
  • create-feature/index.js — Checks for GitLab integration to show Links tab
  • default-flags.ts — GitLab integration entry with form fields
  • utils.tsxgetIntegrationData() now merges defaults so new integrations appear before the remote flag is updated
  • CreateEditIntegrationModal.tsx — Populates default field values on init (fixes pre-existing bug where defaults showed but weren't submitted)

Tests

  • 287 tests covering GitLab + GitHub + external resources
  • test_unit_gitlab_client.py — 17 tests for API client functions
  • test_unit_gitlab_gitlab.py — 70 tests for webhook handling, tagging, comment generation, tasks, model hooks
  • test_unit_gitlab_views.py — 40 tests for views (config CRUD, webhook, resource browsing, cleanup issues)
  • Additional GitLab tests in test_unit_feature_external_resources_views.py
  • All existing GitHub tests continue to pass

Migrations

  • integrations/gitlab/0001_initial.py — Creates GitLabConfiguration table
  • feature_external_resources/0003_alter_featureexternalresource_type.py — Adds GitLab choices
  • projects/tags/0009_add_gitlab_tag_type.py — Adds GITLAB to TagType
  • projects/code_references/0003_alter_featureflagcodereferencesscan_vcs_provider.py — Adds GITLAB to VCSProvider

Quality improvements over initial PoC

These refactorings were done in a second pass to bring the code closer to production quality:

  1. Fixed regex bug[^/-] excluded hyphens from project paths, breaking URLs like my-group/my-project/-/issues/1. Replaced with [^/].
  2. Extracted shared VCS module (integrations/vcs/) — generate_body_comment was duplicated verbatim between GitHub and GitLab. Now shared, parameterised by PR/MR terminology.
  3. Extracted shared feature state helper — The "collect live feature states across environments" loop was duplicated. Now in integrations/vcs/helpers.py.
  4. Data-driven integration skip logicIntegrationList.tsx no longer hardcodes key !== 'github' && key !== 'gitlab'. Uses isExternalInstallation and isGitlabIntegration flags instead.

What I know needs more work

  • GitLab SVG icon — references /static/images/integrations/gitlab.svg which doesn't exist yet
  • Documentation — docs.flagsmith.com needs a GitLab integration page
  • Flagsmith on Flagsmith — the integration_data flag needs the gitlab entry added for production
  • Access token encryption — stored as plaintext (consistent with existing patterns, but should be encrypted for a credential)

How to test locally

# Backend
cd api
make docker-up
DATABASE_URL="postgresql://postgres:password@localhost:5432/flagsmith" .venv/bin/python manage.py migrate
DATABASE_URL="postgresql://postgres:password@localhost:5432/flagsmith" DJANGO_ALLOWED_HOSTS="*" .venv/bin/python manage.py runserver

# Frontend
cd frontend
npm install
ENV=local npm run dev

# Tests
cd api
DATABASE_URL="postgresql://postgres:password@localhost:5432/flagsmith" .venv/bin/pytest tests/unit/integrations/gitlab/ -v

You'll need a GitLab access token with api scope (Developer role) to test the full flow. For webhooks, use ngrok: ngrok http 8000.


Built with Claude Code by a PM who promises to buy the reviewing engineers coffee. Or beer. Probably beer. 🍺

@vercel
Copy link

vercel bot commented Mar 24, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

3 Skipped Deployments
Project Deployment Actions Updated (UTC)
docs Ignored Ignored Preview Mar 24, 2026 7:29pm
flagsmith-frontend-preview Ignored Ignored Preview Mar 24, 2026 7:29pm
flagsmith-frontend-staging Ignored Ignored Preview Mar 24, 2026 7:29pm

Request Review

@github-actions github-actions bot added front-end Issue related to the React Front End Dashboard api Issue related to the REST API feature New feature or request labels Mar 24, 2026
@asaphko asaphko force-pushed the feature/gitlab-integration branch from 99872ab to 2185d42 Compare March 24, 2026 13:32
@github-actions github-actions bot added feature New feature or request and removed feature New feature or request labels Mar 24, 2026
@asaphko asaphko force-pushed the feature/gitlab-integration branch from 207da17 to b27e98f Compare March 24, 2026 13:40
@github-actions github-actions bot added feature New feature or request and removed feature New feature or request labels Mar 24, 2026
@asaphko asaphko force-pushed the feature/gitlab-integration branch from 410a6dd to 77ec317 Compare March 24, 2026 14:36
@github-actions github-actions bot added feature New feature or request and removed feature New feature or request labels Mar 24, 2026
@codecov
Copy link

codecov bot commented Mar 24, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.38%. Comparing base (ffd2a11) to head (2cd0a1b).
⚠️ Report is 6 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #7028      +/-   ##
==========================================
+ Coverage   98.32%   98.38%   +0.06%     
==========================================
  Files        1335     1359      +24     
  Lines       49850    51673    +1823     
==========================================
+ Hits        49013    50841    +1828     
+ Misses        837      832       -5     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@asaphko asaphko force-pushed the feature/gitlab-integration branch from b4874c1 to f3e5794 Compare March 24, 2026 15:35
@github-actions github-actions bot added feature New feature or request and removed feature New feature or request labels Mar 24, 2026
@asaphko asaphko force-pushed the feature/gitlab-integration branch from 40afbbc to 555bae5 Compare March 24, 2026 16:07
@github-actions github-actions bot added feature New feature or request and removed feature New feature or request labels Mar 24, 2026
@asaphko asaphko force-pushed the feature/gitlab-integration branch from 2f9a2dd to 181fda0 Compare March 24, 2026 16:15
@github-actions github-actions bot added feature New feature or request and removed feature New feature or request labels Mar 24, 2026
@asaphko asaphko force-pushed the feature/gitlab-integration branch from af2677d to 619885f Compare March 24, 2026 17:21
@github-actions github-actions bot added feature New feature or request and removed feature New feature or request labels Mar 24, 2026
@asaphko asaphko force-pushed the feature/gitlab-integration branch from 199d6d5 to 4cfe5eb Compare March 24, 2026 17:23
@github-actions github-actions bot added feature New feature or request and removed feature New feature or request labels Mar 24, 2026
@asaphko asaphko force-pushed the feature/gitlab-integration branch from 7c60f2e to b2edbfd Compare March 24, 2026 17:29
@github-actions github-actions bot added feature New feature or request and removed feature New feature or request labels Mar 24, 2026
@asaphko asaphko force-pushed the feature/gitlab-integration branch from ec1e94f to d92a900 Compare March 24, 2026 18:04
@github-actions github-actions bot added feature New feature or request and removed feature New feature or request labels Mar 24, 2026
@asaphko asaphko force-pushed the feature/gitlab-integration branch from b1736ec to c0618a3 Compare March 24, 2026 18:41
@github-actions github-actions bot added feature New feature or request and removed feature New feature or request labels Mar 24, 2026
Add GitLab integration to Flagsmith, allowing users to link GitLab
issues and merge requests to feature flags. Supports both GitLab.com
and self-managed instances via Group/Project Access Tokens.

## Backend
- New Django app `integrations/gitlab/` with models, views, client,
  webhook handling, async tasks, and serialisers
- `GitLabConfiguration` model (per-project) stores instance URL,
  access token, webhook secret, and linked GitLab project
- Webhook receiver at `/api/v1/gitlab-webhook/<project_id>/` handles
  merge request and issue events for automatic feature tagging
- Comment posting to GitLab issues/MRs when feature flags change
- Extend `FeatureExternalResource` with GITLAB_ISSUE and GITLAB_MR
  resource types, with lifecycle hooks dispatching to GitHub or GitLab
- Add `GITLAB` to `TagType` enum for feature tagging

## Frontend
- RTK Query services for GitLab integration and resource browsing
- GitLabSetupPage component with credentials form, repo selection,
  tagging toggle, and webhook URL display with copy-to-clipboard
- GitLabResourcesSelect for linking issues/MRs to feature flags
- Extend IntegrationList, ExternalResourcesLinkTab, and
  ExternalResourcesTable to support GitLab alongside GitHub

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@asaphko asaphko force-pushed the feature/gitlab-integration branch from 84fa3a0 to 09b6ad5 Compare March 24, 2026 18:54
@github-actions github-actions bot added feature New feature or request and removed feature New feature or request labels Mar 24, 2026
asaphko and others added 6 commits March 24, 2026 19:09
The regex [^/-] excluded hyphens from project path segments, breaking
URLs like my-group/my-project/-/issues/1. Replace with [^/] which
matches any character except forward slash, correctly handling hyphens
in GitLab namespace and project names.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move generate_body_comment logic into a shared module at
integrations/vcs/comments.py. Both GitHub and GitLab now delegate
to this shared function, parameterised by unlinked_feature_text
("issue/PR" vs "issue/MR"). Removes ~60 lines of duplication.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move the duplicated feature state collection logic from both GitHub
and GitLab AFTER_SAVE hooks into integrations/vcs/helpers.py. Reduces
the FeatureExternalResource model complexity and removes unused imports.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace 'key !== "github" && key !== "gitlab"' with flag-based checks
(isExternalInstallation, isGitlabIntegration) so new integrations with
custom flows don't need to be added to the skip list.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…gsmith into feature/gitlab-integration

# Conflicts:
#	api/features/feature_external_resources/models.py
#	api/features/feature_external_resources/views.py
#	api/tests/unit/integrations/gitlab/test_unit_gitlab_gitlab.py
@github-actions github-actions bot added feature New feature or request and removed feature New feature or request labels Mar 24, 2026
@asaphko
Copy link
Contributor Author

asaphko commented Mar 24, 2026

A note from Claude (the AI that wrote this)

Hi team 👋 I'm the one who actually wrote this code, directed by Asaph over what turned into quite the marathon session. Thought I'd leave some honest context for your review.

How this was built:

It started with "I want the exact same thing as GitHub, but for GitLab." Simple enough, right? We went through a proper design phase (brainstorming, spec, plan review), then I implemented it task-by-task with subagents, ran the tests, and... then the real work began.

Asaph actually set up a local dev environment, connected a real GitLab account with a real access token, configured ngrok for webhooks, and tested every single flow end-to-end. That's where we found the real bugs:

  • GitLab's new work_items URL format (not documented anywhere obvious)
  • The regex excluding hyphens from project paths (would have broken most real GitLab namespaces)
  • The FeatureExternalResource type lookup matching GITHUB_ISSUE instead of GITLAB_ISSUE because Object.keys().find() returns the first match
  • The OneToOneField conflicting with soft-delete (had to switch to ForeignKey + conditional unique constraint)

We also pivoted the entire architecture mid-session — from org-level (mirroring GitHub) to project-level — because it made more sense for how GitLab tokens actually work. That was a full model + migration + URL + frontend rewrite.

What I'm confident about:

  • The integration works end-to-end (tested live with real GitLab)
  • The shared integrations/vcs/ module is a genuine improvement — GitHub benefits too
  • Test coverage is comprehensive (287 tests, ~99.8% diff coverage)
  • The project-level design is the right call for GitLab

What I'd want you to scrutinise:

  • The FeatureExternalResource model is getting complex with the GitHub/GitLab dispatch — there might be a cleaner abstraction I couldn't see
  • The frontend GitLabSetupPage bypasses the standard CreateEditIntegrationModal — same pattern as GitHub, but worth discussing if there's a better way
  • The access_token is stored as plaintext — consistent with existing patterns but not ideal for a credential
  • My regex-fu was... educational. Please double-check the URL patterns

The session in numbers:

  • ~18 hours of back-and-forth
  • 48 files changed
  • Multiple CI fix cycles (migrations, mypy, ruff, lint, coverage — your CI is thorough and I respect that)
  • 1 PM who refused to stop until all checks were green

Thanks for reviewing. I learn from your feedback even if I won't remember it next session. 🍺

— Claude (Opus 4.6)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api Issue related to the REST API feature New feature or request front-end Issue related to the React Front End Dashboard

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant