From 81fcfe2ed972834f5283a1cc6dfa6bc40e75f6fe Mon Sep 17 00:00:00 2001 From: =?utf8?q?Gr=C3=A9goire=20Aubert?= Date: Wed, 21 Nov 2018 10:01:36 +0100 Subject: [PATCH] SONARCLOUD-171 Redirect already bound org to SC organization page --- .../organization/CreateOrganization.tsx | 28 ++++++++--- .../organization/RemoteOrganizationChoose.tsx | 10 +++- .../__tests__/CreateOrganization-test.tsx | 49 +++++++++++++++++++ .../RemoteOrganizationChoose-test.tsx.snap | 2 + .../main/js/apps/create/organization/utils.ts | 3 ++ .../apps/create/project/AutoProjectCreate.tsx | 6 ++- 6 files changed, 87 insertions(+), 11 deletions(-) diff --git a/server/sonar-web/src/main/js/apps/create/organization/CreateOrganization.tsx b/server/sonar-web/src/main/js/apps/create/organization/CreateOrganization.tsx index 9e54bf6295b..8c4b57192ea 100644 --- a/server/sonar-web/src/main/js/apps/create/organization/CreateOrganization.tsx +++ b/server/sonar-web/src/main/js/apps/create/organization/CreateOrganization.tsx @@ -31,7 +31,8 @@ import { ORGANIZATION_IMPORT_REDIRECT_TO_PROJECT_TIMESTAMP, parseQuery, serializeQuery, - Query + Query, + ORGANIZATION_IMPORT_BINDING_IN_PROGRESS_TIMESTAMP } from './utils'; import AlmApplicationInstalling from './AlmApplicationInstalling'; import AutoOrganizationCreate from './AutoOrganizationCreate'; @@ -196,7 +197,17 @@ export class CreateOrganization extends React.PureComponent { if (this.mounted) { - this.setState({ almOrganization, almOrgLoading: false, boundOrganization }); + if ( + boundOrganization && + boundOrganization.key && + !this.isStoredTimestampValid(ORGANIZATION_IMPORT_BINDING_IN_PROGRESS_TIMESTAMP) + ) { + this.props.router.push({ + pathname: getOrganizationUrl(boundOrganization.key) + }); + } else { + this.setState({ almOrganization, almOrgLoading: false, boundOrganization }); + } } }, () => { @@ -217,12 +228,7 @@ export class CreateOrganization extends React.PureComponent { this.props.skipOnboarding(); - const redirectProjectTimestamp = get(ORGANIZATION_IMPORT_REDIRECT_TO_PROJECT_TIMESTAMP); - remove(ORGANIZATION_IMPORT_REDIRECT_TO_PROJECT_TIMESTAMP); - if ( - redirectProjectTimestamp && - differenceInMinutes(Date.now(), Number(redirectProjectTimestamp)) < 10 - ) { + if (this.isStoredTimestampValid(ORGANIZATION_IMPORT_REDIRECT_TO_PROJECT_TIMESTAMP)) { this.props.router.push({ pathname: '/projects/create', state: { organization, tab: this.state.almOrganization ? 'auto' : 'manual' } @@ -235,6 +241,12 @@ export class CreateOrganization extends React.PureComponent { + const storedTimestamp = get(timestampKey); + remove(timestampKey); + return storedTimestamp && differenceInMinutes(Date.now(), Number(storedTimestamp)) < 10; + }; + onTabChange = (tab: TabKeys) => { this.updateUrlState({ tab }); }; diff --git a/server/sonar-web/src/main/js/apps/create/organization/RemoteOrganizationChoose.tsx b/server/sonar-web/src/main/js/apps/create/organization/RemoteOrganizationChoose.tsx index 149586bde0d..8ac73dfba21 100644 --- a/server/sonar-web/src/main/js/apps/create/organization/RemoteOrganizationChoose.tsx +++ b/server/sonar-web/src/main/js/apps/create/organization/RemoteOrganizationChoose.tsx @@ -21,7 +21,7 @@ import * as React from 'react'; import { WithRouterProps, withRouter } from 'react-router'; import { FormattedMessage } from 'react-intl'; import { sortBy } from 'lodash'; -import { serializeQuery } from './utils'; +import { serializeQuery, ORGANIZATION_IMPORT_BINDING_IN_PROGRESS_TIMESTAMP } from './utils'; import IdentityProviderLink from '../../../components/ui/IdentityProviderLink'; import OrganizationAvatar from '../../../components/common/OrganizationAvatar'; import Select from '../../../components/controls/Select'; @@ -33,9 +33,10 @@ import { AlmUnboundApplication, OrganizationBase } from '../../../app/types'; -import { getBaseUrl } from '../../../helpers/urls'; import { sanitizeAlmId } from '../../../helpers/almIntegrations'; import { translate } from '../../../helpers/l10n'; +import { save } from '../../../helpers/storage'; +import { getBaseUrl } from '../../../helpers/urls'; interface Props { almApplication: AlmApplication; @@ -67,6 +68,10 @@ export class RemoteOrganizationChoose extends React.PureComponent { + save(ORGANIZATION_IMPORT_BINDING_IN_PROGRESS_TIMESTAMP, Date.now().toString(10)); + }; + handleInstallationChange = ({ installationId }: AlmUnboundApplication) => { this.setState({ unboundInstallationId: installationId }); }; @@ -144,6 +149,7 @@ export class RemoteOrganizationChoose extends React.PureComponent {translate( diff --git a/server/sonar-web/src/main/js/apps/create/organization/__tests__/CreateOrganization-test.tsx b/server/sonar-web/src/main/js/apps/create/organization/__tests__/CreateOrganization-test.tsx index febe22899e8..ece50179d3f 100644 --- a/server/sonar-web/src/main/js/apps/create/organization/__tests__/CreateOrganization-test.tsx +++ b/server/sonar-web/src/main/js/apps/create/organization/__tests__/CreateOrganization-test.tsx @@ -245,6 +245,55 @@ it('should redirect to projects creation page after creation', async () => { }); }); +it('should display AutoOrganizationCreate with already bound organization', async () => { + (getAlmOrganization as jest.Mock).mockResolvedValueOnce({ + almOrganization: { + avatar: 'https://avatars3.githubusercontent.com/u/37629810?v=4', + key: 'Foo&Bar', + name: 'Foo & Bar', + personal: true + }, + boundOrganization: { key: 'foobar', name: 'Foo & Bar' } + }); + (get as jest.Mock).mockReturnValueOnce(Date.now().toString()); + const push = jest.fn(); + const wrapper = shallowRender({ + currentUser: { ...user, externalProvider: 'github' }, + location: { query: { installation_id: 'foo' } } as Location, + router: mockRouter({ push }) + }); + await waitAndUpdate(wrapper); + expect(get).toHaveBeenCalled(); + expect(remove).toHaveBeenCalled(); + expect(getAlmOrganization).toHaveBeenCalledWith({ installationId: 'foo' }); + expect(push).not.toHaveBeenCalled(); + expect(wrapper.find('AutoOrganizationCreate').prop('boundOrganization')).toEqual({ + key: 'foobar', + name: 'Foo & Bar' + }); +}); + +it('should redirect to org page when already bound and no binding in progress', async () => { + (getAlmOrganization as jest.Mock).mockResolvedValueOnce({ + almOrganization: { + avatar: 'https://avatars3.githubusercontent.com/u/37629810?v=4', + key: 'Foo&Bar', + name: 'Foo & Bar', + personal: true + }, + boundOrganization: { key: 'foobar', name: 'Foo & Bar' } + }); + const push = jest.fn(); + const wrapper = shallowRender({ + currentUser: { ...user, externalProvider: 'github' }, + location: { query: { installation_id: 'foo' } } as Location, + router: mockRouter({ push }) + }); + await waitAndUpdate(wrapper); + expect(getAlmOrganization).toHaveBeenCalledWith({ installationId: 'foo' }); + expect(push).toHaveBeenCalledWith({ pathname: '/organizations/foobar' }); +}); + function shallowRender(props: Partial = {}) { return shallow( @@ -171,6 +172,7 @@ exports[`should render 1`] = ` "name": "GitHub", } } + onClick={[Function]} small={true} url="https://alm.application.url" > diff --git a/server/sonar-web/src/main/js/apps/create/organization/utils.ts b/server/sonar-web/src/main/js/apps/create/organization/utils.ts index 7c3c9aa3af7..29d09d82fb1 100644 --- a/server/sonar-web/src/main/js/apps/create/organization/utils.ts +++ b/server/sonar-web/src/main/js/apps/create/organization/utils.ts @@ -29,6 +29,9 @@ import { import { isBitbucket, isGithub } from '../../../helpers/almIntegrations'; import { decodeJwt } from '../../../helpers/strings'; +export const ORGANIZATION_IMPORT_BINDING_IN_PROGRESS_TIMESTAMP = + 'sonarcloud.import_org.binding_in_progress'; + export const ORGANIZATION_IMPORT_REDIRECT_TO_PROJECT_TIMESTAMP = 'sonarcloud.import_org.redirect_to_projects'; diff --git a/server/sonar-web/src/main/js/apps/create/project/AutoProjectCreate.tsx b/server/sonar-web/src/main/js/apps/create/project/AutoProjectCreate.tsx index 0737d87182f..1440a56ab88 100644 --- a/server/sonar-web/src/main/js/apps/create/project/AutoProjectCreate.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/AutoProjectCreate.tsx @@ -22,7 +22,10 @@ import RemoteRepositories from './RemoteRepositories'; import OrganizationInput from './OrganizationInput'; import IdentityProviderLink from '../../../components/ui/IdentityProviderLink'; import { AlmApplication, Organization } from '../../../app/types'; -import { ORGANIZATION_IMPORT_REDIRECT_TO_PROJECT_TIMESTAMP } from '../organization/utils'; +import { + ORGANIZATION_IMPORT_BINDING_IN_PROGRESS_TIMESTAMP, + ORGANIZATION_IMPORT_REDIRECT_TO_PROJECT_TIMESTAMP +} from '../organization/utils'; import { translate } from '../../../helpers/l10n'; import { save } from '../../../helpers/storage'; @@ -54,6 +57,7 @@ export default class AutoProjectCreate extends React.PureComponent } handleInstallAppClick = () => { + save(ORGANIZATION_IMPORT_BINDING_IN_PROGRESS_TIMESTAMP, Date.now().toString(10)); save(ORGANIZATION_IMPORT_REDIRECT_TO_PROJECT_TIMESTAMP, Date.now().toString(10)); }; -- 2.39.5