diff options
author | Julien Lancelot <julien.lancelot@sonarsource.com> | 2018-10-30 08:34:54 +0100 |
---|---|---|
committer | SonarTech <sonartech@sonarsource.com> | 2018-11-16 20:21:05 +0100 |
commit | 2507163633382d56601e825ba2aadafc3edbbe9f (patch) | |
tree | 02c8d3f113653a59611637f797b11151b8701638 /server/sonar-web/src/main/js | |
parent | 02f78a025d5a3569c9b1c9e6e1cc5e170b27f91c (diff) | |
download | sonarqube-2507163633382d56601e825ba2aadafc3edbbe9f.tar.gz sonarqube-2507163633382d56601e825ba2aadafc3edbbe9f.zip |
SONAR-11321 Prevent binding already bound application
Diffstat (limited to 'server/sonar-web/src/main/js')
10 files changed, 238 insertions, 102 deletions
diff --git a/server/sonar-web/src/main/js/api/alm-integration.ts b/server/sonar-web/src/main/js/api/alm-integration.ts index 05ba477a887..4ede88d3bff 100644 --- a/server/sonar-web/src/main/js/api/alm-integration.ts +++ b/server/sonar-web/src/main/js/api/alm-integration.ts @@ -22,7 +22,8 @@ import { AlmApplication, AlmOrganization, AlmRepository, - AlmUnboundApplication + AlmUnboundApplication, + OrganizationBase } from '../app/types'; import throwGlobalError from '../app/utils/throwGlobalError'; @@ -51,10 +52,20 @@ function fetchAlmOrganization(data: { installationId: string }, remainingTries: ); } -export function getAlmOrganization(data: { installationId: string }): Promise<AlmOrganization> { - return fetchAlmOrganization(data, 5).then(({ organization }) => ({ - ...organization, - name: organization.name || organization.key +export interface GetAlmOrganizationResponse { + almOrganization: AlmOrganization; + boundOrganization?: OrganizationBase; +} + +export function getAlmOrganization(data: { + installationId: string; +}): Promise<GetAlmOrganizationResponse> { + return fetchAlmOrganization(data, 5).then(({ almOrganization, boundOrganization }) => ({ + almOrganization: { + ...almOrganization, + name: almOrganization.name || almOrganization.key + }, + boundOrganization })); } @@ -64,8 +75,12 @@ export function getRepositories(data: { return getJSON('/api/alm_integration/list_repositories', data).catch(throwGlobalError); } -export function listUnboundApplications(): Promise<{ applications: AlmUnboundApplication[] }> { - return getJSON('/api/alm_integration/list_unbound_applications').catch(throwGlobalError); +export function listUnboundApplications(): Promise<AlmUnboundApplication[]> { + return getJSON('/api/alm_integration/list_unbound_applications').then( + ({ applications }) => + applications.map((app: AlmUnboundApplication) => ({ ...app, name: app.name || app.key })), + throwGlobalError + ); } export function provisionProject(data: { diff --git a/server/sonar-web/src/main/js/app/types.ts b/server/sonar-web/src/main/js/app/types.ts index ff3b88449a0..bcca9babe86 100644 --- a/server/sonar-web/src/main/js/app/types.ts +++ b/server/sonar-web/src/main/js/app/types.ts @@ -42,6 +42,7 @@ export interface AlmRepository { export interface AlmUnboundApplication { installationId: string; + key: string; name: string; } diff --git a/server/sonar-web/src/main/js/apps/create/organization/AutoOrganizationCreate.tsx b/server/sonar-web/src/main/js/apps/create/organization/AutoOrganizationCreate.tsx index f24b6c44f92..5adbae0b71c 100644 --- a/server/sonar-web/src/main/js/apps/create/organization/AutoOrganizationCreate.tsx +++ b/server/sonar-web/src/main/js/apps/create/organization/AutoOrganizationCreate.tsx @@ -38,8 +38,7 @@ import { getBaseUrl } from '../../../helpers/urls'; export enum Filters { Bind = 'bind', - Create = 'create', - None = 'none' + Create = 'create' } interface Props { @@ -47,6 +46,7 @@ interface Props { almInstallId?: string; almOrganization?: AlmOrganization; almUnboundApplications: AlmUnboundApplication[]; + boundOrganization?: OrganizationBase; createOrganization: ( organization: OrganizationBase & { installationId?: string } ) => Promise<Organization>; @@ -55,14 +55,14 @@ interface Props { } interface State { - filter: Filters; + filter?: Filters; } export default class AutoOrganizationCreate extends React.PureComponent<Props, State> { constructor(props: Props) { super(props); this.state = { - filter: props.unboundOrganizations.length === 0 ? Filters.Create : Filters.None + filter: props.unboundOrganizations.length === 0 ? Filters.Create : undefined }; } @@ -71,19 +71,16 @@ export default class AutoOrganizationCreate extends React.PureComponent<Props, S }; handleCreateOrganization = (organization: Required<OrganizationBase>) => { - if (organization) { - return this.props - .createOrganization({ - avatar: organization.avatar, - description: organization.description, - installationId: this.props.almInstallId, - key: organization.key, - name: organization.name || organization.key, - url: organization.url - }) - .then(({ key }) => this.props.onOrgCreated(key)); - } - return Promise.reject(); + return this.props + .createOrganization({ + avatar: organization.avatar, + description: organization.description, + installationId: this.props.almInstallId, + key: organization.key, + name: organization.name || organization.key, + url: organization.url + }) + .then(({ key }) => this.props.onOrgCreated(key)); }; handleBindOrganization = (organization: string) => { @@ -97,8 +94,14 @@ export default class AutoOrganizationCreate extends React.PureComponent<Props, S }; render() { - const { almApplication, almInstallId, almOrganization, unboundOrganizations } = this.props; - if (almInstallId && almOrganization) { + const { + almApplication, + almInstallId, + almOrganization, + boundOrganization, + unboundOrganizations + } = this.props; + if (almInstallId && almOrganization && !boundOrganization) { const { filter } = this.state; const hasUnboundOrgs = unboundOrganizations.length > 0; return ( @@ -168,7 +171,9 @@ export default class AutoOrganizationCreate extends React.PureComponent<Props, S <ChooseRemoteOrganizationStep almApplication={this.props.almApplication} almInstallId={almInstallId} + almOrganization={almOrganization} almUnboundApplications={this.props.almUnboundApplications} + boundOrganization={boundOrganization} /> ); } diff --git a/server/sonar-web/src/main/js/apps/create/organization/AutoPersonalOrganizationBind.tsx b/server/sonar-web/src/main/js/apps/create/organization/AutoPersonalOrganizationBind.tsx index 47a65c594d6..5524c1fc19a 100644 --- a/server/sonar-web/src/main/js/apps/create/organization/AutoPersonalOrganizationBind.tsx +++ b/server/sonar-web/src/main/js/apps/create/organization/AutoPersonalOrganizationBind.tsx @@ -45,20 +45,16 @@ interface Props { export default class AutoPersonalOrganizationBind extends React.PureComponent<Props> { handleCreateOrganization = (organization: Required<OrganizationBase>) => { - if (organization) { - return this.props - .updateOrganization({ - avatar: organization.avatar, - description: organization.description, - installationId: this.props.almInstallId, - key: this.props.importPersonalOrg.key, - name: organization.name || organization.key, - url: organization.url - }) - .then(({ key }) => this.props.onOrgCreated(key)); - } else { - return Promise.reject(); - } + return this.props + .updateOrganization({ + avatar: organization.avatar, + description: organization.description, + installationId: this.props.almInstallId, + key: this.props.importPersonalOrg.key, + name: organization.name || organization.key, + url: organization.url + }) + .then(({ key }) => this.props.onOrgCreated(key)); }; render() { @@ -69,7 +65,7 @@ export default class AutoPersonalOrganizationBind extends React.PureComponent<Pr onOpen={() => {}} open={true} organization={importPersonalOrg}> - <p className="huge-spacer-bottom"> + <div className="huge-spacer-bottom"> <FormattedMessage defaultMessage={translate('onboarding.import_personal_organization_x')} id="onboarding.import_personal_organization_x" @@ -89,7 +85,7 @@ export default class AutoPersonalOrganizationBind extends React.PureComponent<Pr personalName: importPersonalOrg && <strong>{importPersonalOrg.name}</strong> }} /> - </p> + </div> <OrganizationDetailsForm keyReadOnly={true} onContinue={this.handleCreateOrganization} diff --git a/server/sonar-web/src/main/js/apps/create/organization/ChooseRemoteOrganizationStep.tsx b/server/sonar-web/src/main/js/apps/create/organization/ChooseRemoteOrganizationStep.tsx index 9a562150fec..02447552553 100644 --- a/server/sonar-web/src/main/js/apps/create/organization/ChooseRemoteOrganizationStep.tsx +++ b/server/sonar-web/src/main/js/apps/create/organization/ChooseRemoteOrganizationStep.tsx @@ -19,14 +19,21 @@ */ 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 IdentityProviderLink from '../../../components/ui/IdentityProviderLink'; +import OrganizationAvatar from '../../../components/common/OrganizationAvatar'; import Select from '../../../components/controls/Select'; import Step from '../../tutorials/components/Step'; import { Alert } from '../../../components/ui/Alert'; import { SubmitButton } from '../../../components/ui/buttons'; -import { AlmApplication, AlmUnboundApplication } from '../../../app/types'; +import { + AlmApplication, + AlmOrganization, + AlmUnboundApplication, + OrganizationBase +} from '../../../app/types'; import { getBaseUrl } from '../../../helpers/urls'; import { sanitizeAlmId } from '../../../helpers/almIntegrations'; import { translate } from '../../../helpers/l10n'; @@ -34,7 +41,9 @@ import { translate } from '../../../helpers/l10n'; interface Props { almApplication: AlmApplication; almInstallId?: string; + almOrganization?: AlmOrganization; almUnboundApplications: AlmUnboundApplication[]; + boundOrganization?: OrganizationBase; } interface State { @@ -82,19 +91,58 @@ export class ChooseRemoteOrganizationStep extends React.PureComponent< }; renderForm = () => { - const { almApplication, almInstallId, almUnboundApplications } = this.props; + const { + almApplication, + almInstallId, + almOrganization, + almUnboundApplications, + boundOrganization + } = this.props; const { unboundInstallationId } = this.state; return ( <div className="boxed-group-inner"> - {almInstallId && ( - <Alert className="markdown big-spacer-bottom width-60" variant="error"> - {translate('onboarding.import_organization.org_not_found')} - <ul> - <li>{translate('onboarding.import_organization.org_not_found.tips_1')}</li> - <li>{translate('onboarding.import_organization.org_not_found.tips_2')}</li> - </ul> - </Alert> - )} + {almInstallId && + !almOrganization && ( + <Alert className="big-spacer-bottom width-60" variant="error"> + <div className="markdown"> + {translate('onboarding.import_organization.org_not_found')} + <ul> + <li>{translate('onboarding.import_organization.org_not_found.tips_1')}</li> + <li>{translate('onboarding.import_organization.org_not_found.tips_2')}</li> + </ul> + </div> + </Alert> + )} + {almOrganization && + boundOrganization && ( + <Alert className="big-spacer-bottom width-60" variant="error"> + <FormattedMessage + defaultMessage={translate('onboarding.import_organization.already_bound_x')} + id="onboarding.import_organization.already_bound_x" + values={{ + avatar: ( + <img + alt={almApplication.name} + className="little-spacer-left" + src={`${getBaseUrl()}/images/sonarcloud/${sanitizeAlmId( + almApplication.key + )}.svg`} + width={16} + /> + ), + name: <strong>{almOrganization.name}</strong>, + boundAvatar: ( + <OrganizationAvatar + className="little-spacer-left" + organization={boundOrganization} + small={true} + /> + ), + boundName: <strong>{boundOrganization.name}</strong> + }} + /> + </Alert> + )} <div className="display-flex-center"> <div className="display-inline-block abs-width-400"> <IdentityProviderLink 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 44be6b5bcc8..c12db4930b9 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 @@ -42,6 +42,7 @@ import { bindAlmOrganization, getAlmAppInfo, getAlmOrganization, + GetAlmOrganizationResponse, listUnboundApplications } from '../../../api/alm-integration'; import { getSubscriptionPlans } from '../../../api/billing'; @@ -59,8 +60,7 @@ import { translate } from '../../../helpers/l10n'; import { get, remove } from '../../../helpers/storage'; import { slugify } from '../../../helpers/strings'; import { getOrganizationUrl } from '../../../helpers/urls'; -import { skipOnboarding as skipOnboardingAction } from '../../../store/users'; -import { skipOnboarding } from '../../../api/users'; +import { skipOnboarding } from '../../../store/users'; import * as api from '../../../api/organizations'; import * as actions from '../../../store/organizations'; import '../../../app/styles/sonarcloud.css'; @@ -76,7 +76,7 @@ interface Props { organization: OrganizationBase & { installationId?: string } ) => Promise<Organization>; userOrganizations: Organization[]; - skipOnboardingAction: () => void; + skipOnboarding: () => void; } interface State { @@ -84,6 +84,7 @@ interface State { almOrganization?: AlmOrganization; almOrgLoading: boolean; almUnboundApplications: AlmUnboundApplication[]; + boundOrganization?: OrganizationBase; loading: boolean; organization?: Organization; subscriptionPlans?: SubscriptionPlan[]; @@ -127,7 +128,7 @@ export class CreateOrganization extends React.PureComponent<Props & WithRouterPr if (query.almInstallId) { this.fetchAlmOrganization(query.almInstallId); } else { - this.setState({ almOrganization: undefined, loading: true }); + this.setState({ almOrganization: undefined, boundOrganization: undefined, loading: true }); this.fetchAlmUnboundApplications().then(this.stopLoading, this.stopLoading); } } @@ -136,6 +137,9 @@ export class CreateOrganization extends React.PureComponent<Props & WithRouterPr componentWillUnmount() { this.mounted = false; document.body.classList.remove('white-page'); + if (document.documentElement) { + document.documentElement.classList.remove('white-page'); + } } fetchAlmApplication = () => { @@ -147,14 +151,14 @@ export class CreateOrganization extends React.PureComponent<Props & WithRouterPr }; fetchAlmUnboundApplications = () => { - return listUnboundApplications().then(({ applications }) => { + return listUnboundApplications().then(almUnboundApplications => { if (this.mounted) { - this.setState({ almUnboundApplications: applications }); + this.setState({ almUnboundApplications }); } }); }; - fetchValidOrgKey = (almOrganization: AlmOrganization) => { + setValidOrgKey = (almOrganization: AlmOrganization) => { const key = slugify(almOrganization.key); const keys = [key, ...times(9, i => `${key}-${i + 1}`)]; return api @@ -167,24 +171,31 @@ export class CreateOrganization extends React.PureComponent<Props & WithRouterPr () => key ) .then(key => { - return { ...almOrganization, key }; + return { almOrganization: { ...almOrganization, key } }; }); }; fetchAlmOrganization = (installationId: string) => { this.setState({ almOrgLoading: true }); return getAlmOrganization({ installationId }) - .then(this.fetchValidOrgKey) - .then(almOrganization => { - if (this.mounted) { - this.setState({ almOrganization, almOrgLoading: false }); + .then(({ almOrganization, boundOrganization }) => { + if (boundOrganization) { + return Promise.resolve({ almOrganization, boundOrganization }); } + return this.setValidOrgKey(almOrganization); }) - .catch(() => { - if (this.mounted) { - this.setState({ almOrgLoading: false }); + .then( + ({ almOrganization, boundOrganization }: GetAlmOrganizationResponse) => { + if (this.mounted) { + this.setState({ almOrganization, almOrgLoading: false, boundOrganization }); + } + }, + () => { + if (this.mounted) { + this.setState({ almOrgLoading: false }); + } } - }); + ); }; fetchSubscriptionPlans = () => { @@ -196,8 +207,7 @@ export class CreateOrganization extends React.PureComponent<Props & WithRouterPr }; handleOrgCreated = (organization: string, justCreated = true) => { - skipOnboarding().catch(() => {}); - this.props.skipOnboardingAction(); + this.props.skipOnboarding(); const redirectProjectTimestamp = get(ORGANIZATION_IMPORT_REDIRECT_TO_PROJECT_TIMESTAMP); remove(ORGANIZATION_IMPORT_REDIRECT_TO_PROJECT_TIMESTAMP); if ( @@ -237,7 +247,7 @@ export class CreateOrganization extends React.PureComponent<Props & WithRouterPr renderContent = (almInstallId?: string, importPersonalOrg?: Organization) => { const { currentUser, location } = this.props; const { almApplication, almOrganization } = this.state; - const state = (location.state || {}) as LocationState; + const state: LocationState = location.state || {}; if (importPersonalOrg && almOrganization && almApplication) { return ( @@ -287,6 +297,7 @@ export class CreateOrganization extends React.PureComponent<Props & WithRouterPr almInstallId={almInstallId} almOrganization={almOrganization} almUnboundApplications={this.state.almUnboundApplications} + boundOrganization={this.state.boundOrganization} createOrganization={this.props.createOrganization} onOrgCreated={this.handleOrgCreated} unboundOrganizations={this.props.userOrganizations.filter( @@ -392,7 +403,7 @@ const mapDispatchToProps = { createOrganization: createOrganization as any, deleteOrganization: deleteOrganization as any, updateOrganization: updateOrganization as any, - skipOnboardingAction: skipOnboardingAction as any + skipOnboarding: skipOnboarding as any }; export default whenLoggedIn( diff --git a/server/sonar-web/src/main/js/apps/create/organization/__tests__/ChooseRemoteOrganizationStep-test.tsx b/server/sonar-web/src/main/js/apps/create/organization/__tests__/ChooseRemoteOrganizationStep-test.tsx index c9fa537806c..40d80e0b4bb 100644 --- a/server/sonar-web/src/main/js/apps/create/organization/__tests__/ChooseRemoteOrganizationStep-test.tsx +++ b/server/sonar-web/src/main/js/apps/create/organization/__tests__/ChooseRemoteOrganizationStep-test.tsx @@ -31,7 +31,7 @@ it('should display an alert message', () => { }); it('should display unbound installations', () => { - const installation = { installationId: '12345', name: 'Foo' }; + const installation = { installationId: '12345', key: 'foo', name: 'Foo' }; const push = jest.fn(); const wrapper = shallowRender({ almUnboundApplications: [installation], @@ -47,6 +47,16 @@ it('should display unbound installations', () => { }); }); +it('should display already bound alert message', () => { + expect( + shallowRender({ + almInstallId: 'foo', + almOrganization: { avatar: 'foo-avatar', key: 'foo', name: 'Foo', personal: false }, + boundOrganization: { avatar: 'bound-avatar', key: 'bound', name: 'Bound' } + }).find('Alert') + ).toMatchSnapshot(); +}); + function shallowRender(props: Partial<ChooseRemoteOrganizationStep['props']> = {}) { return shallow( // @ts-ignore avoid passing everything from WithRouterProps 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 38e0c5bb65b..1c505aab71d 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 @@ -50,14 +50,16 @@ jest.mock('../../../../api/alm-integration', () => ({ } }), getAlmOrganization: jest.fn().mockResolvedValue({ - avatar: 'my-avatar', - description: 'Continuous Code Quality', - key: 'sonarsource', - name: 'SonarSource', - personal: false, - url: 'https://www.sonarsource.com' + almOrganization: { + avatar: 'my-avatar', + description: 'Continuous Code Quality', + key: 'sonarsource', + name: 'SonarSource', + personal: false, + url: 'https://www.sonarsource.com' + } }), - listUnboundApplications: jest.fn().mockResolvedValue({ applications: [] }) + listUnboundApplications: jest.fn().mockResolvedValue([]) })); jest.mock('../../../../api/organizations', () => ({ @@ -127,10 +129,12 @@ it('should render with auto tab selected and manual disabled', async () => { it('should render with auto personal organization bind page', async () => { (getAlmOrganization as jest.Mock<any>).mockResolvedValueOnce({ - key: 'foo', - name: 'Foo', - avatar: 'my-avatar', - personal: true + almOrganization: { + key: 'foo', + name: 'Foo', + avatar: 'my-avatar', + personal: true + } }); const wrapper = shallowRender({ currentUser: { ...user, externalProvider: 'github', personalOrganization: 'foo' }, @@ -143,10 +147,12 @@ it('should render with auto personal organization bind page', async () => { it('should slugify and find a uniq organization key', async () => { (getAlmOrganization as jest.Mock<any>).mockResolvedValueOnce({ - avatar: 'https://avatars3.githubusercontent.com/u/37629810?v=4', - key: 'Foo&Bar', - name: 'Foo & Bar', - personal: true + almOrganization: { + avatar: 'https://avatars3.githubusercontent.com/u/37629810?v=4', + key: 'Foo&Bar', + name: 'Foo & Bar', + personal: true + } }); (getOrganizations as jest.Mock<any>).mockResolvedValueOnce({ organizations: [{ key: 'foo-and-bar' }, { key: 'foo-and-bar-1' }] @@ -246,7 +252,7 @@ function shallowRender(props: Partial<CreateOrganization['props']> = {}) { // @ts-ignore avoid passing everything from WithRouterProps location={{}} router={mockRouter()} - skipOnboardingAction={jest.fn()} + skipOnboarding={jest.fn()} updateOrganization={jest.fn()} userOrganizations={[ { actions: { admin: true }, key: 'foo', name: 'Foo' }, diff --git a/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/AutoPersonalOrganizationBind-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/AutoPersonalOrganizationBind-test.tsx.snap index f29f5f9d008..2b2026ad813 100644 --- a/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/AutoPersonalOrganizationBind-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/AutoPersonalOrganizationBind-test.tsx.snap @@ -12,7 +12,7 @@ exports[`should render correctly 1`] = ` } } > - <p + <div className="huge-spacer-bottom" > <FormattedMessage @@ -44,7 +44,7 @@ exports[`should render correctly 1`] = ` } } /> - </p> + </div> <OrganizationDetailsForm keyReadOnly={true} onContinue={[Function]} diff --git a/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/ChooseRemoteOrganizationStep-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/ChooseRemoteOrganizationStep-test.tsx.snap index 77778ab5130..6faa756f708 100644 --- a/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/ChooseRemoteOrganizationStep-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/ChooseRemoteOrganizationStep-test.tsx.snap @@ -1,19 +1,62 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`should display already bound alert message 1`] = ` +<Alert + className="big-spacer-bottom width-60" + variant="error" +> + <FormattedMessage + defaultMessage="onboarding.import_organization.already_bound_x" + id="onboarding.import_organization.already_bound_x" + values={ + Object { + "avatar": <img + alt="GitHub" + className="little-spacer-left" + src="/images/sonarcloud/github.svg" + width={16} + />, + "boundAvatar": <OrganizationAvatar + className="little-spacer-left" + organization={ + Object { + "avatar": "bound-avatar", + "key": "bound", + "name": "Bound", + } + } + small={true} + />, + "boundName": <strong> + Bound + </strong>, + "name": <strong> + Foo + </strong>, + } + } + /> +</Alert> +`; + exports[`should display an alert message 1`] = ` <Alert - className="markdown big-spacer-bottom width-60" + className="big-spacer-bottom width-60" variant="error" > - onboarding.import_organization.org_not_found - <ul> - <li> - onboarding.import_organization.org_not_found.tips_1 - </li> - <li> - onboarding.import_organization.org_not_found.tips_2 - </li> - </ul> + <div + className="markdown" + > + onboarding.import_organization.org_not_found + <ul> + <li> + onboarding.import_organization.org_not_found.tips_1 + </li> + <li> + onboarding.import_organization.org_not_found.tips_2 + </li> + </ul> + </div> </Alert> `; @@ -103,6 +146,7 @@ exports[`should display unbound installations 1`] = ` Array [ Object { "installationId": "12345", + "key": "foo", "name": "Foo", }, ] |