]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9937 Add TOS checkbox when installing an edition in the marketplace
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>
Mon, 6 Nov 2017 11:09:19 +0000 (12:09 +0100)
committerGrégoire Aubert <gregoire.aubert@sonarsource.com>
Mon, 6 Nov 2017 15:18:50 +0000 (16:18 +0100)
server/sonar-web/src/main/js/apps/marketplace/components/LicenseEditionForm.tsx
server/sonar-web/src/main/js/apps/marketplace/components/LicenseEditionSet.tsx
server/sonar-web/src/main/js/apps/marketplace/components/__tests__/LicenseEditionForm-test.tsx
server/sonar-web/src/main/js/apps/marketplace/components/__tests__/LicenseEditionSet-test.tsx
server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/LicenseEditionForm-test.tsx.snap
server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/LicenseEditionSet-test.tsx.snap

index abab7c84ed4a8858a6f6c9f1945936498702e502..778c63fb4575e550090528d345c7ceed546fd959 100644 (file)
@@ -81,7 +81,7 @@ export default class LicenseEditionForm extends React.PureComponent<Props, State
 
   render() {
     const { edition, isDowngrade } = this.props;
-    const { submitting, status } = this.state;
+    const { license, submitting, status } = this.state;
 
     const header = isDowngrade
       ? translateWithParameters('marketplace.downgrade_to_x', edition.name)
@@ -107,7 +107,10 @@ export default class LicenseEditionForm extends React.PureComponent<Props, State
         <footer className="modal-foot">
           {submitting && <i className="spinner spacer-right" />}
           {status && (
-            <button className="js-confirm" onClick={this.handleConfirmClick} disabled={submitting}>
+            <button
+              className="js-confirm"
+              onClick={this.handleConfirmClick}
+              disabled={!license || submitting}>
               {status === 'AUTOMATIC_INSTALL' ? (
                 translate('marketplace.install')
               ) : (
index edcd13a3d037f2b1ce3406e8b7d40f686205e60a..2053ea87e5099b1663d2b10b77106269680c7f47 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+import { stringify } from 'querystring';
 import * as React from 'react';
 import * as classNames from 'classnames';
+import Checkbox from '../../../components/controls/Checkbox';
 import { FormattedMessage } from 'react-intl';
-import { stringify } from 'querystring';
 import { debounce } from 'lodash';
 import DeferredSpinner from '../../../components/common/DeferredSpinner';
 import { omitNil } from '../../../helpers/request';
@@ -35,6 +36,7 @@ export interface Props {
 }
 
 interface State {
+  acceptTerms: boolean;
   license: string;
   licenseEdition?: Edition;
   loading: boolean;
@@ -50,7 +52,7 @@ export default class LicenseEditionSet extends React.PureComponent<Props, State>
 
   constructor(props: Props) {
     super(props);
-    this.state = { license: '', loading: false };
+    this.state = { acceptTerms: false, license: '', loading: false };
     this.fetchLicensePreview = debounce(this.fetchLicensePreview, 100);
   }
 
@@ -116,57 +118,93 @@ export default class LicenseEditionSet extends React.PureComponent<Props, State>
     }
   };
 
+  handleTermsCheck = (checked: boolean) =>
+    this.setState({ acceptTerms: checked }, () =>
+      this.updateLicense(this.state.license, this.state.licenseEdition, this.state.previewStatus)
+    );
+
   updateLicense = (license: string, licenseEdition?: Edition, previewStatus?: string) => {
     this.setState({ license, licenseEdition, loading: false, previewStatus });
-    this.props.updateLicense(license, previewStatus);
+    this.props.updateLicense(
+      previewStatus !== 'NO_INSTALL' && !this.state.acceptTerms ? undefined : license,
+      previewStatus
+    );
   };
 
   renderAlert() {
     const { licenseEdition, previewStatus } = this.state;
     if (!previewStatus) {
       const { edition } = this.props;
-      if (edition && licenseEdition && edition.key !== licenseEdition.key) {
-        return (
-          <p className="alert alert-danger spacer-top">
-            {translateWithParameters('marketplace.wrong_license_type_x', edition.name)}
-          </p>
-        );
+      if (!edition) {
+        return undefined;
       }
 
-      return undefined;
+      return (
+        <div className="spacer-top">
+          {licenseEdition !== undefined &&
+          edition.key !== licenseEdition.key && (
+            <p className="alert alert-danger">
+              {translateWithParameters('marketplace.wrong_license_type_x', edition.name)}
+            </p>
+          )}
+          <a href={this.getLicenseFormUrl(edition)} target="_blank">
+            {translate('marketplace.i_need_a_license')}
+          </a>
+        </div>
+      );
     }
 
     return (
-      <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,
-          licenseEdition ? licenseEdition.name : translate('marketplace.commercial_edition')
-        )}
-        {licenseEdition &&
-        licenseEdition.key === 'datacenter' &&
-        previewStatus !== 'NO_INSTALL' && (
-          <span className="little-spacer-left">
-            <FormattedMessage
-              defaultMessage={translate('marketplace.how_to_setup_cluster_url')}
-              id="marketplace.how_to_setup_cluster_url"
-              values={{
-                url: (
-                  <a
-                    href="https://redirect.sonarsource.com/doc/data-center-edition.html"
-                    target="_blank">
-                    {licenseEdition.name}
-                  </a>
-                )
-              }}
-            />
+      <div className="spacer-top">
+        <p
+          className={classNames('alert', {
+            'alert-warning': previewStatus === 'AUTOMATIC_INSTALL',
+            'alert-success': previewStatus === 'NO_INSTALL',
+            'alert-danger': previewStatus === 'MANUAL_INSTALL'
+          })}>
+          {translateWithParameters(
+            'marketplace.license_preview_status.' + previewStatus,
+            licenseEdition ? licenseEdition.name : translate('marketplace.commercial_edition')
+          )}
+          {licenseEdition &&
+          licenseEdition.key === 'datacenter' &&
+          previewStatus !== 'NO_INSTALL' && (
+            <span className="little-spacer-left">
+              <FormattedMessage
+                defaultMessage={translate('marketplace.how_to_setup_cluster_url')}
+                id="marketplace.how_to_setup_cluster_url"
+                values={{
+                  url: (
+                    <a
+                      href="https://redirect.sonarsource.com/doc/data-center-edition.html"
+                      target="_blank">
+                      {licenseEdition.name}
+                    </a>
+                  )
+                }}
+              />
+            </span>
+          )}
+        </p>
+        {previewStatus !== 'NO_INSTALL' && (
+          <span className="js-edition-tos">
+            <Checkbox
+              checked={this.state.acceptTerms}
+              id="edition-terms"
+              onCheck={this.handleTermsCheck}>
+              <label className="little-spacer-left" htmlFor="edition-terms">
+                {translate('marketplace.i_accept_the')}
+              </label>
+            </Checkbox>
+            <a
+              className="nowrap little-spacer-left"
+              href="http://dist.sonarsource.com/SonarSource_Terms_And_Conditions.pdf"
+              target="_blank">
+              {translate('marketplace.terms_and_conditions')}
+            </a>
           </span>
         )}
-      </p>
+      </div>
     );
   }
 
@@ -194,7 +232,6 @@ export default class LicenseEditionSet extends React.PureComponent<Props, State>
         />
 
         <DeferredSpinner
-          className="spacer-top"
           loading={loading}
           customSpinner={
             <p className="spacer-top">
@@ -204,15 +241,6 @@ export default class LicenseEditionSet extends React.PureComponent<Props, State>
           }>
           {this.renderAlert()}
         </DeferredSpinner>
-
-        {edition && (
-          <a
-            className="display-inline-block spacer-top"
-            href={this.getLicenseFormUrl(edition)}
-            target="_blank">
-            {translate('marketplace.i_need_a_license')}
-          </a>
-        )}
       </div>
     );
   }
index 745c8308a9b7dd0ff486297d10385078d102e2c7..7db7c0c0b5d135934ce5890869555e931ff878ae 100644 (file)
@@ -47,15 +47,28 @@ it('should display correctly', () => {
   expect(getWrapper()).toMatchSnapshot();
 });
 
-it('should correctly change the button based on the status', () => {
+it('should correctly change the button based on the status and license', () => {
   const wrapper = getWrapper();
+  let button;
   (wrapper.instance() as LicenseEditionForm).mounted = true;
-  wrapper.setState({ status: 'NO_INSTALL' });
-  expect(wrapper.find('button')).toMatchSnapshot();
+
+  wrapper.setState({ license: 'mylicense', status: 'NO_INSTALL' });
+  button = wrapper.find('button');
+  expect(button.text()).toBe('save');
+  expect(button.prop('disabled')).toBeFalsy();
+
+  wrapper.setState({ license: undefined, status: 'MANUAL_INSTALL' });
+  button = wrapper.find('button');
+  expect(button.text()).toBe('save');
+  expect(button.prop('disabled')).toBeTruthy();
+
   wrapper.setState({ status: 'AUTOMATIC_INSTALL' });
-  expect(wrapper.find('button')).toMatchSnapshot();
-  wrapper.setState({ status: 'MANUAL_INSTALL' });
-  expect(wrapper.find('button')).toMatchSnapshot();
+  button = wrapper.find('button');
+  expect(button.text()).toContain('install');
+  expect(button.prop('disabled')).toBeTruthy();
+
+  wrapper.setState({ license: 'mylicense' });
+  expect(wrapper.find('button').prop('disabled')).toBeFalsy();
 });
 
 it('should update the edition status after install', async () => {
index ce68f4c2d3ea8fb9b03111ecf9b60ae0f222d935..b0be56873fd02c33ccd14793e7047dfcbeff6ecb 100644 (file)
@@ -30,7 +30,7 @@ jest.mock('../../../../api/marketplace', () => ({
 
 jest.mock('lodash', () => {
   const lodash = require.requireActual('lodash');
-  lodash.debounce = (fn: Function) => (...args: any[]) => fn(args);
+  lodash.debounce = (fn: Function) => (...args: any[]) => fn(...args);
   return lodash;
 });
 
@@ -56,9 +56,32 @@ it('should display correctly', () => {
 });
 
 it('should correctly display status message after checking license', async () => {
-  await testLicenseStatus('NO_INSTALL');
-  await testLicenseStatus('AUTOMATIC_INSTALL');
-  await testLicenseStatus('MANUAL_INSTALL');
+  let wrapper = await testLicenseStatus('NO_INSTALL', jest.fn());
+  expect(wrapper.find('p.alert')).toMatchSnapshot();
+  wrapper = await testLicenseStatus('AUTOMATIC_INSTALL', jest.fn());
+  expect(wrapper.find('p.alert')).toMatchSnapshot();
+  wrapper = await testLicenseStatus('MANUAL_INSTALL', jest.fn());
+  expect(wrapper.find('p.alert')).toMatchSnapshot();
+});
+
+it('should display terms of license checkbox', async () => {
+  let updateLicense = jest.fn();
+  let wrapper = await testLicenseStatus('NO_INSTALL', updateLicense);
+  expect(wrapper.find('.js-edition-tos').exists()).toBeFalsy();
+  expect(updateLicense).toHaveBeenCalledWith('mylicense', 'NO_INSTALL');
+
+  updateLicense = jest.fn();
+  wrapper = await testLicenseStatus('AUTOMATIC_INSTALL', updateLicense);
+  let tosCheckbox = wrapper.find('.js-edition-tos');
+  expect(tosCheckbox.find('a').exists()).toBeTruthy();
+  expect(updateLicense).toHaveBeenLastCalledWith(undefined, 'AUTOMATIC_INSTALL');
+  (tosCheckbox.find('Checkbox').prop('onCheck') as Function)(true);
+  expect(updateLicense).toHaveBeenLastCalledWith('mylicense', 'AUTOMATIC_INSTALL');
+
+  updateLicense = jest.fn();
+  wrapper = await testLicenseStatus('MANUAL_INSTALL', updateLicense);
+  expect(wrapper.find('.js-edition-tos').exists()).toBeTruthy();
+  expect(updateLicense).toHaveBeenLastCalledWith(undefined, 'MANUAL_INSTALL');
 });
 
 function getWrapper(props = {}) {
@@ -72,16 +95,15 @@ function getWrapper(props = {}) {
   );
 }
 
-async function testLicenseStatus(status: string) {
+async function testLicenseStatus(status: string, updateLicense: jest.Mock<any>) {
   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();
+  return wrapper;
 }
index d178e5134f41258454ee2087e23cc3164773f4d7..49d105b88f590330b46576d2e03779b9829c9928 100644 (file)
@@ -1,35 +1,5 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`should correctly change the button based on the status 1`] = `
-<button
-  className="js-confirm"
-  disabled={false}
-  onClick={[Function]}
->
-  save
-</button>
-`;
-
-exports[`should correctly change the button based on the status 2`] = `
-<button
-  className="js-confirm"
-  disabled={false}
-  onClick={[Function]}
->
-  marketplace.install
-</button>
-`;
-
-exports[`should correctly change the button based on the status 3`] = `
-<button
-  className="js-confirm"
-  disabled={false}
-  onClick={[Function]}
->
-  save
-</button>
-`;
-
 exports[`should display correctly 1`] = `
 <Modal
   ariaHideApp={true}
index b990a23a26c4d1fac8e651c21bf9ccb58e429438..19750aefb8bbcc60f13af6f55bd10a647ac95463 100644 (file)
@@ -2,7 +2,7 @@
 
 exports[`should correctly display status message after checking license 1`] = `
 <p
-  className="alert spacer-top alert-success"
+  className="alert alert-success"
 >
   marketplace.license_preview_status.NO_INSTALL.Foo
 </p>
@@ -10,7 +10,7 @@ exports[`should correctly display status message after checking license 1`] = `
 
 exports[`should correctly display status message after checking license 2`] = `
 <p
-  className="alert spacer-top alert-warning"
+  className="alert alert-warning"
 >
   marketplace.license_preview_status.AUTOMATIC_INSTALL.Foo
 </p>
@@ -18,7 +18,7 @@ exports[`should correctly display status message after checking license 2`] = `
 
 exports[`should correctly display status message after checking license 3`] = `
 <p
-  className="alert spacer-top alert-danger"
+  className="alert alert-danger"
 >
   marketplace.license_preview_status.MANUAL_INSTALL.Foo
 </p>
@@ -52,7 +52,6 @@ exports[`should display correctly 1`] = `
     value=""
   />
   <DeferredSpinner
-    className="spacer-top"
     customSpinner={
       <p
         className="spacer-top"
@@ -65,13 +64,17 @@ exports[`should display correctly 1`] = `
     }
     loading={false}
     timeout={100}
-  />
-  <a
-    className="display-inline-block spacer-top"
-    href="license_url"
-    target="_blank"
   >
-    marketplace.i_need_a_license
-  </a>
+    <div
+      className="spacer-top"
+    >
+      <a
+        href="license_url"
+        target="_blank"
+      >
+        marketplace.i_need_a_license
+      </a>
+    </div>
+  </DeferredSpinner>
 </div>
 `;