aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/sonar-web/src/main/js/api/marketplace.ts15
-rw-r--r--server/sonar-web/src/main/js/app/utils/exposeLibraries.js2
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/EditionBoxes.tsx14
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/__tests__/EditionBoxes-test.tsx44
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/EditionBoxes-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/EditionBox.tsx9
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/LicenseEditionForm.tsx117
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/LicenseEditionSet.tsx142
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/__tests__/EditionBox-test.tsx1
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/__tests__/LicenseEditionForm-test.tsx79
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/__tests__/LicenseEditionSet-test.tsx81
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/EditionBox-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/LicenseEditionForm-test.tsx.snap67
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/LicenseEditionSet-test.tsx.snap76
-rw-r--r--server/sonar-web/src/main/less/init/misc.less4
15 files changed, 634 insertions, 21 deletions
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<Editions> {
.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<EditionStatus> {
+ 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<Props, State> {
@@ -67,8 +69,11 @@ export default class EditionBoxes extends React.PureComponent<Props, State> {
);
};
+ 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<Props, State> {
editionKey={key}
editionStatus={this.props.editionStatus}
key={key}
+ onInstall={this.handleOpenLicenseForm}
/>
))
)}
+
+ {installEdition && (
+ <LicenseEditionForm edition={installEdition} onClose={this.handleCloseLicenseForm} />
+ )}
</div>
);
}
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(
<EditionBoxes editionStatus={DEFAULT_STATUS} updateCenterActive={true} {...props} />
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]}
/>
<EditionBox
edition={
@@ -68,6 +69,7 @@ exports[`should display the edition boxes 2`] = `
"nextEditionKey": "",
}
}
+ onInstall={[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 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<Props> {
+ 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<Props> {
{translate('marketplace.learn_more')}
</a>
{!isInstalled && (
- <button disabled={installInProgress}>{translate('marketplace.install')}</button>
+ <button disabled={installInProgress} onClick={this.handleInstall}>
+ {translate('marketplace.install')}
+ </button>
)}
{isInstalled && (
<button className="button-red" disabled={installInProgress}>
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/LicenseEditionForm.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/LicenseEditionForm.tsx
new file mode 100644
index 00000000000..1f00178f113
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/marketplace/components/LicenseEditionForm.tsx
@@ -0,0 +1,117 @@
+/*
+ * 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 LicenseEditionSet from './LicenseEditionSet';
+import getStore from '../../../app/utils/getStore';
+import { setEditionStatus } from '../../../store/appState/duck';
+import { Edition, applyLicense } from '../../../api/marketplace';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
+
+export interface Props {
+ edition: Edition;
+ onClose: () => void;
+}
+
+interface State {
+ license: string;
+ loading: boolean;
+ status?: string;
+}
+
+export default class LicenseEditionForm extends React.PureComponent<Props, State> {
+ mounted: boolean;
+ state: State = { license: '', loading: false };
+
+ componentDidMount() {
+ this.mounted = true;
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ handleLicenseChange = (license: string, status?: string) => {
+ if (this.mounted) {
+ this.setState({ license, status });
+ }
+ };
+
+ handleCancelClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
+ event.preventDefault();
+ this.props.onClose();
+ };
+
+ handleConfirmClick = (event: React.SyntheticEvent<HTMLButtonElement>) => {
+ event.preventDefault();
+ const { license, status } = this.state;
+ if (license && status && ['AUTOMATIC_INSTALL', 'NO_INSTALL'].includes(status)) {
+ this.setState({ loading: true });
+ applyLicense({ license }).then(
+ editionStatus => {
+ getStore().dispatch(setEditionStatus(editionStatus));
+ this.props.onClose();
+ },
+ () => {
+ if (this.mounted) {
+ this.setState({ loading: false });
+ }
+ }
+ );
+ }
+ };
+
+ render() {
+ const { edition } = this.props;
+ const { status } = this.state;
+ const header = translateWithParameters('marketplace.install_x', edition.name);
+ return (
+ <Modal
+ isOpen={true}
+ contentLabel={header}
+ className="modal"
+ overlayClassName="modal-overlay"
+ onRequestClose={this.props.onClose}>
+ <header className="modal-head">
+ <h2>{header}</h2>
+ </header>
+
+ <LicenseEditionSet
+ className="modal-body"
+ edition={edition}
+ updateLicense={this.handleLicenseChange}
+ />
+
+ <footer className="modal-foot">
+ {this.state.loading && <i className="spinner spacer-right" />}
+ {status &&
+ ['NO_INSTALL', 'AUTOMATIC_INSTALL'].includes(status) && (
+ <button className="js-confirm" onClick={this.handleConfirmClick}>
+ {status === 'NO_INSTALL' ? translate('save') : translate('marketplace.install')}
+ </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/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<Props, State> {
+ 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<HTMLTextAreaElement>) => {
+ 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 (
+ <div className={className}>
+ <label htmlFor="set-license">
+ {translateWithParameters('marketplace.enter_license_for_x', edition.name)}
+ <em className="mandatory">*</em>
+ </label>
+ <textarea
+ autoFocus={true}
+ id="set-license"
+ className="spacer-top display-block"
+ cols={62}
+ onChange={this.handleLicenseChange}
+ required={true}
+ rows={6}
+ value={license}
+ />
+ {previewStatus && (
+ <p
+ className={classNames('alert spacer-top', {
+ 'alert-warning': previewStatus === 'AUTOMATIC_INSTALL',
+ 'alert-success': previewStatus === 'NO_INSTALL',
+ 'alert-danger': previewStatus === 'MANUAL_INSTALL'
+ })}>
+ {translateWithParameters(
+ 'marketplace.license_preview_status.' + previewStatus,
+ edition.name
+ )}
+ {previewStatus === 'MANUAL_INSTALL' && (
+ <p className="spacer-top">
+ <a
+ className="button"
+ download={`sonarqube-${edition.name}.zip`}
+ href={edition.download_link}
+ target="_blank">
+ {translate('marketplace.download_package')}
+ </a>
+ <a
+ className="spacer-left"
+ href="https://redirect.sonarsource.com/doc/how-to-install-an-edition.html"
+ target="_blank">
+ {translate('marketplace.how_to_install')}
+ </a>
+ </p>
+ )}
+ </p>
+ )}
+ <a
+ className="display-inline-block spacer-top"
+ href={edition.request_license_link}
+ target="_blank">
+ {translate('marketplace.i_need_a_license')}
+ </a>
+ </div>
+ );
+ }
+}
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 ebb42f27489..e0faa1bc3a3 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
@@ -94,6 +94,7 @@ function getWrapper(props = {}) {
edition={DEFAULT_EDITION}
editionKey="foo"
editionStatus={DEFAULT_STATUS}
+ onInstall={jest.fn()}
{...props}
/>
);
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/LicenseEditionForm-test.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/LicenseEditionForm-test.tsx
new file mode 100644
index 00000000000..850d9afb411
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/LicenseEditionForm-test.tsx
@@ -0,0 +1,79 @@
+/*
+ * 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 LicenseEditionForm from '../LicenseEditionForm';
+
+jest.mock('../../../../app/utils/getStore', () => {
+ const dispatch = jest.fn();
+ return { default: () => ({ dispatch }) };
+});
+jest.mock('../../../../api/marketplace', () => ({
+ applyLicense: jest.fn(() =>
+ Promise.resolve({ nextEditionKey: 'foo', installationStatus: 'AUTOMATIC_IN_PROGRESS' })
+ )
+}));
+
+const applyLicense = require('../../../../api/marketplace').applyLicense as jest.Mock<any>;
+const getStore = require('../../../../app/utils/getStore').default as jest.Mock<any>;
+
+const DEFAULT_EDITION = {
+ name: 'Foo',
+ desc: 'Foo desc',
+ download_link: 'download_url',
+ more_link: 'more_url',
+ request_license_link: 'license_url'
+};
+
+beforeEach(() => {
+ applyLicense.mockClear();
+ getStore().dispatch.mockClear();
+});
+
+it('should display correctly', () => {
+ expect(getWrapper()).toMatchSnapshot();
+});
+
+it('should correctly change the button based on the status', () => {
+ const wrapper = getWrapper();
+ (wrapper.instance() as LicenseEditionForm).mounted = true;
+ wrapper.setState({ status: 'NO_INSTALL' });
+ expect(wrapper.find('button')).toMatchSnapshot();
+ wrapper.setState({ status: 'AUTOMATIC_INSTALL' });
+ expect(wrapper.find('button')).toMatchSnapshot();
+ wrapper.setState({ status: 'MANUAL_INSTALL' });
+ expect(wrapper.find('button').exists()).toBeFalsy();
+});
+
+it('should update the edition status after install', async () => {
+ const wrapper = getWrapper();
+ const form = wrapper.instance() as LicenseEditionForm;
+ form.mounted = true;
+ form.handleLicenseChange('mylicense', 'AUTOMATIC_INSTALL');
+ click(wrapper.find('button'));
+ expect(applyLicense).toHaveBeenCalledWith({ license: 'mylicense' });
+ await new Promise(setImmediate);
+ expect(getStore().dispatch).toHaveBeenCalled();
+});
+
+function getWrapper(props = {}) {
+ return shallow(<LicenseEditionForm edition={DEFAULT_EDITION} onClose={jest.fn()} {...props} />);
+}
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/LicenseEditionSet-test.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/LicenseEditionSet-test.tsx
new file mode 100644
index 00000000000..49c97b43027
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/LicenseEditionSet-test.tsx
@@ -0,0 +1,81 @@
+/*
+ * 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 { change } from '../../../../helpers/testUtils';
+import LicenseEditionSet from '../LicenseEditionSet';
+
+jest.mock('../../../../api/marketplace', () => ({
+ getLicensePreview: jest.fn(() =>
+ Promise.resolve({ nextEditionKey: 'foo', previewStatus: 'NO_INSTALL' })
+ )
+}));
+
+jest.mock('lodash', () => {
+ const lodash = require.requireActual('lodash');
+ lodash.debounce = (fn: Function) => (...args: any[]) => fn(args);
+ return lodash;
+});
+
+const getLicensePreview = require('../../../../api/marketplace').getLicensePreview as jest.Mock<
+ any
+>;
+
+const DEFAULT_EDITION = {
+ name: 'Foo',
+ desc: 'Foo desc',
+ download_link: 'download_url',
+ more_link: 'more_url',
+ request_license_link: 'license_url'
+};
+
+beforeEach(() => {
+ getLicensePreview.mockClear();
+});
+
+it('should display correctly', () => {
+ expect(getWrapper()).toMatchSnapshot();
+});
+
+it('should correctly display status message after checking license', async () => {
+ await testLicenseStatus('NO_INSTALL');
+ await testLicenseStatus('AUTOMATIC_INSTALL');
+ await testLicenseStatus('MANUAL_INSTALL');
+});
+
+function getWrapper(props = {}) {
+ return shallow(
+ <LicenseEditionSet edition={DEFAULT_EDITION} updateLicense={jest.fn()} {...props} />
+ );
+}
+
+async function testLicenseStatus(status: string) {
+ getLicensePreview.mockImplementation(() =>
+ Promise.resolve({ nextEditionKey: 'foo', previewStatus: status })
+ );
+ const updateLicense = jest.fn();
+ const wrapper = getWrapper({ updateLicense });
+ (wrapper.instance() as LicenseEditionSet).mounted = true;
+ change(wrapper.find('textarea'), 'mylicense');
+ expect(getLicensePreview).toHaveBeenCalled();
+ await new Promise(setImmediate);
+ expect(updateLicense).toHaveBeenCalled();
+ expect(wrapper.find('p.alert')).toMatchSnapshot();
+}
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 6814875f5ba..e37bd585c1b 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
@@ -72,6 +72,7 @@ exports[`should disable uninstall button 1`] = `
</a>
<button
disabled={true}
+ onClick={[Function]}
>
marketplace.install
</button>
@@ -184,6 +185,7 @@ exports[`should display the edition 1`] = `
</a>
<button
disabled={false}
+ onClick={[Function]}
>
marketplace.install
</button>
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/LicenseEditionForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/LicenseEditionForm-test.tsx.snap
new file mode 100644
index 00000000000..492f821f630
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/LicenseEditionForm-test.tsx.snap
@@ -0,0 +1,67 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should correctly change the button based on the status 1`] = `
+<button
+ className="js-confirm"
+ onClick={[Function]}
+>
+ save
+</button>
+`;
+
+exports[`should correctly change the button based on the status 2`] = `
+<button
+ className="js-confirm"
+ onClick={[Function]}
+>
+ marketplace.install
+</button>
+`;
+
+exports[`should display correctly 1`] = `
+<Modal
+ ariaHideApp={true}
+ bodyOpenClassName="ReactModal__Body--open"
+ className="modal"
+ closeTimeoutMS={0}
+ contentLabel="marketplace.install_x.Foo"
+ isOpen={true}
+ onRequestClose={[Function]}
+ overlayClassName="modal-overlay"
+ parentSelector={[Function]}
+ portalClassName="ReactModalPortal"
+ shouldCloseOnOverlayClick={true}
+>
+ <header
+ className="modal-head"
+ >
+ <h2>
+ marketplace.install_x.Foo
+ </h2>
+ </header>
+ <LicenseEditionSet
+ className="modal-body"
+ edition={
+ Object {
+ "desc": "Foo desc",
+ "download_link": "download_url",
+ "more_link": "more_url",
+ "name": "Foo",
+ "request_license_link": "license_url",
+ }
+ }
+ updateLicense={[Function]}
+ />
+ <footer
+ className="modal-foot"
+ >
+ <a
+ className="js-modal-close"
+ href="#"
+ onClick={[Function]}
+ >
+ cancel
+ </a>
+ </footer>
+</Modal>
+`;
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/LicenseEditionSet-test.tsx.snap b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/LicenseEditionSet-test.tsx.snap
new file mode 100644
index 00000000000..9b89e852330
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/LicenseEditionSet-test.tsx.snap
@@ -0,0 +1,76 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should correctly display status message after checking license 1`] = `
+<p
+ className="alert spacer-top alert-success"
+>
+ marketplace.license_preview_status.NO_INSTALL.Foo
+</p>
+`;
+
+exports[`should correctly display status message after checking license 2`] = `
+<p
+ className="alert spacer-top alert-warning"
+>
+ marketplace.license_preview_status.AUTOMATIC_INSTALL.Foo
+</p>
+`;
+
+exports[`should correctly display status message after checking license 3`] = `
+<p
+ className="alert spacer-top alert-danger"
+>
+ marketplace.license_preview_status.MANUAL_INSTALL.Foo
+ <p
+ className="spacer-top"
+ >
+ <a
+ className="button"
+ download="sonarqube-Foo.zip"
+ href="download_url"
+ target="_blank"
+ >
+ marketplace.download_package
+ </a>
+ <a
+ className="spacer-left"
+ href="https://redirect.sonarsource.com/doc/how-to-install-an-edition.html"
+ target="_blank"
+ >
+ marketplace.how_to_install
+ </a>
+ </p>
+</p>
+`;
+
+exports[`should display correctly 1`] = `
+<div>
+ <label
+ htmlFor="set-license"
+ >
+ marketplace.enter_license_for_x.Foo
+ <em
+ className="mandatory"
+ >
+ *
+ </em>
+ </label>
+ <textarea
+ autoFocus={true}
+ className="spacer-top display-block"
+ cols={62}
+ id="set-license"
+ onChange={[Function]}
+ required={true}
+ rows={6}
+ value=""
+ />
+ <a
+ className="display-inline-block spacer-top"
+ href="license_url"
+ target="_blank"
+ >
+ marketplace.i_need_a_license
+ </a>
+</div>
+`;
diff --git a/server/sonar-web/src/main/less/init/misc.less b/server/sonar-web/src/main/less/init/misc.less
index cff3da4fdf0..b5f4e4f5ada 100644
--- a/server/sonar-web/src/main/less/init/misc.less
+++ b/server/sonar-web/src/main/less/init/misc.less
@@ -226,6 +226,10 @@ td.big-spacer-top {
pointer-events: none !important;
}
+.display-block {
+ display: block !important;
+}
+
.display-inline-block {
display: inline-block !important;
}