]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9937 Create a form to apply the license and install/update an edition
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>
Mon, 16 Oct 2017 15:18:19 +0000 (17:18 +0200)
committerGrégoire Aubert <gregoire.aubert@sonarsource.com>
Mon, 23 Oct 2017 15:01:13 +0000 (08:01 -0700)
16 files changed:
server/sonar-web/src/main/js/api/marketplace.ts
server/sonar-web/src/main/js/app/utils/exposeLibraries.js
server/sonar-web/src/main/js/apps/marketplace/EditionBoxes.tsx
server/sonar-web/src/main/js/apps/marketplace/__tests__/EditionBoxes-test.tsx
server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/EditionBoxes-test.tsx.snap
server/sonar-web/src/main/js/apps/marketplace/components/EditionBox.tsx
server/sonar-web/src/main/js/apps/marketplace/components/LicenseEditionForm.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/marketplace/components/LicenseEditionSet.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/marketplace/components/__tests__/EditionBox-test.tsx
server/sonar-web/src/main/js/apps/marketplace/components/__tests__/LicenseEditionForm-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/marketplace/components/__tests__/LicenseEditionSet-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/EditionBox-test.tsx.snap
server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/LicenseEditionForm-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/LicenseEditionSet-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/less/init/misc.less
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 282be5bc7b3494f2c1f0b8f94ecd56b5f10fec2a..17ef7d367a3445035bd5c5ab58b782bab0b4332b 100644 (file)
@@ -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);
+}
index b0d2a4fdd9f9d253df707adb9a1af73a6010e878..b1c1238c9a63db8515ae2a224a4527232b2638f8 100644 (file)
@@ -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,
index 5c74a1e8aa43566c3d031c4e75a8fa7eafe716ef..64290f13e9e754919cd4bbd90e7fe1b7532eeddc 100644 (file)
@@ -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>
     );
   }
index 49ee1abdcde14c9a87a80c0369c3ada791e30d7e..ca32326f225ce8ba2fbae065233baa43e3f3637c 100644 (file)
@@ -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} />
index 7ee4e6b73e9e73a0dc7336d63be63c5bdefdf92f..cfc6695810a6c25eb759824ea112d71edb6bf1f6 100644 (file)
@@ -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>
 `;
index 3299504fdad904e580008dc6e15fecab03aa6ae8..43dcd7ea577777fa6a394901391544a2fcfbc078 100644 (file)
@@ -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 (file)
index 0000000..1f00178
--- /dev/null
@@ -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 (file)
index 0000000..7b74d6b
--- /dev/null
@@ -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>
+    );
+  }
+}
index ebb42f274893939006ecc68f9900913a7be59719..e0faa1bc3a3a53c2d7c0618a1332d2e50e15faf3 100644 (file)
@@ -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 (file)
index 0000000..850d9af
--- /dev/null
@@ -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 (file)
index 0000000..49c97b4
--- /dev/null
@@ -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();
+}
index 6814875f5ba96d57f04ac6688dce80533bf8850c..e37bd585c1bd1f31644943046ae83c92a134905e 100644 (file)
@@ -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 (file)
index 0000000..492f821
--- /dev/null
@@ -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 (file)
index 0000000..9b89e85
--- /dev/null
@@ -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>
+`;
index cff3da4fdf067e748aa8a283a9e13d554eb90452..b5f4e4f5ada41fb1b6abeaf8c6670e9441d10405 100644 (file)
@@ -226,6 +226,10 @@ td.big-spacer-top {
   pointer-events: none !important;
 }
 
+.display-block {
+  display: block !important;
+}
+
 .display-inline-block {
   display: inline-block !important;
 }
index a91bb089669d44b26a370b3db4b5912599875c01..373b3504a627278e9325d3d85350a9d72a9363c0 100644 (file)
@@ -2068,6 +2068,7 @@ marketplace.restart=Restart
 marketplace.revert=Revert
 marketplace.system_upgrades=System Upgrades
 marketplace.install=Install
+marketplace.install_x=Install {0}
 marketplace.installed=Installed
 marketplace.installing=Installing...
 marketplace._installed=installed
@@ -2085,6 +2086,9 @@ marketplace.status.COMPATIBLE=Compatible
 marketplace.status.INCOMPATIBLE=Incompatible
 marketplace.status.REQUIRES_SYSTEM_UPGRADE=Requires system update
 marketplace.status.DEPS_REQUIRE_SYSTEM_UPGRADE=Some of dependencies requires system update
+marketplace.license_preview_status.NO_INSTALL=No installation needed, your license will be updated directly.
+marketplace.license_preview_status.AUTOMATIC_INSTALL=After clicking on "Install", your {0} will be automatically downloaded and installed, and you'll need to restart your server once it's completed.
+marketplace.license_preview_status.MANUAL_INSTALL=Can't install {0} because of internet access issue. Please manually install the package in your SonarQube's plugins folder.
 marketplace.installing_this_plugin_will_also_install_x=Installing this plugin will also install: {0}
 marketplace.update_to_x=Update to {0}
 marketplace.uninstall=Uninstall
@@ -2096,6 +2100,9 @@ marketplace.status.AUTOMATIC_READY=New installation complete. Please restart Ser
 marketplace.status.MANUAL_IN_PROGRESS=Can't install Developer Edition because of internet access issue. Please manually install the package in your SonarQube's plugins folder.
 marketplace.status.AUTOMATIC_FAILURE=Can't install Developer Edition. Please manually install the package in your SonarQube's plugins folder.
 marketplace.how_to_install=How to install it?
+marketplace.enter_license_for_x=Enter your license key for {0}
+marketplace.i_need_a_license=I need a license key
+marketplace.download_package=Download package
 
 #------------------------------------------------------------------------------
 #