diff --git a/.gcloudignore b/.gcloudignore new file mode 100644 index 000000000000..d5de8d5ad830 --- /dev/null +++ b/.gcloudignore @@ -0,0 +1,13 @@ +# Cloud Build source upload ignore +# gcloud builds submit uses this to exclude files from the source tarball +.devenv/ +.git/ +.venv/ +.webpack_cache/ +node_modules/ +/fixtures/ +/tests/ +*.pyc +__pycache__/ +.coverage* +.DS_Store diff --git a/.github/workflows/build-kencove.yml b/.github/workflows/build-kencove.yml new file mode 100644 index 000000000000..6da0dd7b93fc --- /dev/null +++ b/.github/workflows/build-kencove.yml @@ -0,0 +1,55 @@ +name: Build Kencove Sentry Image + +on: + push: + branches: + - '*-kencove' + workflow_dispatch: + inputs: + tag: + description: 'Image tag override (default: commit SHA)' + required: false + default: '' + +env: + REGION: us-central1 + PROJECT_ID: kencove-prod + REPOSITORY: kencove-docker-repo + IMAGE_NAME: sentry + +jobs: + build: + runs-on: ubuntu-24.04 + permissions: + contents: read + id-token: write # For Workload Identity Federation + + steps: + - uses: actions/checkout@v4 + + - name: Authenticate to Google Cloud + uses: google-github-actions/auth@v2 + with: + workload_identity_provider: 'projects/103143301688/locations/global/workloadIdentityPools/github-wlif/providers/github-oidc' + service_account: 'github-actions-seer@kencove-prod.iam.gserviceaccount.com' + + - name: Set up Cloud SDK + uses: google-github-actions/setup-gcloud@v2 + + - name: Determine image tag + id: tag + run: | + if [ -n "${{ github.event.inputs.tag }}" ]; then + echo "tag=${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT + else + echo "tag=${{ github.sha }}" >> $GITHUB_OUTPUT + fi + + - name: Submit Cloud Build + run: | + gcloud builds submit \ + --config=cloudbuild.yaml \ + --substitutions=COMMIT_SHA=${{ github.sha }},_TAG=${{ steps.tag.outputs.tag }} \ + --project=${{ env.PROJECT_ID }} \ + --async \ + . diff --git a/.github/workflows/build-push-ghcr.yml b/.github/workflows/build-push-ghcr.yml new file mode 100644 index 000000000000..e2946539a0a2 --- /dev/null +++ b/.github/workflows/build-push-ghcr.yml @@ -0,0 +1,127 @@ +name: Build and Push Sentry to GHCR (Public) + +# Publishes a public ledoent fork of self-hosted Sentry to GitHub Container Registry. +# Companion to build-kencove.yml (kencove-prod GCP Cloud Build, legacy). This workflow exists +# so self-hosted Sentry+Seer deployments can pull without GCP credentials. + +on: + push: + branches: + - '*-ledoent' + - feat/build-push-ghcr + tags: + - 'v*-ledoent' + workflow_dispatch: + inputs: + tag: + description: 'Image tag override (default: branch-shortsha)' + required: false + type: string + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ledoent/sentry + +# Cancel any in-flight build for the same ref when a new push lands. Saves +# ~30 GHA minutes per force-push and prevents two builds from racing to +# publish the same mutable tag. +concurrency: + group: build-push-ghcr-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-and-push: + # ubuntu-latest is 16 GB RAM on GitHub-hosted free runners as of 2026 + # (was 7 GB before the 2024 Larger Hosted Runners refresh). The Sentry + # frontend build uses NODE_OPTIONS=4096 in Dockerfile.ledoent; our first + # build completed in ~12 min on this runner. Bump to ubuntu-latest-large + # (32 GB) only if webpack starts OOMing. + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 # full history so sentry.build can derive the version + + - name: Free disk space + run: | + # Sentry's build is disk-hungry; trim runner-preinstalled toolchains + # we don't use. Each rm is best-effort against a wildcard so a path + # being absent doesn't fail the build — but we keep the *step* exit + # status real so an unrelated failure (e.g., the docker prune) still + # surfaces. + df -h / + for path in /usr/share/dotnet /usr/local/lib/android /opt/ghc \ + /opt/hostedtoolcache/CodeQL; do + [ -e "$path" ] && sudo rm -rf "$path" + done + sudo docker image prune -af + df -h / + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Generate image tags + id: tags + env: + INPUT_TAG: ${{ inputs.tag }} + REF_NAME: ${{ github.ref_name }} + run: | + IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}" + SHA_SHORT=$(git rev-parse --short HEAD) + BRANCH_TAG=$(echo "$REF_NAME" | sed 's|[^a-zA-Z0-9._-]|-|g' | cut -c1-60) + + TAGS="${IMAGE}:${BRANCH_TAG}-${SHA_SHORT}" + TAGS="${TAGS},${IMAGE}:${BRANCH_TAG}" + + if [ -n "$INPUT_TAG" ]; then + TAGS="${TAGS},${IMAGE}:${INPUT_TAG}" + fi + + if [ "${{ github.ref_type }}" = "tag" ]; then + TAGS="${TAGS},${IMAGE}:${REF_NAME}" + fi + + { + echo "tags=${TAGS}" + echo "sha_short=${SHA_SHORT}" + } >> "$GITHUB_OUTPUT" + echo "Generated tags: ${TAGS}" + + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: . + file: self-hosted/Dockerfile.ledoent + platforms: linux/amd64 + push: true + tags: ${{ steps.tags.outputs.tags }} + build-args: | + SOURCE_COMMIT=${{ github.sha }} + labels: | + org.opencontainers.image.source=https://github.com/ledoent/sentry + org.opencontainers.image.revision=${{ github.sha }} + org.opencontainers.image.title=Sentry (ledoent fork) + org.opencontainers.image.description=Self-hosted Sentry with SEER_URL env support + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Summary + run: | + { + echo "### Sentry pushed to GHCR :rocket:" + echo "" + echo "**Tags:**" + echo '${{ steps.tags.outputs.tags }}' | tr ',' '\n' | sed 's/^/- `/' | sed 's/$/`/' + } >> "$GITHUB_STEP_SUMMARY" diff --git a/AGENTS.md b/AGENTS.md index 8f69f58b4eb2..37c1b1407040 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -256,3 +256,53 @@ Frontend (`static/`) and backend (`src/`, `tests/`) are **not atomically deploye - If your changes touch both frontend and backend, split them into **separate PRs**. - Land the backend PR first when the frontend depends on new API changes. - Pure test additions alongside `src/` changes are fine in one PR. + +## Kencove Fork + +This is **ledoent/sentry** (originally kencove/sentry) — a fork of getsentry/sentry with custom modifications for our self-hosted deployment. + +### Key Modifications + +1. **GitLab Autofix Support** (`static/app/components/events/autofix/utils.tsx`) + - Added `'gitlab'` and `'integrations:gitlab'` to `supportedProviders` array + - Enables GitLab repositories for Seer Autofix feature + - Note: upstream 26.5.0 added GitLab support behind `organizations:seer-gitlab-support` feature flag; this patch may be redundant. Verify before keeping in future rebases. + +2. **Seer endpoints serializer** (`src/sentry/seer/endpoints/project_seer_preferences.py`) + - Make `org_id` and `integration_id` optional to handle Seer's repo format differences for self-hosted single-org deployments + +3. **Cursor / GitLab integration** (`src/sentry/integrations/cursor/*`) + - Custom GitLab repo URL builder + webhook handler + +4. **Self-hosted SEER_URL patch** (`self-hosted/Dockerfile.patch`) + - Patches `server.py` during Docker build so `SEER_DEFAULT_URL` defaults to the in-cluster seer service + +### Building Custom Image + +The primary build now ships to GHCR: + +```bash +# Via the GHCR workflow (default — produces ghcr.io/ledoent/sentry:-kencove) +gh workflow run build-push-ghcr.yml --ref -kencove + +# Legacy Cloud Build path (kencove-prod GCP, still works) +gcloud builds submit --config=cloudbuild.yaml . +``` + +### Syncing with Upstream + +```bash +git fetch upstream +git checkout -b -kencove +git log --reverse --format=%H ..origin/-kencove \ + | xargs -I{} git cherry-pick {} +# Resolve conflicts (typically in src/sentry/seer/autofix/coding_agent.py +# and src/sentry/seer/endpoints/project_seer_preferences.py) +git push origin -kencove +``` + +### Related Repositories + +- **Seer AI Service**: [ledoent/seer](https://github.com/ledoent/seer) - GitLab repository client +- **Helm Charts**: [kencove/charts](https://github.com/kencove/charts) - Deployment configuration +- **Infra Clusters**: `~/projects/ledoent/infra/deployments/` - Deployment values diff --git a/build-and-push.sh b/build-and-push.sh new file mode 100755 index 000000000000..ea1341903e13 --- /dev/null +++ b/build-and-push.sh @@ -0,0 +1,75 @@ +#!/bin/bash +# Build and push Kencove Sentry image to Google Artifact Registry +# +# Usage: +# ./build-and-push.sh # Build with commit SHA tag +# ./build-and-push.sh v26.1.0-gitlab # Build with custom tag +# ./build-and-push.sh --local # Build locally without pushing + +set -euo pipefail + +# Configuration +PROJECT_ID="${PROJECT_ID:-kencove-prod}" +REGION="${REGION:-us-central1}" +REPOSITORY="${REPOSITORY:-kencove-docker-repo}" +IMAGE_NAME="${IMAGE_NAME:-sentry}" +TAG="${1:-$(git rev-parse --short HEAD)}" + +FULL_IMAGE="${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY}/${IMAGE_NAME}" + +echo "=== Kencove Sentry Build ===" +echo "Image: ${FULL_IMAGE}:${TAG}" +echo "Commit: $(git rev-parse HEAD)" +echo "" + +if [[ "${1:-}" == "--local" ]]; then + echo "Building locally (not pushing)..." + docker build \ + -t "${IMAGE_NAME}:${TAG}" \ + -t "${IMAGE_NAME}:latest" \ + -f self-hosted/Dockerfile.kencove \ + --build-arg SOURCE_COMMIT="$(git rev-parse HEAD)" \ + --progress=plain \ + . + echo "" + echo "Local build complete: ${IMAGE_NAME}:${TAG}" + exit 0 +fi + +# Check if using Cloud Build or local Docker +if command -v gcloud &> /dev/null && [[ "${USE_CLOUD_BUILD:-true}" == "true" ]]; then + echo "Using Cloud Build..." + gcloud builds submit \ + --config=cloudbuild.yaml \ + --substitutions="_TAG=${TAG}" \ + --project="${PROJECT_ID}" \ + . +else + echo "Using local Docker build + push..." + + # Configure Docker for GAR + gcloud auth configure-docker "${REGION}-docker.pkg.dev" --quiet + + # Build + docker build \ + -t "${FULL_IMAGE}:${TAG}" \ + -t "${FULL_IMAGE}:latest" \ + -f self-hosted/Dockerfile.kencove \ + --build-arg SOURCE_COMMIT="$(git rev-parse HEAD)" \ + --progress=plain \ + . + + # Push + docker push "${FULL_IMAGE}:${TAG}" + docker push "${FULL_IMAGE}:latest" +fi + +echo "" +echo "=== Build Complete ===" +echo "Image: ${FULL_IMAGE}:${TAG}" +echo "" +echo "To use in Helm values:" +echo " images:" +echo " sentry:" +echo " repository: ${FULL_IMAGE}" +echo " tag: \"${TAG}\"" diff --git a/cloudbuild.yaml b/cloudbuild.yaml new file mode 100644 index 000000000000..2b177ead905d --- /dev/null +++ b/cloudbuild.yaml @@ -0,0 +1,57 @@ +# Cloud Build configuration for kencove/sentry +# Builds custom Sentry image with GitLab Autofix support +# +# Usage: +# gcloud builds submit --config=cloudbuild.yaml . +# +# Or with custom tag: +# gcloud builds submit --config=cloudbuild.yaml --substitutions=_TAG=v26.1.0-gitlab . +# +# Trigger on push: +# gcloud builds triggers create github \ +# --repo-name=sentry --repo-owner=kencove \ +# --branch-pattern="^master$" \ +# --build-config=cloudbuild.yaml + +substitutions: + _REGION: us-central1 + _REPOSITORY: kencove-docker-repo + _IMAGE_NAME: sentry + _TAG: ${COMMIT_SHA} + +options: + machineType: E2_HIGHCPU_32 + diskSizeGb: 200 + logging: CLOUD_LOGGING_ONLY + +steps: + # Build using multi-stage Dockerfile that handles both frontend and runtime + - name: 'gcr.io/cloud-builders/docker' + id: 'build-image' + args: + - 'build' + - '-t' + - '${_REGION}-docker.pkg.dev/$PROJECT_ID/${_REPOSITORY}/${_IMAGE_NAME}:${_TAG}' + - '-t' + - '${_REGION}-docker.pkg.dev/$PROJECT_ID/${_REPOSITORY}/${_IMAGE_NAME}:latest' + - '-f' + - 'self-hosted/Dockerfile.kencove' + - '--build-arg' + - 'SOURCE_COMMIT=${COMMIT_SHA}' + - '--progress=plain' + - '.' + + # Push to Artifact Registry + - name: 'gcr.io/cloud-builders/docker' + id: 'push-image' + args: + - 'push' + - '--all-tags' + - '${_REGION}-docker.pkg.dev/$PROJECT_ID/${_REPOSITORY}/${_IMAGE_NAME}' + waitFor: ['build-image'] + +images: + - '${_REGION}-docker.pkg.dev/$PROJECT_ID/${_REPOSITORY}/${_IMAGE_NAME}:${_TAG}' + - '${_REGION}-docker.pkg.dev/$PROJECT_ID/${_REPOSITORY}/${_IMAGE_NAME}:latest' + +timeout: 3600s # 1 hour - Sentry build is slow diff --git a/self-hosted/Dockerfile.ledoent b/self-hosted/Dockerfile.ledoent new file mode 100644 index 000000000000..57d48d885b9b --- /dev/null +++ b/self-hosted/Dockerfile.ledoent @@ -0,0 +1,70 @@ +# Ledoent/Kencove self-hosted Sentry image. +# +# Strategy (changed in the 26.5.0 rebase): use the upstream +# `getsentry/sentry:26.5.0` image as a base and overlay our small set +# of backend patches + the self-hosted SEER_URL fix. Previously we +# rebuilt frontend + backend from source, but upstream's frontend +# build pipeline (rspack + type-loader.ts) is more involved than we +# want to maintain across version bumps. The pre-built upstream image +# is the same one Sentry's own CI publishes, so we inherit upstream's +# frontend bundle for free. +# +# Our patches in this image: +# 1. `src/sentry/seer/endpoints/project_seer_preferences.py` — make +# organization_id and integration_id optional + add a GitLab +# external_id fallback in the POST endpoint (self-hosted + +# GitLab repos sometimes don't have those fields) +# 2. `src/sentry/conf/server.py` — patch `SEER_DEFAULT_URL` to read +# from `$SEER_URL` env, defaulting to `http://seer:9091` (the +# in-cluster service name) +# 3. `self-hosted/sentry.conf.py` — disable the `seer-explorer` +# feature flag for self-hosted +# +# The legacy kencove-fork frontend patch (adding GitLab to +# `supportedProviders`) is obsolete in 26.5.0 — upstream's +# BASE_SUPPORTED_PROVIDERS already includes `gitlab` and +# `integrations:gitlab`, with a feature-flag-gated provider +# mechanism (`seer-gitlab-support`) layered on top. + +ARG UPSTREAM_TAG=26.5.0 +FROM ghcr.io/getsentry/sentry:${UPSTREAM_TAG} + +USER root + +# 1. Overlay our patched serializer. +COPY src/sentry/seer/endpoints/project_seer_preferences.py \ + /usr/src/sentry/src/sentry/seer/endpoints/project_seer_preferences.py + +# 2. Patch SEER_URL in conf/server.py. Upstream defaults to +# 127.0.0.1:9091 (single-host setup); we run seer as a separate +# container so we need the docker network DNS name. Allow override +# via $SEER_URL env at runtime so non-cluster deploys can point +# elsewhere without rebuilding the image. +RUN sed -i \ + 's#SEER_DEFAULT_URL = "http://127.0.0.1:9091"#SEER_DEFAULT_URL = os.environ.get("SEER_URL", "http://seer:9091")#' \ + /usr/src/sentry/src/sentry/conf/server.py \ + && grep -q 'SEER_DEFAULT_URL = os.environ.get' \ + /usr/src/sentry/src/sentry/conf/server.py \ + && echo "SEER_URL patch applied" + +# 3. Overlay our self-hosted Sentry config (turns off seer-explorer +# feature flag among other tweaks). config.yml stays as upstream's +# default since /opt/sentry/sentry/config.yml mounts over it at +# runtime on the VM. +COPY self-hosted/sentry.conf.py /etc/sentry/sentry.conf.py + +# 4. Recompile the touched .py files so the runtime doesn't pay the +# JIT compile cost on first request. +RUN python3 -m compileall -q \ + /usr/src/sentry/src/sentry/seer/endpoints/project_seer_preferences.py \ + /usr/src/sentry/src/sentry/conf/server.py \ + /etc/sentry/sentry.conf.py + +USER sentry + +ARG SOURCE_COMMIT +ENV SENTRY_BUILD=${SOURCE_COMMIT:-unknown} +LABEL org.opencontainers.image.revision=$SOURCE_COMMIT +LABEL org.opencontainers.image.source="https://github.com/ledoent/sentry/tree/${SOURCE_COMMIT:-26.5.0-ledoent}/" +LABEL org.opencontainers.image.title="Sentry (ledoent fork)" +LABEL org.opencontainers.image.description="getsentry/sentry:26.5.0 + ledoent backend patches" diff --git a/self-hosted/Dockerfile.patch b/self-hosted/Dockerfile.patch new file mode 100644 index 000000000000..f316ffea43da --- /dev/null +++ b/self-hosted/Dockerfile.patch @@ -0,0 +1,23 @@ +# Dockerfile that patches the official Sentry image with GitLab Autofix support +# Much faster than rebuilding from source (~2 minutes vs ~45 minutes) +# +# Build: docker build -f self-hosted/Dockerfile.patch -t sentry:kencove . + +ARG SENTRY_VERSION=26.1.0 +FROM ghcr.io/getsentry/sentry:${SENTRY_VERSION} + +LABEL maintainer="kencove" +LABEL org.opencontainers.image.title="Sentry (Kencove)" +LABEL org.opencontainers.image.description="Kencove fork with GitLab Autofix support" + +# Copy patched autofix utils with GitLab support +# The change adds 'gitlab' and 'integrations:gitlab' to supportedProviders +COPY static/app/components/events/autofix/utils.tsx \ + /usr/src/sentry/src/sentry/static/sentry/dist/app/components/events/autofix/utils.tsx + +# Note: The above path may need adjustment - the built JS is in dist/ +# Let's find the actual location and patch the compiled JS instead + +ARG SOURCE_COMMIT +ENV SENTRY_BUILD=${SOURCE_COMMIT:-patched} +LABEL org.opencontainers.image.revision=$SOURCE_COMMIT diff --git a/self-hosted/sentry.conf.py b/self-hosted/sentry.conf.py index 3e5b4c18aa31..14fc6aceb9cd 100644 --- a/self-hosted/sentry.conf.py +++ b/self-hosted/sentry.conf.py @@ -6,6 +6,23 @@ import os import os.path +############################################## +# CRITICAL: Patch Seer URLs before any imports +# This must happen BEFORE `from sentry.conf.server import *` +# to ensure all code sees the correct Seer service URLs +############################################## +import sentry.conf.server as _sentry_server +_sentry_server.SEER_DEFAULT_URL = os.environ.get("SEER_URL", "http://seer:9091") +_sentry_server.SEER_AUTOFIX_URL = _sentry_server.SEER_DEFAULT_URL +_sentry_server.SEER_SIMILARITY_URL = _sentry_server.SEER_DEFAULT_URL +_sentry_server.SEER_ANOMALY_DETECTION_URL = _sentry_server.SEER_DEFAULT_URL +_sentry_server.SEER_SEVERITY_URL = _sentry_server.SEER_DEFAULT_URL +_sentry_server.SEER_BREAKPOINT_DETECTION_URL = _sentry_server.SEER_DEFAULT_URL +_sentry_server.SEER_GROUPING_URL = _sentry_server.SEER_DEFAULT_URL +_sentry_server.SEER_SUMMARIZATION_URL = _sentry_server.SEER_DEFAULT_URL +_sentry_server.SEER_RPC_SHARED_SECRET = os.environ.get("SEER_RPC_SHARED_SECRET", "").split(",") if os.environ.get("SEER_RPC_SHARED_SECRET") else None +############################################## + # For Docker, the following environment variables are supported: # SENTRY_POSTGRES_HOST # SENTRY_POSTGRES_PORT @@ -234,3 +251,10 @@ SENTRY_OPTIONS["system.secret-key"] = secret_key SENTRY_USE_RELAY = True + +############################################## +# Feature Flags for Self-Hosted +############################################## +# Disable Explorer-based autofix (not available in self-hosted Seer) +# This forces Sentry to use the legacy /v1/automation/autofix/start endpoint +SENTRY_FEATURES["organizations:seer-explorer"] = False diff --git a/src/sentry/seer/endpoints/project_seer_preferences.py b/src/sentry/seer/endpoints/project_seer_preferences.py index 3ffa7d6bf3e0..e23e7666824c 100644 --- a/src/sentry/seer/endpoints/project_seer_preferences.py +++ b/src/sentry/seer/endpoints/project_seer_preferences.py @@ -1,5 +1,7 @@ from __future__ import annotations +import logging + from rest_framework import serializers from rest_framework.request import Request from rest_framework.response import Response @@ -9,7 +11,9 @@ from sentry.api.base import cell_silo_endpoint from sentry.api.bases.project import ProjectEndpoint, ProjectEventPermission from sentry.api.serializers.rest_framework import CamelSnakeSerializer +from sentry.constants import ObjectStatus from sentry.models.project import Project +from sentry.models.repository import Repository from sentry.ratelimits.config import RateLimitConfig from sentry.seer.autofix.utils import ( deduplicate_repositories, @@ -24,10 +28,15 @@ from sentry.seer.utils import filter_repo_by_provider from sentry.types.ratelimit import RateLimit, RateLimitCategory +logger = logging.getLogger(__name__) + class RepositorySerializer(BaseRepositorySerializer): - organization_id = serializers.IntegerField(required=True) - integration_id = serializers.CharField(required=True) + # ledoent/kencove fork: make org_id + integration_id optional. Self-hosted + # single-org deployments and GitLab repos don't always carry these on the + # request; we fill them in from the matched DB row in the endpoint below. + organization_id = serializers.IntegerField(required=False, allow_null=True) + integration_id = serializers.CharField(required=False, allow_null=True, allow_blank=True) class SeerAutomationHandoffConfigurationSerializer(CamelSnakeSerializer): @@ -108,13 +117,29 @@ def post(self, request: Request, project: Project) -> Response: repo_data["organization_id"] = project.organization.id - repo = filter_repo_by_provider( + # Try matching by provider + external_id + owner/name first. + repo_qs = filter_repo_by_provider( project.organization.id, provider, external_id, owner, name - ).first() + ) + repo = repo_qs.first() if repo is None: - return Response({"detail": "Invalid repository"}, status=400) + # ledoent/kencove fork: fallback matches by external_id only. + # Handles GitLab repos where Seer stores a simplified + # owner/name that differs from the DB format. + repo = Repository.objects.filter( + organization_id=project.organization.id, + external_id=external_id, + status=ObjectStatus.ACTIVE, + ).first() + if repo is None: + return Response({"detail": "Invalid repository"}, status=400) repo_data["repository_id"] = repo.id + # Fill in integration_id from the matched repo if the request + # didn't include one (also kencove fork — see optional fields above). + if not repo_data.get("integration_id") and repo.integration_id: + repo_data["integration_id"] = str(repo.integration_id) + preference = SeerProjectPreference.validate( { **serializer.validated_data, diff --git a/static/app/components/events/autofix/utils.tsx b/static/app/components/events/autofix/utils.tsx index 919f0fdc78a0..1323dc54f413 100644 --- a/static/app/components/events/autofix/utils.tsx +++ b/static/app/components/events/autofix/utils.tsx @@ -192,6 +192,8 @@ const BASE_SUPPORTED_PROVIDERS = [ 'integrations:github', 'github_enterprise', 'integrations:github_enterprise', + 'gitlab', + 'integrations:gitlab', ]; /**