From e04386641c397fee411827f3d0e838701ffec06c Mon Sep 17 00:00:00 2001 From: Christinarlong Date: Mon, 19 May 2025 15:02:46 -0700 Subject: [PATCH 01/12] add gh mo to permanent ff list --- src/sentry/features/permanent.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sentry/features/permanent.py b/src/sentry/features/permanent.py index 6e351a9223afba..8054f6dfaf631e 100644 --- a/src/sentry/features/permanent.py +++ b/src/sentry/features/permanent.py @@ -124,6 +124,8 @@ def register_permanent_features(manager: FeatureManager): "organizations:seer-based-priority": False, # Enable Vercel integration - there is a custom handler in getsentry "organizations:integrations-vercel": True, + # Enable GitHub multi-org for users to connect many Sentry orgs to a single GitHub org. + "organizations:github-multi-org": True, } permanent_project_features = { From 2ba5d3e744a9e2e057b8741933cb013d05d8bed0 Mon Sep 17 00:00:00 2001 From: Christinarlong Date: Tue, 20 May 2025 14:16:53 -0700 Subject: [PATCH 02/12] switch flag to integrations-multi-org --- src/sentry/features/permanent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sentry/features/permanent.py b/src/sentry/features/permanent.py index 8054f6dfaf631e..9a5531165ffedb 100644 --- a/src/sentry/features/permanent.py +++ b/src/sentry/features/permanent.py @@ -125,7 +125,7 @@ def register_permanent_features(manager: FeatureManager): # Enable Vercel integration - there is a custom handler in getsentry "organizations:integrations-vercel": True, # Enable GitHub multi-org for users to connect many Sentry orgs to a single GitHub org. - "organizations:github-multi-org": True, + "organizations:integrations-multi-org": True, } permanent_project_features = { From 4e2d4cf639b48b60fe3b62954078277996994684 Mon Sep 17 00:00:00 2001 From: Christinarlong Date: Tue, 20 May 2025 14:48:30 -0700 Subject: [PATCH 03/12] add ff check for business plans --- src/sentry/integrations/github/integration.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/sentry/integrations/github/integration.py b/src/sentry/integrations/github/integration.py index 8f63af841aeef1..57b31ee6123286 100644 --- a/src/sentry/integrations/github/integration.py +++ b/src/sentry/integrations/github/integration.py @@ -886,6 +886,15 @@ def _get_eligible_multi_org_installations( class GithubOrganizationSelection(PipelineView): def dispatch(self, request: HttpRequest, pipeline: Pipeline) -> HttpResponseBase: self.active_user_organization = determine_active_organization(request) + has_business_plan = ( + features.has( + "organizations:integrations-scm-multi-org", + organization=self.active_user_organization.organization, + actor=request.user, + ) + if self.active_user_organization is not None + else False + ) if self.active_user_organization is None or not features.has( "organizations:github-multi-org", @@ -911,7 +920,7 @@ def dispatch(self, request: HttpRequest, pipeline: Pipeline) -> HttpResponseBase ) if chosen_installation_id := request.GET.get("chosen_installation_id"): - if chosen_installation_id == "-1": + if chosen_installation_id == "-1" or not has_business_plan: return pipeline.next_step() # Verify that the given GH installation belongs to the person installing the pipeline @@ -935,7 +944,10 @@ def dispatch(self, request: HttpRequest, pipeline: Pipeline) -> HttpResponseBase return self.render_react_view( request=request, pipeline_name="githubInstallationSelect", - props={"installation_info": installation_info}, + props={ + "installation_info": installation_info, + "has_business_plan": has_business_plan, + }, ) From 5acabb2d5cd85e9f4f797787a5d7a9b22a336fab Mon Sep 17 00:00:00 2001 From: Christinarlong Date: Wed, 21 May 2025 09:17:31 -0700 Subject: [PATCH 04/12] add permanent ff and feature check in be --- src/sentry/features/permanent.py | 2 +- .../integrations/github/test_integration.py | 59 ++++++++++++++++++- 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/src/sentry/features/permanent.py b/src/sentry/features/permanent.py index 9a5531165ffedb..6313a9dbd0b524 100644 --- a/src/sentry/features/permanent.py +++ b/src/sentry/features/permanent.py @@ -125,7 +125,7 @@ def register_permanent_features(manager: FeatureManager): # Enable Vercel integration - there is a custom handler in getsentry "organizations:integrations-vercel": True, # Enable GitHub multi-org for users to connect many Sentry orgs to a single GitHub org. - "organizations:integrations-multi-org": True, + "organizations:integrations-scm-multi-org": True, } permanent_project_features = { diff --git a/tests/sentry/integrations/github/test_integration.py b/tests/sentry/integrations/github/test_integration.py index 078d821de49bad..df5d1ed565891a 100644 --- a/tests/sentry/integrations/github/test_integration.py +++ b/tests/sentry/integrations/github/test_integration.py @@ -1272,6 +1272,7 @@ def test_get_account_id_backfill_missing(self): integration = Integration.objects.get(id=integration_id) assert integration.metadata["account_id"] == 60591805 + @with_feature("organizations:integrations-scm-multi-org") @with_feature("organizations:github-multi-org") @responses.activate @patch("sentry.integrations.utils.metrics.EventLifecycle.record_event") @@ -1315,12 +1316,13 @@ def test_github_installation_calls_ui(self, mock_render, mock_record): mock_render.assert_called_with( request=ANY, pipeline_name="githubInstallationSelect", - props={"installation_info": installations}, + props={"installation_info": installations, "has_business_plan": True}, ) # SLO assertions assert_success_metric(mock_record) + @with_feature("organizations:integrations-scm-multi-org") @with_feature("organizations:github-multi-org") @responses.activate @patch("sentry.integrations.utils.metrics.EventLifecycle.record_event") @@ -1405,6 +1407,7 @@ def test_github_installation_stores_chosen_installation(self, mock_record): # SLO assertions assert_success_metric(mock_record) + @with_feature("organizations:integrations-scm-multi-org") @with_feature("organizations:github-multi-org") @responses.activate @patch("sentry.integrations.utils.metrics.EventLifecycle.record_event") @@ -1476,6 +1479,58 @@ def test_github_installation_fails_on_invalid_installation(self, mock_record): assert_failure_metric(mock_record, GitHubInstallationError.INVALID_INSTALLATION) + @with_feature( + {"organizations:github-multi-org": True, "organizations:integrations-scm-multi-org": False} + ) + @responses.activate + @patch("sentry.integrations.utils.metrics.EventLifecycle.record_event") + @patch.object(PipelineView, "render_react_view", return_value=HttpResponse()) + def test_github_installation_calls_ui_no_biz_plan(self, mock_render, mock_record): + self._setup_with_existing_installations() + installations = [ + { + "installation_id": "1", + "github_account": "santry", + "avatar_url": "https://github.com/knobiknows/all-the-bufo/raw/main/all-the-bufo/bufo-pitchforks.png", + }, + { + "installation_id": "2", + "github_account": "bufo-bot", + "avatar_url": "https://github.com/knobiknows/all-the-bufo/raw/main/all-the-bufo/bufo-pog.png", + }, + { + "installation_id": "-1", + "github_account": "Integrate with a new GitHub organization", + "avatar_url": "", + }, + ] + + resp = self.client.get(self.init_path) + assert resp.status_code == 302 + redirect = urlparse(resp["Location"]) + assert redirect.scheme == "https" + assert redirect.netloc == "github.com" + assert redirect.path == "/login/oauth/authorize" + assert ( + redirect.query + == f"client_id=github-client-id&state={self.pipeline.signature}&redirect_uri=http://testserver/extensions/github/setup/" + ) + resp = self.client.get( + "{}?{}".format( + self.setup_path, + urlencode({"code": "12345678901234567890", "state": self.pipeline.signature}), + ) + ) + mock_render.assert_called_with( + request=ANY, + pipeline_name="githubInstallationSelect", + props={"installation_info": installations, "has_business_plan": False}, + ) + + # SLO assertions + assert_success_metric(mock_record) + + @with_feature("organizations:integrations-scm-multi-org") @with_feature("organizations:github-multi-org") @responses.activate @patch("sentry.integrations.utils.metrics.EventLifecycle.record_event") @@ -1543,6 +1598,7 @@ def test_github_installation_skips_chosen_installation(self, mock_record): # SLO assertions assert_success_metric(mock_record) + @with_feature("organizations:integrations-scm-multi-org") @with_feature("organizations:github-multi-org") @responses.activate def test_github_installation_gets_owner_orgs(self): @@ -1554,6 +1610,7 @@ def test_github_installation_gets_owner_orgs(self): assert owner_orgs == ["santry"] + @with_feature("organizations:integrations-scm-multi-org") @with_feature("organizations:github-multi-org") @responses.activate def test_github_installation_filters_valid_installations(self): From db11b2020f6e3e872014d5ed2ece783e8449659e Mon Sep 17 00:00:00 2001 From: Christinarlong Date: Wed, 21 May 2025 10:07:25 -0700 Subject: [PATCH 05/12] weirdge test --- tests/sentry/api/serializers/test_organization.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/sentry/api/serializers/test_organization.py b/tests/sentry/api/serializers/test_organization.py index 2e900aab3d709a..19bd37fd664d43 100644 --- a/tests/sentry/api/serializers/test_organization.py +++ b/tests/sentry/api/serializers/test_organization.py @@ -82,6 +82,7 @@ def test_simple(self): "integrations-incident-management", "integrations-issue-basic", "integrations-issue-sync", + "integrations-scm-multi-org", "integrations-stacktrace-link", "integrations-ticket-rules", "integrations-vercel", From e1a57ccb628850928bc38d6ed0c9cb7bb295a0e8 Mon Sep 17 00:00:00 2001 From: Christinarlong Date: Tue, 20 May 2025 12:47:53 -0700 Subject: [PATCH 06/12] add ff to feature list --- src/sentry/integrations/base.py | 1 + src/sentry/integrations/github/integration.py | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/src/sentry/integrations/base.py b/src/sentry/integrations/base.py index 32b88ebdfeea7c..fe6d28a934dd90 100644 --- a/src/sentry/integrations/base.py +++ b/src/sentry/integrations/base.py @@ -122,6 +122,7 @@ class IntegrationFeatures(StrEnum): STACKTRACE_LINK = "stacktrace-link" CODEOWNERS = "codeowners" USER_MAPPING = "user-mapping" + SCM_MULTI_ORG = "scm-multi-org" # features currently only existing on plugins: DATA_FORWARDING = "data-forwarding" diff --git a/src/sentry/integrations/github/integration.py b/src/sentry/integrations/github/integration.py index b2c4d02c544cab..c21e36563d7f1c 100644 --- a/src/sentry/integrations/github/integration.py +++ b/src/sentry/integrations/github/integration.py @@ -120,6 +120,12 @@ """, IntegrationFeatures.TICKET_RULES, ), + FeatureDescription( + """ + Connect multiple Sentry organizations to a single GitHub account. + """, + IntegrationFeatures.SCM_MULTI_ORG, + ), ] metadata = IntegrationMetadata( From 49e22aa0714a2421a026101cc3da2aef46c1c907 Mon Sep 17 00:00:00 2001 From: Christinarlong Date: Wed, 21 May 2025 10:48:29 -0700 Subject: [PATCH 07/12] rename business plan to feature check --- src/sentry/integrations/github/integration.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sentry/integrations/github/integration.py b/src/sentry/integrations/github/integration.py index c21e36563d7f1c..1d0bc7c17f6927 100644 --- a/src/sentry/integrations/github/integration.py +++ b/src/sentry/integrations/github/integration.py @@ -893,7 +893,7 @@ def _get_eligible_multi_org_installations( class GithubOrganizationSelection(PipelineView): def dispatch(self, request: HttpRequest, pipeline: Pipeline) -> HttpResponseBase: self.active_user_organization = determine_active_organization(request) - has_business_plan = ( + has_scm_multi_org = ( features.has( "organizations:integrations-scm-multi-org", organization=self.active_user_organization.organization, @@ -927,7 +927,7 @@ def dispatch(self, request: HttpRequest, pipeline: Pipeline) -> HttpResponseBase ) if chosen_installation_id := request.GET.get("chosen_installation_id"): - if chosen_installation_id == "-1" or not has_business_plan: + if chosen_installation_id == "-1" or not has_scm_multi_org: return pipeline.next_step() # Verify that the given GH installation belongs to the person installing the pipeline @@ -953,7 +953,7 @@ def dispatch(self, request: HttpRequest, pipeline: Pipeline) -> HttpResponseBase pipeline_name="githubInstallationSelect", props={ "installation_info": installation_info, - "has_business_plan": has_business_plan, + "has_multi_org": has_scm_multi_org, }, ) From 48b29d1c654986b07dfa2a6d5203de41f0e44fda Mon Sep 17 00:00:00 2001 From: Christinarlong Date: Wed, 21 May 2025 10:54:00 -0700 Subject: [PATCH 08/12] wording --- src/sentry/integrations/github/integration.py | 2 +- tests/sentry/integrations/github/test_integration.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sentry/integrations/github/integration.py b/src/sentry/integrations/github/integration.py index 1d0bc7c17f6927..57f418e6c6d53b 100644 --- a/src/sentry/integrations/github/integration.py +++ b/src/sentry/integrations/github/integration.py @@ -953,7 +953,7 @@ def dispatch(self, request: HttpRequest, pipeline: Pipeline) -> HttpResponseBase pipeline_name="githubInstallationSelect", props={ "installation_info": installation_info, - "has_multi_org": has_scm_multi_org, + "has_scm_multi_org": has_scm_multi_org, }, ) diff --git a/tests/sentry/integrations/github/test_integration.py b/tests/sentry/integrations/github/test_integration.py index df5d1ed565891a..8851220649d4f7 100644 --- a/tests/sentry/integrations/github/test_integration.py +++ b/tests/sentry/integrations/github/test_integration.py @@ -1316,7 +1316,7 @@ def test_github_installation_calls_ui(self, mock_render, mock_record): mock_render.assert_called_with( request=ANY, pipeline_name="githubInstallationSelect", - props={"installation_info": installations, "has_business_plan": True}, + props={"installation_info": installations, "has_scm_multi_org": True}, ) # SLO assertions @@ -1524,7 +1524,7 @@ def test_github_installation_calls_ui_no_biz_plan(self, mock_render, mock_record mock_render.assert_called_with( request=ANY, pipeline_name="githubInstallationSelect", - props={"installation_info": installations, "has_business_plan": False}, + props={"installation_info": installations, "has_scm_multi_org": False}, ) # SLO assertions From 510aa8aa6b3a19d3007fbdb8be352646dba60e4c Mon Sep 17 00:00:00 2001 From: Christinarlong Date: Thu, 22 May 2025 10:45:51 -0700 Subject: [PATCH 09/12] add error if user tries to access multi org without valid feature --- src/sentry/integrations/github/integration.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/sentry/integrations/github/integration.py b/src/sentry/integrations/github/integration.py index 57f418e6c6d53b..8a82b35bbcfab5 100644 --- a/src/sentry/integrations/github/integration.py +++ b/src/sentry/integrations/github/integration.py @@ -762,6 +762,7 @@ class GitHubInstallationError(StrEnum): USER_MISMATCH = "Authenticated user is not the same as who installed the app." MISSING_INTEGRATION = "Integration does not exist." INVALID_INSTALLATION = "User does not have access to given installation" + FEATURE_NOT_AVAILABLE = "Organization does not have access to this feature" def record_event(event: IntegrationPipelineViewType): @@ -927,9 +928,18 @@ def dispatch(self, request: HttpRequest, pipeline: Pipeline) -> HttpResponseBase ) if chosen_installation_id := request.GET.get("chosen_installation_id"): - if chosen_installation_id == "-1" or not has_scm_multi_org: + + if chosen_installation_id == "-1": return pipeline.next_step() + if not has_scm_multi_org: + lifecycle.record_failure(GitHubInstallationError.FEATURE_NOT_AVAILABLE) + return error( + request, + self.active_user_organization, + error_short=GitHubInstallationError.FEATURE_NOT_AVAILABLE, + ) + # Verify that the given GH installation belongs to the person installing the pipeline installation_ids = [ installation["installation_id"] for installation in installation_info From 5ed8d978acbbacac729ae5a32770dad3f88d42b1 Mon Sep 17 00:00:00 2001 From: Christinarlong Date: Thu, 22 May 2025 11:26:28 -0700 Subject: [PATCH 10/12] add error if user tries to mo without ff --- src/sentry/integrations/github/integration.py | 5 +- .../integrations/github/test_integration.py | 70 +++++++++++++++++++ 2 files changed, 72 insertions(+), 3 deletions(-) diff --git a/src/sentry/integrations/github/integration.py b/src/sentry/integrations/github/integration.py index 8a82b35bbcfab5..fafbbfb062c7ed 100644 --- a/src/sentry/integrations/github/integration.py +++ b/src/sentry/integrations/github/integration.py @@ -761,8 +761,8 @@ class GitHubInstallationError(StrEnum): INSTALLATION_EXISTS = "Github installed on another Sentry organization." USER_MISMATCH = "Authenticated user is not the same as who installed the app." MISSING_INTEGRATION = "Integration does not exist." - INVALID_INSTALLATION = "User does not have access to given installation" - FEATURE_NOT_AVAILABLE = "Organization does not have access to this feature" + INVALID_INSTALLATION = "User does not have access to given installation." + FEATURE_NOT_AVAILABLE = "Your organization does not have access to this feature." def record_event(event: IntegrationPipelineViewType): @@ -928,7 +928,6 @@ def dispatch(self, request: HttpRequest, pipeline: Pipeline) -> HttpResponseBase ) if chosen_installation_id := request.GET.get("chosen_installation_id"): - if chosen_installation_id == "-1": return pipeline.next_step() diff --git a/tests/sentry/integrations/github/test_integration.py b/tests/sentry/integrations/github/test_integration.py index 8851220649d4f7..5b0117d770e60f 100644 --- a/tests/sentry/integrations/github/test_integration.py +++ b/tests/sentry/integrations/github/test_integration.py @@ -1530,6 +1530,76 @@ def test_github_installation_calls_ui_no_biz_plan(self, mock_render, mock_record # SLO assertions assert_success_metric(mock_record) + @with_feature( + {"organizations:github-multi-org": True, "organizations:integrations-scm-multi-org": False} + ) + @responses.activate + @patch("sentry.integrations.utils.metrics.EventLifecycle.record_event") + @patch.object(PipelineView, "render_react_view", return_value=HttpResponse()) + def test_errors_when_invalid_access_to_multi_org(self, mock_render, mock_record): + self._setup_with_existing_installations() + installations = [ + { + "installation_id": "1", + "github_account": "santry", + "avatar_url": "https://github.com/knobiknows/all-the-bufo/raw/main/all-the-bufo/bufo-pitchforks.png", + }, + { + "installation_id": "2", + "github_account": "bufo-bot", + "avatar_url": "https://github.com/knobiknows/all-the-bufo/raw/main/all-the-bufo/bufo-pog.png", + }, + { + "installation_id": "-1", + "github_account": "Integrate with a new GitHub organization", + "avatar_url": "", + }, + ] + + resp = self.client.get(self.init_path) + assert resp.status_code == 302 + redirect = urlparse(resp["Location"]) + assert redirect.scheme == "https" + assert redirect.netloc == "github.com" + assert redirect.path == "/login/oauth/authorize" + assert ( + redirect.query + == f"client_id=github-client-id&state={self.pipeline.signature}&redirect_uri=http://testserver/extensions/github/setup/" + ) + resp = self.client.get( + "{}?{}".format( + self.setup_path, + urlencode({"code": "12345678901234567890", "state": self.pipeline.signature}), + ) + ) + mock_render.assert_called_with( + request=ANY, + pipeline_name="githubInstallationSelect", + props={"installation_info": installations, "has_scm_multi_org": False}, + ) + + # We rendered the GithubOrganizationSelection UI and the user chose to skip + resp = self.client.get( + "{}?{}".format( + self.setup_path, + urlencode( + { + "code": "12345678901234567890", + "state": self.pipeline.signature, + "chosen_installation_id": "12345", + } + ), + ) + ) + + self.assertTemplateUsed(resp, "sentry/integrations/github-integration-failed.html") + assert ( + b'{"success":false,"data":{"error":"Your organization does not have access to this feature."}}' + in resp.content + ) + assert b'window.opener.postMessage({"success":false' in resp.content + assert_failure_metric(mock_record, GitHubInstallationError.FEATURE_NOT_AVAILABLE) + @with_feature("organizations:integrations-scm-multi-org") @with_feature("organizations:github-multi-org") @responses.activate From 156fb07a7a4dfe918a2ddfe9f0f8f45550d8b1c0 Mon Sep 17 00:00:00 2001 From: Christinarlong Date: Thu, 22 May 2025 11:27:19 -0700 Subject: [PATCH 11/12] add error if user tries to mo without ff --- src/sentry/integrations/base.py | 1 - src/sentry/integrations/github/integration.py | 6 ------ tests/sentry/integrations/github/test_integration.py | 2 +- 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/sentry/integrations/base.py b/src/sentry/integrations/base.py index fe6d28a934dd90..32b88ebdfeea7c 100644 --- a/src/sentry/integrations/base.py +++ b/src/sentry/integrations/base.py @@ -122,7 +122,6 @@ class IntegrationFeatures(StrEnum): STACKTRACE_LINK = "stacktrace-link" CODEOWNERS = "codeowners" USER_MAPPING = "user-mapping" - SCM_MULTI_ORG = "scm-multi-org" # features currently only existing on plugins: DATA_FORWARDING = "data-forwarding" diff --git a/src/sentry/integrations/github/integration.py b/src/sentry/integrations/github/integration.py index fafbbfb062c7ed..78a95bbeab782d 100644 --- a/src/sentry/integrations/github/integration.py +++ b/src/sentry/integrations/github/integration.py @@ -120,12 +120,6 @@ """, IntegrationFeatures.TICKET_RULES, ), - FeatureDescription( - """ - Connect multiple Sentry organizations to a single GitHub account. - """, - IntegrationFeatures.SCM_MULTI_ORG, - ), ] metadata = IntegrationMetadata( diff --git a/tests/sentry/integrations/github/test_integration.py b/tests/sentry/integrations/github/test_integration.py index 5b0117d770e60f..7b2887144333e7 100644 --- a/tests/sentry/integrations/github/test_integration.py +++ b/tests/sentry/integrations/github/test_integration.py @@ -1452,7 +1452,7 @@ def test_github_installation_fails_on_invalid_installation(self, mock_record): self.assertTemplateUsed(resp, "sentry/integrations/github-integration-failed.html") assert ( - b'{"success":false,"data":{"error":"User does not have access to given installation"}' + b'{"success":false,"data":{"error":"User does not have access to given installation."}' in resp.content ) assert ( From 84a0414df7b59a9a50576e78ecad614d335bc285 Mon Sep 17 00:00:00 2001 From: Christinarlong Date: Thu, 22 May 2025 13:40:26 -0700 Subject: [PATCH 12/12] remove user parameter in ff check --- src/sentry/integrations/github/integration.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/sentry/integrations/github/integration.py b/src/sentry/integrations/github/integration.py index 78a95bbeab782d..be75db50aa178f 100644 --- a/src/sentry/integrations/github/integration.py +++ b/src/sentry/integrations/github/integration.py @@ -835,7 +835,6 @@ def dispatch(self, request: HttpRequest, pipeline: Pipeline) -> HttpResponseBase if self.active_user_organization is not None and features.has( "organizations:github-multi-org", organization=self.active_user_organization.organization, - actor=request.user, ): owner_orgs = self._get_owner_github_organizations() @@ -892,7 +891,6 @@ def dispatch(self, request: HttpRequest, pipeline: Pipeline) -> HttpResponseBase features.has( "organizations:integrations-scm-multi-org", organization=self.active_user_organization.organization, - actor=request.user, ) if self.active_user_organization is not None else False @@ -901,7 +899,6 @@ def dispatch(self, request: HttpRequest, pipeline: Pipeline) -> HttpResponseBase if self.active_user_organization is None or not features.has( "organizations:github-multi-org", organization=self.active_user_organization.organization, - actor=request.user, ): return pipeline.next_step() @@ -1000,7 +997,6 @@ def dispatch(self, request: HttpRequest, pipeline: Pipeline) -> HttpResponseBase if features.has( "organizations:github-multi-org", organization=self.active_user_organization.organization, - actor=request.user, ): try: integration = Integration.objects.get(