diff options
author | Grégoire Aubert <gregoire.aubert@sonarsource.com> | 2017-10-18 16:46:15 +0200 |
---|---|---|
committer | Grégoire Aubert <gregoire.aubert@sonarsource.com> | 2017-10-23 08:01:13 -0700 |
commit | cc18c716b7b930b7b93c94218e8947755b2dbe87 (patch) | |
tree | 0db2d6bd4561c8c7d272f5ef08486c187d25ba8b /server | |
parent | 13b8590acb21f58d0485729425cff724dee3d999 (diff) | |
download | sonarqube-cc18c716b7b930b7b93c94218e8947755b2dbe87.tar.gz sonarqube-cc18c716b7b930b7b93c94218e8947755b2dbe87.zip |
SONAR-10001 Add dialog box to uninstall and edition
Diffstat (limited to 'server')
10 files changed, 311 insertions, 48 deletions
diff --git a/server/sonar-web/src/main/js/api/marketplace.ts b/server/sonar-web/src/main/js/api/marketplace.ts index 4cff1587d94..30c801ae0f2 100644 --- a/server/sonar-web/src/main/js/api/marketplace.ts +++ b/server/sonar-web/src/main/js/api/marketplace.ts @@ -41,7 +41,8 @@ export interface EditionStatus { | 'AUTOMATIC_IN_PROGRESS' | 'MANUAL_IN_PROGRESS' | 'AUTOMATIC_READY' - | 'AUTOMATIC_FAILURE'; + | 'AUTOMATIC_FAILURE' + | 'UNINSTALL_IN_PROGRESS'; } export function getEditionStatus(): Promise<EditionStatus> { @@ -67,3 +68,7 @@ export function getLicensePreview(data: { export function applyLicense(data: { license: string }): Promise<EditionStatus> { return postJSON('/api/editions/apply_license', data).catch(throwGlobalError); } + +export function uninstallEdition(): Promise<void | Response> { + return postJSON('/api/editions/uninstall').catch(throwGlobalError); +} diff --git a/server/sonar-web/src/main/js/apps/marketplace/EditionBoxes.tsx b/server/sonar-web/src/main/js/apps/marketplace/EditionBoxes.tsx index 88fc6f4cebc..112205c8d36 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/EditionBoxes.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/EditionBoxes.tsx @@ -21,6 +21,7 @@ import * as React from 'react'; import { FormattedMessage } from 'react-intl'; import EditionBox from './components/EditionBox'; import LicenseEditionForm from './components/LicenseEditionForm'; +import UninstallEditionForm from './components/UninstallEditionForm'; import { Edition, EditionStatus, getEditionsList } from '../../api/marketplace'; import { getEditionsForVersion } from './utils'; import { translate } from '../../helpers/l10n'; @@ -38,11 +39,12 @@ interface State { editionsError: boolean; loading: boolean; installEdition?: Edition; + openUninstallForm: boolean; } export default class EditionBoxes extends React.PureComponent<Props, State> { mounted: boolean; - state: State = { editionsError: false, loading: true }; + state: State = { editionsError: false, loading: true, openUninstallForm: false }; componentDidMount() { this.mounted = true; @@ -76,14 +78,19 @@ export default class EditionBoxes extends React.PureComponent<Props, State> { handleOpenLicenseForm = (edition: Edition) => this.setState({ installEdition: edition }); handleCloseLicenseForm = () => this.setState({ installEdition: undefined }); + handleOpenUninstallForm = () => this.setState({ openUninstallForm: true }); + handleCloseUninstallForm = () => this.setState({ openUninstallForm: false }); + render() { - const { editions, editionsError, loading, installEdition } = this.state; + const { editionStatus } = this.props; + const { editions, editionsError, loading, installEdition, openUninstallForm } = this.state; if (loading) { return <i className="big-spacer-bottom spinner" />; } - return ( - <div className="spacer-bottom marketplace-editions"> - {!editions || editionsError ? ( + + if (!editions || editionsError) { + return ( + <div className="spacer-bottom marketplace-editions"> <span className="alert alert-info"> <FormattedMessage defaultMessage={translate('marketplace.editions_unavailable')} @@ -97,19 +104,23 @@ export default class EditionBoxes extends React.PureComponent<Props, State> { }} /> </span> - ) : ( - editions.map(edition => ( - <EditionBox - edition={edition} - editionStatus={this.props.editionStatus} - key={edition.key} - onInstall={this.handleOpenLicenseForm} - /> - )) - )} + </div> + ); + } - {editions && - installEdition && ( + return ( + <div className="spacer-bottom marketplace-editions"> + {editions.map(edition => ( + <EditionBox + edition={edition} + editionStatus={editionStatus} + key={edition.key} + onInstall={this.handleOpenLicenseForm} + onUninstall={this.handleOpenUninstallForm} + /> + ))} + + {installEdition && ( <LicenseEditionForm edition={installEdition} editions={editions} @@ -117,6 +128,17 @@ export default class EditionBoxes extends React.PureComponent<Props, State> { updateEditionStatus={this.props.updateEditionStatus} /> )} + + {openUninstallForm && + editionStatus && + editionStatus.currentEditionKey && ( + <UninstallEditionForm + edition={editions.find(edition => edition.key === editionStatus.currentEditionKey)} + editionStatus={editionStatus} + onClose={this.handleCloseUninstallForm} + updateEditionStatus={this.props.updateEditionStatus} + /> + )} </div> ); } diff --git a/server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/EditionBoxes-test.tsx.snap b/server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/EditionBoxes-test.tsx.snap index f6b1475c0e6..59582a7e13d 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/EditionBoxes-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/EditionBoxes-test.tsx.snap @@ -54,6 +54,7 @@ exports[`should display the edition boxes 2`] = ` } } onInstall={[Function]} + onUninstall={[Function]} /> <EditionBox edition={ @@ -74,6 +75,7 @@ exports[`should display the edition boxes 2`] = ` } } onInstall={[Function]} + onUninstall={[Function]} /> </div> `; diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/EditionBox.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/EditionBox.tsx index 91142a45cf7..8ca571c0bfc 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/components/EditionBox.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/components/EditionBox.tsx @@ -26,6 +26,7 @@ interface Props { edition: Edition; editionStatus?: EditionStatus; onInstall: (edition: Edition) => void; + onUninstall: () => void; } export default class EditionBox extends React.PureComponent<Props> { @@ -68,7 +69,10 @@ export default class EditionBox extends React.PureComponent<Props> { </button> )} {isInstalled && ( - <button className="button-red" disabled={installInProgress}> + <button + className="button-red" + disabled={installInProgress} + onClick={this.props.onUninstall}> {translate('marketplace.uninstall')} </button> )} diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/EditionsStatusNotif.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/EditionsStatusNotif.tsx index 312f0360afd..000eb5fb85e 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/components/EditionsStatusNotif.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/components/EditionsStatusNotif.tsx @@ -37,35 +37,37 @@ export default class EditionsStatusNotif extends React.PureComponent<Props, Stat hanleCloseRestart = () => this.setState({ openRestart: false }); render() { - const { editionStatus } = this.props; - if (editionStatus.installationStatus === 'AUTOMATIC_IN_PROGRESS') { - return ( - <div className="alert alert-info"> - <i className="spinner spacer-right text-bottom" /> - <span>{translate('marketplace.status.AUTOMATIC_IN_PROGRESS')}</span> - </div> - ); - } else if (editionStatus.installationStatus === 'AUTOMATIC_READY') { - return ( - <div className="alert alert-success"> - <span>{translate('marketplace.status.AUTOMATIC_READY')}</span> - <button className="js-restart spacer-left" onClick={this.handleOpenRestart}> - {translate('marketplace.restart')} - </button> - {this.state.openRestart && <RestartForm onClose={this.hanleCloseRestart} />} - </div> - ); - } else if ( - ['MANUAL_IN_PROGRESS', 'AUTOMATIC_FAILURE'].includes(editionStatus.installationStatus) - ) { - return ( - <div className="alert alert-danger"> - {translate('marketplace.status', editionStatus.installationStatus)} - <a className="little-spacer-left" href="https://www.sonarsource.com" target="_blank"> - {translate('marketplace.how_to_install')} - </a> - </div> - ); + const { installationStatus } = this.props.editionStatus; + + switch (installationStatus) { + case 'AUTOMATIC_IN_PROGRESS': + return ( + <div className="alert alert-info"> + <i className="spinner spacer-right text-bottom" /> + <span>{translate('marketplace.status.AUTOMATIC_IN_PROGRESS')}</span> + </div> + ); + case 'AUTOMATIC_READY': + case 'UNINSTALL_IN_PROGRESS': + return ( + <div className="alert alert-success"> + <span>{translate('marketplace.status', installationStatus)}</span> + <button className="js-restart spacer-left" onClick={this.handleOpenRestart}> + {translate('marketplace.restart')} + </button> + {this.state.openRestart && <RestartForm onClose={this.hanleCloseRestart} />} + </div> + ); + case 'MANUAL_IN_PROGRESS': + case 'AUTOMATIC_FAILURE': + return ( + <div className="alert alert-danger"> + {translate('marketplace.status', installationStatus)} + <a className="little-spacer-left" href="https://www.sonarsource.com" target="_blank"> + {translate('marketplace.how_to_install')} + </a> + </div> + ); } return null; } diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/UninstallEditionForm.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/UninstallEditionForm.tsx new file mode 100644 index 00000000000..705d02f3907 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/marketplace/components/UninstallEditionForm.tsx @@ -0,0 +1,103 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import Modal from 'react-modal'; +import { Edition, EditionStatus, uninstallEdition } from '../../../api/marketplace'; +import { translate, translateWithParameters } from '../../../helpers/l10n'; + +export interface Props { + edition?: Edition; + editionStatus: EditionStatus; + onClose: () => void; + updateEditionStatus: (editionStatus: EditionStatus) => void; +} + +interface State { + loading: boolean; +} + +export default class UninstallEditionForm extends React.PureComponent<Props, State> { + mounted: boolean; + state: State = { loading: false }; + + componentDidMount() { + this.mounted = true; + } + + componentWillUnmount() { + this.mounted = false; + } + + handleCancelClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { + event.preventDefault(); + this.props.onClose(); + }; + + handleConfirmClick = (event: React.SyntheticEvent<HTMLButtonElement>) => { + event.preventDefault(); + this.setState({ loading: true }); + uninstallEdition() + .then(() => { + this.props.updateEditionStatus({ + ...this.props.editionStatus, + installationStatus: 'UNINSTALL_IN_PROGRESS' + }); + this.props.onClose(); + }) + .catch(() => { + if (this.mounted) { + this.setState({ loading: false }); + } + }); + }; + + render() { + const { edition } = this.props; + const { loading } = this.state; + const currentEdition = edition ? edition.name : translate('marketplace.commercial_edition'); + const header = translateWithParameters('marketplace.uninstall_x', currentEdition); + return ( + <Modal + isOpen={true} + contentLabel={header} + className="modal" + overlayClassName="modal-overlay" + onRequestClose={this.props.onClose}> + <header className="modal-head"> + <h2>{header}</h2> + </header> + + <div className="modal-body"> + <p>{translateWithParameters('marketplace.uninstall_x_confirmation', currentEdition)}</p> + </div> + + <footer className="modal-foot"> + {loading && <i className="spinner spacer-right" />} + <button className="button-red" disabled={loading} onClick={this.handleConfirmClick}> + {translate('marketplace.uninstall')} + </button> + <a className="js-modal-close" href="#" onClick={this.handleCancelClick}> + {translate('cancel')} + </a> + </footer> + </Modal> + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/EditionBox-test.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/EditionBox-test.tsx index c1a000d33ad..0eb4f92f95d 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/EditionBox-test.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/EditionBox-test.tsx @@ -116,6 +116,7 @@ function getWrapper(props = {}) { edition={DEFAULT_EDITION} editionStatus={DEFAULT_STATUS} onInstall={jest.fn()} + onUninstall={jest.fn()} {...props} /> ); diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/UninstallEditionForm-test.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/UninstallEditionForm-test.tsx new file mode 100644 index 00000000000..037c2d33cd2 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/UninstallEditionForm-test.tsx @@ -0,0 +1,71 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import { click } from '../../../../helpers/testUtils'; +import UninstallEditionForm from '../UninstallEditionForm'; + +jest.mock('../../../../api/marketplace', () => ({ + uninstallEdition: jest.fn(() => Promise.resolve()) +})); + +const uninstallEdition = require('../../../../api/marketplace').uninstallEdition as jest.Mock<any>; + +const DEFAULT_EDITION = { + key: 'foo', + name: 'Foo', + desc: 'Foo desc', + download_link: 'download_url', + more_link: 'more_url', + request_license_link: 'license_url' +}; + +beforeEach(() => { + uninstallEdition.mockClear(); +}); + +it('should display correctly', () => { + expect(getWrapper()).toMatchSnapshot(); +}); + +it('should update the edition status after uninstall', async () => { + const updateEditionStatus = jest.fn(); + const wrapper = getWrapper({ updateEditionStatus }); + (wrapper.instance() as UninstallEditionForm).mounted = true; + click(wrapper.find('button')); + expect(uninstallEdition).toHaveBeenCalled(); + await new Promise(setImmediate); + expect(updateEditionStatus).toHaveBeenCalledWith({ + currentEditionKey: 'foo', + installationStatus: 'UNINSTALL_IN_PROGRESS' + }); +}); + +function getWrapper(props = {}) { + return shallow( + <UninstallEditionForm + edition={DEFAULT_EDITION} + editionStatus={{ currentEditionKey: 'foo', installationStatus: 'NONE' }} + onClose={jest.fn()} + updateEditionStatus={jest.fn()} + {...props} + /> + ); +} diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/EditionBox-test.tsx.snap b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/EditionBox-test.tsx.snap index 6aabd158c3a..7b8a8eaa902 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/EditionBox-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/EditionBox-test.tsx.snap @@ -107,6 +107,7 @@ exports[`should disable uninstall button 1`] = ` <button className="button-red" disabled={true} + onClick={[Function]} > marketplace.uninstall </button> @@ -149,6 +150,7 @@ exports[`should display installed badge 1`] = ` <button className="button-red" disabled={false} + onClick={[Function]} > marketplace.uninstall </button> @@ -187,6 +189,7 @@ exports[`should display installing badge 1`] = ` <button className="button-red" disabled={true} + onClick={[Function]} > marketplace.uninstall </button> diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/UninstallEditionForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/UninstallEditionForm-test.tsx.snap new file mode 100644 index 00000000000..507f7e4fe73 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/UninstallEditionForm-test.tsx.snap @@ -0,0 +1,50 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should display correctly 1`] = ` +<Modal + ariaHideApp={true} + bodyOpenClassName="ReactModal__Body--open" + className="modal" + closeTimeoutMS={0} + contentLabel="marketplace.uninstall_x.Foo" + isOpen={true} + onRequestClose={[Function]} + overlayClassName="modal-overlay" + parentSelector={[Function]} + portalClassName="ReactModalPortal" + shouldCloseOnOverlayClick={true} +> + <header + className="modal-head" + > + <h2> + marketplace.uninstall_x.Foo + </h2> + </header> + <div + className="modal-body" + > + <p> + marketplace.uninstall_x_confirmation.Foo + </p> + </div> + <footer + className="modal-foot" + > + <button + className="button-red" + disabled={false} + onClick={[Function]} + > + marketplace.uninstall + </button> + <a + className="js-modal-close" + href="#" + onClick={[Function]} + > + cancel + </a> + </footer> +</Modal> +`; |