From: Grégoire Aubert Date: Mon, 16 Oct 2017 15:18:19 +0000 (+0200) Subject: SONAR-9937 Create a form to apply the license and install/update an edition X-Git-Tag: 6.7-RC1~96 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=4b9c83214cb871d3f73d083411278266327c8cd4;p=sonarqube.git SONAR-9937 Create a form to apply the license and install/update an edition --- diff --git a/server/sonar-web/src/main/js/api/marketplace.ts b/server/sonar-web/src/main/js/api/marketplace.ts index 282be5bc7b3..17ef7d367a3 100644 --- a/server/sonar-web/src/main/js/api/marketplace.ts +++ b/server/sonar-web/src/main/js/api/marketplace.ts @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { checkStatus, corsRequest, getJSON, parseJSON } from '../helpers/request'; +import { checkStatus, corsRequest, getJSON, parseJSON, postJSON } from '../helpers/request'; import throwGlobalError from '../app/utils/throwGlobalError'; export interface Edition { @@ -56,3 +56,16 @@ export function getEditionsList(): Promise { .then(checkStatus) .then(parseJSON); } + +export function getLicensePreview(data: { + license: string; +}): Promise<{ + nextEditionKey: string; + previewStatus: 'NO_INSTALL' | 'AUTOMATIC_INSTALL' | 'MANUAL_INSTALL'; +}> { + return postJSON('/api/editions/preview', data).catch(throwGlobalError); +} + +export function applyLicense(data: { license: string }): Promise { + return postJSON('/api/editions/apply_license', data).catch(throwGlobalError); +} diff --git a/server/sonar-web/src/main/js/app/utils/exposeLibraries.js b/server/sonar-web/src/main/js/app/utils/exposeLibraries.js index b0d2a4fdd9f..b1c1238c9a6 100644 --- a/server/sonar-web/src/main/js/app/utils/exposeLibraries.js +++ b/server/sonar-web/src/main/js/app/utils/exposeLibraries.js @@ -29,6 +29,7 @@ import DateFromNow from '../../components/intl/DateFromNow'; import DateFormatter from '../../components/intl/DateFormatter'; import DateTimeFormatter from '../../components/intl/DateTimeFormatter'; import FavoriteContainer from '../../components/controls/FavoriteContainer'; +import LicenseEditionSet from '../../apps/marketplace/components/LicenseEditionSet'; import ListFooter from '../../components/controls/ListFooter'; import Tooltip from '../../components/controls/Tooltip'; import ModalForm from '../../components/common/modal-form'; @@ -48,6 +49,7 @@ const exposeLibraries = () => { DateFormatter, DateTimeFormatter, FavoriteContainer, + LicenseEditionSet, ListFooter, Modal, Tooltip, 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 5c74a1e8aa4..64290f13e9e 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/EditionBoxes.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/EditionBoxes.tsx @@ -20,7 +20,8 @@ import * as React from 'react'; import { FormattedMessage } from 'react-intl'; import EditionBox from './components/EditionBox'; -import { Editions, EditionStatus, getEditionsList } from '../../api/marketplace'; +import LicenseEditionForm from './components/LicenseEditionForm'; +import { Edition, Editions, EditionStatus, getEditionsList } from '../../api/marketplace'; import { translate } from '../../helpers/l10n'; export interface Props { @@ -32,6 +33,7 @@ interface State { editions: Editions; editionsError: boolean; loading: boolean; + installEdition?: Edition; } export default class EditionBoxes extends React.PureComponent { @@ -67,8 +69,11 @@ export default class EditionBoxes extends React.PureComponent { ); }; + handleOpenLicenseForm = (edition: Edition) => this.setState({ installEdition: edition }); + handleCloseLicenseForm = () => this.setState({ installEdition: undefined }); + render() { - const { editions, loading } = this.state; + const { editions, loading, installEdition } = this.state; if (loading) { return null; } @@ -95,9 +100,14 @@ export default class EditionBoxes extends React.PureComponent { editionKey={key} editionStatus={this.props.editionStatus} key={key} + onInstall={this.handleOpenLicenseForm} /> )) )} + + {installEdition && ( + + )} ); } diff --git a/server/sonar-web/src/main/js/apps/marketplace/__tests__/EditionBoxes-test.tsx b/server/sonar-web/src/main/js/apps/marketplace/__tests__/EditionBoxes-test.tsx index 49ee1abdcde..ca32326f225 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/__tests__/EditionBoxes-test.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/__tests__/EditionBoxes-test.tsx @@ -28,26 +28,28 @@ const DEFAULT_STATUS: EditionStatus = { installationStatus: 'NONE' }; +const DEFAULT_EDITIONS = { + foo: { + name: 'Foo', + desc: 'Foo desc', + download_link: 'download_url', + more_link: 'more_url', + request_license_link: 'license_url' + }, + bar: { + name: 'Bar', + desc: 'Bar desc', + download_link: 'download_url', + more_link: 'more_url', + request_license_link: 'license_url' + } +}; + it('should display the edition boxes', () => { const wrapper = getWrapper(); expect(wrapper).toMatchSnapshot(); wrapper.setState({ - editions: { - foo: { - name: 'Foo', - desc: 'Foo desc', - download_link: 'download_url', - more_link: 'more_url', - request_license_link: 'license_url' - }, - bar: { - name: 'Bar', - desc: 'Bar desc', - download_link: 'download_url', - more_link: 'more_url', - request_license_link: 'license_url' - } - }, + editions: DEFAULT_EDITIONS, loading: false }); expect(wrapper).toMatchSnapshot(); @@ -59,6 +61,16 @@ it('should display an error message', () => { expect(wrapper).toMatchSnapshot(); }); +it('should open the license form', () => { + const wrapper = getWrapper(); + wrapper.setState({ + editions: DEFAULT_EDITIONS, + loading: false + }); + (wrapper.instance() as EditionBoxes).handleOpenLicenseForm(DEFAULT_EDITIONS.foo); + expect(wrapper.find('LicenseEditionForm').exists()).toBeTruthy(); +}); + function getWrapper(props = {}) { return shallow( 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 7ee4e6b73e9..cfc6695810a 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 @@ -49,6 +49,7 @@ exports[`should display the edition boxes 2`] = ` "nextEditionKey": "", } } + onInstall={[Function]} /> `; 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 3299504fdad..43dcd7ea577 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 @@ -22,13 +22,16 @@ import CheckIcon from '../../../components/icons-components/CheckIcon'; import { Edition, EditionStatus } from '../../../api/marketplace'; import { translate } from '../../../helpers/l10n'; -export interface Props { +interface Props { edition: Edition; editionKey: string; editionStatus?: EditionStatus; + onInstall: (edition: Edition) => void; } export default class EditionBox extends React.PureComponent { + handleInstall = () => this.props.onInstall(this.props.edition); + render() { const { edition, editionKey, editionStatus } = this.props; const isInstalled = editionStatus && editionStatus.currentEditionKey === editionKey; @@ -58,7 +61,9 @@ export default class EditionBox extends React.PureComponent { {translate('marketplace.learn_more')} {!isInstalled && ( - + )} {isInstalled && ( + )} + + {translate('cancel')} + + + + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/LicenseEditionSet.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/LicenseEditionSet.tsx new file mode 100644 index 00000000000..7b74d6b6bcf --- /dev/null +++ b/server/sonar-web/src/main/js/apps/marketplace/components/LicenseEditionSet.tsx @@ -0,0 +1,142 @@ +/* + * 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 * as classNames from 'classnames'; +import { debounce } from 'lodash'; +import { Edition, getLicensePreview } from '../../../api/marketplace'; +import { translate, translateWithParameters } from '../../../helpers/l10n'; + +export interface Props { + className?: string; + edition: Edition; + updateLicense: (license?: string, status?: string) => void; +} + +interface State { + license: string; + loading: boolean; + previewStatus?: string; +} + +export default class LicenseEditionSet extends React.PureComponent { + mounted: boolean; + + constructor(props: Props) { + super(props); + this.state = { license: '', loading: false }; + this.fetchLicensePreview = debounce(this.fetchLicensePreview, 250); + } + + componentDidMount() { + this.mounted = true; + } + + componentWillUnmount() { + this.mounted = false; + } + + fetchLicensePreview = (license: string) => + getLicensePreview({ license }).then( + r => { + if (this.mounted) { + this.updateLicense(license, r.previewStatus); + } + }, + () => { + if (this.mounted) { + this.updateLicense(license, undefined); + } + } + ); + + handleLicenseChange = (event: React.SyntheticEvent) => { + const license = event.currentTarget.value; + if (license) { + this.fetchLicensePreview(license); + this.setState({ license }); + } else { + this.updateLicense(license, undefined); + } + }; + + updateLicense = (license: string, previewStatus?: string) => { + this.setState({ license, previewStatus }); + this.props.updateLicense(license, previewStatus); + }; + + render() { + const { className, edition } = this.props; + const { license, previewStatus } = this.state; + return ( +
+ +