Skip to content

Commit 80bbe2e

Browse files
feat: Change org selector add gh integration button behavior (#102680)
Followup to https://linear.app/getsentry/issue/CCMRG-1689/redirect-the-integrated-org-link Changes link to org integrated selection "Add GitHub organization" button to also open GH OAuth. Opens popout window to this link like Settings>Integration and now the TA Preonboarding page also after above PR <img width="1072" height="140" alt="Screenshot 2025-11-06 at 11 31 31 AM" src="https://github.com/user-attachments/assets/7385128c-b673-430f-bdc8-e9ac2f44d9e9" />
1 parent 37615e1 commit 80bbe2e

File tree

7 files changed

+117
-10
lines changed

7 files changed

+117
-10
lines changed

static/app/components/prevent/integratedOrgSelector/integratedOrgSelector.spec.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import {GitHubIntegrationProviderFixture} from 'sentry-fixture/githubIntegrationProvider';
2+
13
import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
24

35
import PreventQueryParamsProvider from 'sentry/components/prevent/container/preventParamsProvider';
@@ -18,6 +20,13 @@ const mockApiCall = () => {
1820
method: 'GET',
1921
body: mockIntegrations,
2022
});
23+
MockApiClient.addMockResponse({
24+
url: `/organizations/org-slug/config/integrations/`,
25+
method: 'GET',
26+
body: {
27+
providers: [GitHubIntegrationProviderFixture()],
28+
},
29+
});
2130
};
2231

2332
describe('IntegratedOrgSelector', () => {

static/app/components/prevent/integratedOrgSelector/integratedOrgSelector.tsx

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,53 @@
11
import {useCallback, useMemo} from 'react';
22
import styled from '@emotion/styled';
33

4+
import {Button} from 'sentry/components/core/button';
45
import {LinkButton} from 'sentry/components/core/button/linkButton';
56
import type {SelectOption} from 'sentry/components/core/compactSelect';
67
import {CompactSelect} from 'sentry/components/core/compactSelect';
78
import {Flex, Grid} from 'sentry/components/core/layout';
89
import {ExternalLink} from 'sentry/components/core/link';
910
import {Text} from 'sentry/components/core/text';
1011
import DropdownButton from 'sentry/components/dropdownButton';
12+
import LoadingIndicator from 'sentry/components/loadingIndicator';
1113
import {usePreventContext} from 'sentry/components/prevent/context/preventContext';
1214
import {integratedOrgIdToName} from 'sentry/components/prevent/utils';
1315
import {IconAdd, IconBuilding, IconInfo} from 'sentry/icons';
1416
import {t, tct} from 'sentry/locale';
17+
import type {IntegrationWithConfig} from 'sentry/types/integrations';
18+
import {useApiQuery} from 'sentry/utils/queryClient';
1519
import useOrganization from 'sentry/utils/useOrganization';
1620
import {useGetActiveIntegratedOrgs} from 'sentry/views/prevent/tests/queries/useGetActiveIntegratedOrgs';
21+
import AddIntegration from 'sentry/views/settings/organizationIntegrations/addIntegration';
22+
import type {IntegrationInformation} from 'sentry/views/settings/organizationIntegrations/integrationDetailedView';
1723

1824
const DEFAULT_ORG_LABEL = 'Select GitHub Org';
1925

2026
function OrgFooterMessage() {
27+
const organization = useOrganization();
28+
29+
const handleAddIntegration = useCallback((_integration: IntegrationWithConfig) => {
30+
window.location.reload();
31+
}, []);
32+
33+
const {data: integrationInfo, isPending: isIntegrationInfoPending} =
34+
useApiQuery<IntegrationInformation>(
35+
[
36+
`/organizations/${organization.slug}/config/integrations/`,
37+
{
38+
query: {
39+
provider_key: 'github',
40+
},
41+
},
42+
],
43+
{
44+
staleTime: Infinity,
45+
retry: false,
46+
}
47+
);
48+
49+
const provider = integrationInfo?.providers[0];
50+
2151
return (
2252
<Flex gap="sm" direction="column" align="start">
2353
<Grid columns="max-content 1fr" gap="sm">
@@ -37,14 +67,35 @@ function OrgFooterMessage() {
3767
</Text>
3868
)}
3969
</Grid>
40-
<LinkButton
41-
href="https://github.com/apps/sentry/installations/select_target"
42-
size="xs"
43-
icon={<IconAdd />}
44-
external
45-
>
46-
{t('GitHub Organization')}
47-
</LinkButton>
70+
{isIntegrationInfoPending ? (
71+
<LoadingIndicator />
72+
) : provider ? (
73+
<AddIntegration
74+
provider={provider}
75+
onInstall={handleAddIntegration}
76+
organization={organization}
77+
>
78+
{openDialog => (
79+
<Button
80+
size="xs"
81+
icon={<IconAdd />}
82+
priority="default"
83+
onClick={() => openDialog()}
84+
>
85+
{t('GitHub Organization')}
86+
</Button>
87+
)}
88+
</AddIntegration>
89+
) : (
90+
<LinkButton
91+
href="https://github.com/apps/sentry/installations/select_target"
92+
size="xs"
93+
icon={<IconAdd />}
94+
external
95+
>
96+
{t('GitHub Organization')}
97+
</LinkButton>
98+
)}
4899
</Flex>
49100
);
50101
}

static/app/views/prevent/index.spec.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import {GitHubIntegrationProviderFixture} from 'sentry-fixture/githubIntegrationProvider';
12
import {OrganizationFixture} from 'sentry-fixture/organization';
23

34
import {render, screen} from 'sentry-test/reactTestingLibrary';
@@ -15,6 +16,16 @@ describe('PreventPage', () => {
1516
children: [{index: true, element: <p>Test content</p>}],
1617
};
1718

19+
beforeEach(() => {
20+
MockApiClient.addMockResponse({
21+
url: `/organizations/org-slug/config/integrations/`,
22+
method: 'GET',
23+
body: {
24+
providers: [GitHubIntegrationProviderFixture()],
25+
},
26+
});
27+
});
28+
1829
describe('when the user has access to the feature', () => {
1930
it('renders the passed children', () => {
2031
render(<PreventPage />, {

static/app/views/prevent/tests/onboarding.spec.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import {GitHubIntegrationProviderFixture} from 'sentry-fixture/githubIntegrationProvider';
2+
13
import {render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrary';
24

35
import {PreventContext} from 'sentry/components/prevent/context/preventContext';
@@ -9,6 +11,7 @@ jest.mock('sentry/utils/regions', () => ({
911
}));
1012

1113
const mockGetRegionData = jest.mocked(getRegionDataFromOrganization);
14+
const mockProvider = GitHubIntegrationProviderFixture();
1215

1316
const mockPreventContext = {
1417
repository: 'test-repo',
@@ -87,6 +90,13 @@ describe('TestsOnboardingPage', () => {
8790
url: `/organizations/org-slug/prevent/owner/123/repository/test-repo/`,
8891
body: mockRepoData,
8992
});
93+
MockApiClient.addMockResponse({
94+
url: `/organizations/org-slug/config/integrations/`,
95+
method: 'GET',
96+
body: {
97+
providers: [mockProvider],
98+
},
99+
});
90100
});
91101

92102
afterEach(() => {

static/app/views/prevent/tests/tests.spec.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import {GitHubIntegrationProviderFixture} from 'sentry-fixture/githubIntegrationProvider';
12
import {OrganizationFixture} from 'sentry-fixture/organization';
23

34
import {render, screen, waitFor} from 'sentry-test/reactTestingLibrary';
@@ -136,6 +137,13 @@ const mockApiCall = () => {
136137
method: 'GET',
137138
body: mockRepoData,
138139
});
140+
MockApiClient.addMockResponse({
141+
url: `/organizations/org-slug/config/integrations/`,
142+
method: 'GET',
143+
body: {
144+
providers: [GitHubIntegrationProviderFixture()],
145+
},
146+
});
139147
};
140148

141149
describe('CoveragePageWrapper', () => {

static/app/views/prevent/tokens/tokens.spec.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import {GitHubIntegrationProviderFixture} from 'sentry-fixture/githubIntegrationProvider';
2+
13
import {render, screen, waitFor} from 'sentry-test/reactTestingLibrary';
24

35
import PreventQueryParamsProvider from 'sentry/components/prevent/container/preventParamsProvider';
@@ -48,6 +50,13 @@ const mockApiCall = () => {
4850
method: 'GET',
4951
body: mockRepositoryTokensResponse,
5052
});
53+
MockApiClient.addMockResponse({
54+
url: `/organizations/org-slug/config/integrations/`,
55+
method: 'GET',
56+
body: {
57+
providers: [GitHubIntegrationProviderFixture()],
58+
},
59+
});
5160
};
5261

5362
describe('TokensPage', () => {
@@ -190,6 +199,14 @@ describe('TokensPage', () => {
190199
body: mockIntegrations,
191200
});
192201

202+
MockApiClient.addMockResponse({
203+
url: `/organizations/org-slug/config/integrations/`,
204+
method: 'GET',
205+
body: {
206+
providers: [GitHubIntegrationProviderFixture()],
207+
},
208+
});
209+
193210
const mockTokensCall = MockApiClient.addMockResponse({
194211
url: `/organizations/org-slug/prevent/owner/1/repositories/tokens/`,
195212
method: 'GET',

static/app/views/settings/organizationIntegrations/addIntegrationButton.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,12 @@ export function AddIntegrationButton({
3131
...buttonProps
3232
}: AddIntegrationButtonProps) {
3333
const label =
34-
(buttonText ?? reinstall)
34+
buttonText ??
35+
(reinstall
3536
? t('Enable')
3637
: installStatus === 'Disabled'
3738
? t('Reinstall')
38-
: t('Add %s', provider.metadata.noun);
39+
: t('Add %s', provider.metadata.noun));
3940

4041
return (
4142
<Tooltip

0 commit comments

Comments
 (0)