Browse Source

fix(integrations): adds a redirect to the org subdomain when picking the org (#62245)

This PR fixes a lot of the problems we have on the external install page
when selecting an org that's a different subdomain than in the URL. This
fixes the problem by just changing the subdomain to be what we want if
the user changes the org. Note there is probably more cleanup that needs
to be done in this file with regards to how we handle URLs but that's
out of scope for now.
Stephen Cefali 1 year ago
parent
commit
15db84eb13

+ 74 - 11
static/app/views/integrationOrganizationLink/index.spec.tsx

@@ -1,23 +1,28 @@
 import selectEvent from 'react-select-event';
 import selectEvent from 'react-select-event';
 import pick from 'lodash/pick';
 import pick from 'lodash/pick';
+import {ConfigFixture} from 'sentry-fixture/config';
 import {OrganizationFixture} from 'sentry-fixture/organization';
 import {OrganizationFixture} from 'sentry-fixture/organization';
 import {VercelProviderFixture} from 'sentry-fixture/vercelIntegration';
 import {VercelProviderFixture} from 'sentry-fixture/vercelIntegration';
 
 
 import {initializeOrg} from 'sentry-test/initializeOrg';
 import {initializeOrg} from 'sentry-test/initializeOrg';
 import {render, screen} from 'sentry-test/reactTestingLibrary';
 import {render, screen} from 'sentry-test/reactTestingLibrary';
 
 
+import ConfigStore from 'sentry/stores/configStore';
+import {generateOrgSlugUrl} from 'sentry/utils';
 import IntegrationOrganizationLink from 'sentry/views/integrationOrganizationLink';
 import IntegrationOrganizationLink from 'sentry/views/integrationOrganizationLink';
 
 
 describe('IntegrationOrganizationLink', () => {
 describe('IntegrationOrganizationLink', () => {
-  it('selecting org from dropdown loads the org through the API', async () => {
-    const {routerProps} = initializeOrg();
+  let org1, org2, getOrgsMock;
+  beforeEach(() => {
+    MockApiClient.clearMockResponses();
+    window.location.assign = jest.fn();
 
 
-    const org1 = OrganizationFixture({
+    org1 = OrganizationFixture({
       slug: 'org1',
       slug: 'org1',
       name: 'Organization 1',
       name: 'Organization 1',
     });
     });
 
 
-    const org2 = OrganizationFixture({
+    org2 = OrganizationFixture({
       slug: 'org2',
       slug: 'org2',
       name: 'Organization 2',
       name: 'Organization 2',
     });
     });
@@ -25,18 +30,36 @@ describe('IntegrationOrganizationLink', () => {
     const org1Lite = pick(org1, ['slug', 'name', 'id']);
     const org1Lite = pick(org1, ['slug', 'name', 'id']);
     const org2Lite = pick(org2, ['slug', 'name', 'id']);
     const org2Lite = pick(org2, ['slug', 'name', 'id']);
 
 
-    const getOrgsMock = MockApiClient.addMockResponse({
+    getOrgsMock = MockApiClient.addMockResponse({
       url: '/organizations/',
       url: '/organizations/',
       body: [org1Lite, org2Lite],
       body: [org1Lite, org2Lite],
     });
     });
+  });
+
+  it('selecting org changes the url', async () => {
+    const preselectedOrg = OrganizationFixture();
+    const {routerProps} = initializeOrg({organization: preselectedOrg});
+
+    window.__initialData = ConfigFixture({
+      customerDomain: {
+        subdomain: 'foobar',
+        organizationUrl: 'https://foobar.sentry.io',
+        sentryUrl: 'https://sentry.io',
+      },
+      links: {
+        ...(window.__initialData?.links ?? {}),
+        sentryUrl: 'https://sentry.io',
+      },
+    });
+    ConfigStore.loadInitialData(window.__initialData);
 
 
     const getOrgMock = MockApiClient.addMockResponse({
     const getOrgMock = MockApiClient.addMockResponse({
-      url: `/organizations/${org2.slug}/`,
-      body: org2,
+      url: `/organizations/foobar/`,
+      body: preselectedOrg,
     });
     });
 
 
-    const getProviderMock = MockApiClient.addMockResponse({
-      url: `/organizations/${org2.slug}/config/integrations/?provider_key=vercel`,
+    MockApiClient.addMockResponse({
+      url: `/organizations/foobar/config/integrations/?provider_key=vercel`,
       body: {providers: [VercelProviderFixture()]},
       body: {providers: [VercelProviderFixture()]},
     });
     });
 
 
@@ -48,13 +71,53 @@ describe('IntegrationOrganizationLink', () => {
     );
     );
 
 
     expect(getOrgsMock).toHaveBeenCalled();
     expect(getOrgsMock).toHaveBeenCalled();
-    expect(getOrgMock).not.toHaveBeenCalled();
+    expect(getOrgMock).toHaveBeenCalled();
 
 
     // Select organization
     // Select organization
     await selectEvent.select(screen.getByRole('textbox'), org2.name);
     await selectEvent.select(screen.getByRole('textbox'), org2.name);
+    expect(window.location.assign).toHaveBeenCalledWith(generateOrgSlugUrl(org2.slug));
+  });
+  it('Selecting the same org as the domain allows you to install', async () => {
+    const initialData = initializeOrg({organization: org2});
 
 
-    expect(screen.getByRole('button', {name: 'Install Vercel'})).toBeEnabled();
+    window.__initialData = ConfigFixture({
+      customerDomain: {
+        subdomain: org2.slug,
+        organizationUrl: `https://${org2.slug}.sentry.io`,
+        sentryUrl: 'https://sentry.io',
+      },
+      links: {
+        ...(window.__initialData?.links ?? {}),
+        sentryUrl: 'https://sentry.io',
+      },
+    });
+    ConfigStore.loadInitialData(window.__initialData);
+
+    const getProviderMock = MockApiClient.addMockResponse({
+      url: `/organizations/${org2.slug}/config/integrations/?provider_key=vercel`,
+      body: {providers: [VercelProviderFixture()]},
+    });
+
+    const getOrgMock = MockApiClient.addMockResponse({
+      url: `/organizations/${org2.slug}/`,
+      body: org2,
+    });
 
 
+    render(
+      <IntegrationOrganizationLink
+        {...initialData.routerProps}
+        params={{integrationSlug: 'vercel'}}
+      />,
+      {
+        context: initialData.routerContext,
+      }
+    );
+
+    // Select the same organization as the domain
+    await selectEvent.select(screen.getByRole('textbox'), org2.name);
+    expect(window.location.assign).not.toHaveBeenCalled();
+
+    expect(screen.getByRole('button', {name: 'Install Vercel'})).toBeEnabled();
     expect(getProviderMock).toHaveBeenCalled();
     expect(getProviderMock).toHaveBeenCalled();
     expect(getOrgMock).toHaveBeenCalled();
     expect(getOrgMock).toHaveBeenCalled();
   });
   });

+ 20 - 4
static/app/views/integrationOrganizationLink/index.tsx

@@ -14,6 +14,7 @@ import ExternalLink from 'sentry/components/links/externalLink';
 import LoadingIndicator from 'sentry/components/loadingIndicator';
 import LoadingIndicator from 'sentry/components/loadingIndicator';
 import NarrowLayout from 'sentry/components/narrowLayout';
 import NarrowLayout from 'sentry/components/narrowLayout';
 import {t, tct} from 'sentry/locale';
 import {t, tct} from 'sentry/locale';
+import ConfigStore from 'sentry/stores/configStore';
 import {Integration, IntegrationProvider, Organization} from 'sentry/types';
 import {Integration, IntegrationProvider, Organization} from 'sentry/types';
 import {generateBaseControlSiloUrl, generateOrgSlugUrl} from 'sentry/utils';
 import {generateBaseControlSiloUrl, generateOrgSlugUrl} from 'sentry/utils';
 import {IntegrationAnalyticsKey} from 'sentry/utils/analytics/integrations';
 import {IntegrationAnalyticsKey} from 'sentry/utils/analytics/integrations';
@@ -54,6 +55,8 @@ export default class IntegrationOrganizationLink extends DeprecatedAsyncView<
   State
   State
 > {
 > {
   disableErrorReport = false;
   disableErrorReport = false;
+  // TODO: stop using control silo which is dependent on figuring out how to
+  // check the Github installation data which is on the control silo
   controlSiloApi = new Client({baseUrl: generateBaseControlSiloUrl() + '/api/0'});
   controlSiloApi = new Client({baseUrl: generateBaseControlSiloUrl() + '/api/0'});
 
 
   getEndpoints(): ReturnType<DeprecatedAsyncView['getEndpoints']> {
   getEndpoints(): ReturnType<DeprecatedAsyncView['getEndpoints']> {
@@ -112,11 +115,25 @@ export default class IntegrationOrganizationLink extends DeprecatedAsyncView<
     // auto select the org if there is only one
     // auto select the org if there is only one
     const {organizations} = this.state;
     const {organizations} = this.state;
     if (organizations.length === 1) {
     if (organizations.length === 1) {
-      this.onSelectOrg({value: organizations[0].slug});
+      this.onSelectOrg(organizations[0].slug);
+    }
+
+    // now check the subomdain and use that org slug if it exists
+    const customerDomain = ConfigStore.get('customerDomain');
+    if (customerDomain?.subdomain) {
+      this.onSelectOrg(customerDomain.subdomain);
     }
     }
   }
   }
 
 
-  onSelectOrg = async ({value: orgSlug}: {value: string}) => {
+  onSelectOrg = async (orgSlug: string) => {
+    const customerDomain = ConfigStore.get('customerDomain');
+    // redirect to the org if it's different than the org being selected
+    if (customerDomain?.subdomain && orgSlug !== customerDomain?.subdomain) {
+      window.location.assign(generateOrgSlugUrl(orgSlug));
+      return;
+    }
+
+    // otherwise proceed as normal
     this.setState({selectedOrgSlug: orgSlug, reloading: true, organization: undefined});
     this.setState({selectedOrgSlug: orgSlug, reloading: true, organization: undefined});
 
 
     try {
     try {
@@ -375,7 +392,7 @@ export default class IntegrationOrganizationLink extends DeprecatedAsyncView<
 
 
         <FieldGroup label={t('Organization')} inline={false} stacked required>
         <FieldGroup label={t('Organization')} inline={false} stacked required>
           <SelectControl
           <SelectControl
-            onChange={this.onSelectOrg}
+            onChange={({value: orgSlug}) => this.onSelectOrg(orgSlug)}
             value={selectedOrgSlug}
             value={selectedOrgSlug}
             placeholder={t('Select an organization')}
             placeholder={t('Select an organization')}
             options={options}
             options={options}
@@ -402,5 +419,4 @@ const ButtonWrapper = styled('div')`
   display: flex;
   display: flex;
   flex-direction: column;
   flex-direction: column;
   align-items: center;
   align-items: center;
-  line-height: 24px;
 `;
 `;