]> source.dussan.org Git - sonarqube.git/commitdiff
Display last available version editions in read only when current edition is not...
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>
Fri, 20 Oct 2017 13:45:41 +0000 (15:45 +0200)
committerGrégoire Aubert <gregoire.aubert@sonarsource.com>
Mon, 23 Oct 2017 15:01:13 +0000 (08:01 -0700)
14 files changed:
server/sonar-web/src/main/js/apps/marketplace/App.tsx
server/sonar-web/src/main/js/apps/marketplace/PluginsList.tsx
server/sonar-web/src/main/js/apps/marketplace/components/EditionBox.tsx
server/sonar-web/src/main/js/apps/marketplace/components/EditionsStatusNotif.tsx
server/sonar-web/src/main/js/apps/marketplace/components/LicenseEditionSet.tsx
server/sonar-web/src/main/js/apps/marketplace/components/PluginActions.tsx
server/sonar-web/src/main/js/apps/marketplace/components/UninstallEditionForm.tsx
server/sonar-web/src/main/js/apps/marketplace/components/__tests__/UninstallEditionForm-test.tsx
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__/EditionsStatusNotif-test.tsx.snap
server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/LicenseEditionSet-test.tsx.snap
server/sonar-web/src/main/js/apps/marketplace/utils.ts
server/sonar-web/src/main/less/components/alerts.less
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index c5012002c9569d7df77780a1d62c43d2e6a41344..c57f3de78fdda17432bd4a2b9be691607bbea306 100644 (file)
@@ -39,7 +39,14 @@ import {
 import { Edition, EditionStatus, getEditionsList, getEditionStatus } from '../../api/marketplace';
 import { RawQuery } from '../../helpers/query';
 import { translate } from '../../helpers/l10n';
-import { getEditionsForVersion, filterPlugins, parseQuery, Query, serializeQuery } from './utils';
+import {
+  getEditionsForLastVersion,
+  getEditionsForVersion,
+  filterPlugins,
+  parseQuery,
+  Query,
+  serializeQuery
+} from './utils';
 
 export interface Props {
   editionsUrl: string;
@@ -51,6 +58,7 @@ export interface Props {
 
 interface State {
   editions?: Edition[];
+  editionsReadOnly: boolean;
   editionStatus?: EditionStatus;
   loadingEditions: boolean;
   loadingPlugins: boolean;
@@ -73,6 +81,7 @@ export default class App extends React.PureComponent<Props, State> {
   constructor(props: Props) {
     super(props);
     this.state = {
+      editionsReadOnly: false,
       loadingEditions: true,
       loadingPlugins: true,
       pending: {
@@ -113,37 +122,26 @@ export default class App extends React.PureComponent<Props, State> {
 
   fetchAllPlugins = () => {
     this.setState({ loadingPlugins: true });
-    Promise.all([getInstalledPluginsWithUpdates(), getAvailablePlugins()]).then(
-      ([installed, available]) => {
-        if (this.mounted) {
-          this.setState({
-            loadingPlugins: false,
-            plugins: sortBy(uniqBy([...installed, ...available.plugins], 'key'), 'name')
-          });
-        }
-      },
-      () => {
-        if (this.mounted) {
-          this.setState({ loadingPlugins: false });
-        }
+    Promise.all([
+      getInstalledPluginsWithUpdates(),
+      getAvailablePlugins()
+    ]).then(([installed, available]) => {
+      if (this.mounted) {
+        this.setState({
+          loadingPlugins: false,
+          plugins: sortBy(uniqBy([...installed, ...available.plugins], 'key'), 'name')
+        });
       }
-    );
+    }, this.stopLoadingPlugins);
   };
 
   fetchUpdatesOnly = () => {
     this.setState({ loadingPlugins: true });
-    getPluginUpdates().then(
-      plugins => {
-        if (this.mounted) {
-          this.setState({ loadingPlugins: false, plugins });
-        }
-      },
-      () => {
-        if (this.mounted) {
-          this.setState({ loadingPlugins: false });
-        }
+    getPluginUpdates().then(plugins => {
+      if (this.mounted) {
+        this.setState({ loadingPlugins: false, plugins });
       }
-    );
+    }, this.stopLoadingPlugins);
   };
 
   fetchPendingPlugins = () =>
@@ -171,10 +169,16 @@ export default class App extends React.PureComponent<Props, State> {
     getEditionsList(this.props.editionsUrl).then(
       editionsPerVersion => {
         if (this.mounted) {
-          this.setState({
+          const newState = {
             editions: getEditionsForVersion(editionsPerVersion, this.props.sonarqubeVersion),
+            editionsReadOnly: false,
             loadingEditions: false
-          });
+          };
+          if (!newState.editions) {
+            newState.editions = getEditionsForLastVersion(editionsPerVersion);
+            newState.editionsReadOnly = true;
+          }
+          this.setState(newState);
         }
       },
       () => {
@@ -204,6 +208,12 @@ export default class App extends React.PureComponent<Props, State> {
     this.context.router.push({ pathname: this.props.location.pathname, query });
   };
 
+  stopLoadingPlugins = () => {
+    if (this.mounted) {
+      this.setState({ loadingPlugins: false });
+    }
+  };
+
   render() {
     const { standaloneMode } = this.props;
     const { editions, editionStatus, loadingPlugins, plugins, pending } = this.state;
@@ -222,7 +232,7 @@ export default class App extends React.PureComponent<Props, State> {
               updateEditionStatus={this.updateEditionStatus}
             />
           )}
-          {!standaloneMode && (
+          {standaloneMode && (
             <PendingActions refreshPending={this.fetchPendingPlugins} pending={pending} />
           )}
         </div>
@@ -232,7 +242,7 @@ export default class App extends React.PureComponent<Props, State> {
           loading={this.state.loadingEditions}
           editionStatus={editionStatus}
           editionsUrl={this.props.editionsUrl}
-          readOnly={!standaloneMode}
+          readOnly={!standaloneMode || this.state.editionsReadOnly}
           sonarqubeVersion={this.props.sonarqubeVersion}
           updateCenterActive={this.props.updateCenterActive}
           updateEditionStatus={this.updateEditionStatus}
index 4bc679d4d2a834d31f10d6ac37e55a03296b453f..79498c13948c45ce75f7845167411f653b0918fa 100644 (file)
@@ -74,6 +74,7 @@ export default class PluginsList extends React.PureComponent<Props> {
         />
       );
     }
+    return null;
   };
 
   render() {
index d637c7116ae190ef9b66bfa873c92dc4dfe8a476..27130a159a4997e3798dce6972763e448f2fd77d 100644 (file)
@@ -48,7 +48,7 @@ export default class EditionBox extends React.PureComponent<Props> {
     if (isInstalled) {
       return (
         <span className="marketplace-edition-badge badge badge-normal-size">
-          <CheckIcon size={14} className="little-spacer-right text-text-top" />
+          <CheckIcon size={14} className="little-spacer-right text-bottom" />
           {translate('marketplace.installed')}
         </span>
       );
@@ -59,6 +59,8 @@ export default class EditionBox extends React.PureComponent<Props> {
   render() {
     const { edition, editionStatus, readOnly } = this.props;
     const isInstalled = editionStatus && editionStatus.currentEditionKey === edition.key;
+    const uninstallInProgress =
+      editionStatus && editionStatus.installationStatus === 'UNINSTALL_IN_PROGRESS';
     const installInProgress =
       editionStatus &&
       ['AUTOMATIC_IN_PROGRESS', 'AUTOMATIC_READY'].includes(editionStatus.installationStatus);
@@ -74,20 +76,20 @@ export default class EditionBox extends React.PureComponent<Props> {
             {translate('marketplace.learn_more')}
           </a>
           {!readOnly &&
-          !isInstalled && (
-            <button disabled={installInProgress} onClick={this.handleInstall}>
-              {translate('marketplace.install')}
-            </button>
-          )}
-          {!readOnly &&
-          isInstalled && (
-            <button
-              className="button-red"
-              disabled={installInProgress}
-              onClick={this.props.onUninstall}>
-              {translate('marketplace.uninstall')}
-            </button>
-          )}
+            (isInstalled ? (
+              <button
+                className="button-red"
+                disabled={installInProgress || uninstallInProgress}
+                onClick={this.props.onUninstall}>
+                {translate('marketplace.uninstall')}
+              </button>
+            ) : (
+              <button
+                disabled={installInProgress || uninstallInProgress}
+                onClick={this.handleInstall}>
+                {translate('marketplace.install')}
+              </button>
+            ))}
         </div>
       </div>
     );
index 2430200ac27fbd0031ea7ddb733e39324a0c86d8..f21e8a305883e8e9060d788f30d02050109b1b49 100644 (file)
@@ -49,8 +49,67 @@ export default class EditionsStatusNotif extends React.PureComponent<Props, Stat
     );
   };
 
-  renderStatusAlert() {
+  renderRestartMsg(edition?: Edition) {
     const { editionStatus, readOnly } = this.props;
+    return (
+      <div className="alert alert-success">
+        <span>
+          {edition ? (
+            translateWithParameters(
+              'marketplace.status_x.' + editionStatus.installationStatus,
+              edition.name
+            )
+          ) : (
+            translate('marketplace.status', editionStatus.installationStatus)
+          )}
+        </span>
+        {!readOnly && (
+          <button className="js-restart spacer-left" onClick={this.handleOpenRestart}>
+            {translate('marketplace.restart')}
+          </button>
+        )}
+        {!readOnly && this.state.openRestart && <RestartForm onClose={this.hanleCloseRestart} />}
+      </div>
+    );
+  }
+
+  renderManualMsg(edition?: Edition) {
+    const { editionStatus } = this.props;
+    return (
+      <div className="alert alert-danger">
+        {edition ? (
+          translateWithParameters(
+            'marketplace.status_x.' + editionStatus.installationStatus,
+            edition.name
+          )
+        ) : (
+          translate('marketplace.status', editionStatus.installationStatus)
+        )}
+        <p className="spacer-left">
+          {edition && (
+            <a
+              className="button spacer-right"
+              download={`sonarqube-${edition.name}.zip`}
+              href={edition.downloadUrl}
+              target="_blank">
+              {translate('marketplace.download_package')}
+            </a>
+          )}
+          <a
+            href="https://redirect.sonarsource.com/doc/how-to-install-an-edition.html"
+            target="_blank">
+            {translate('marketplace.how_to_install')}
+          </a>
+        </p>
+        <a className="little-spacer-left" href="https://www.sonarsource.com" target="_blank">
+          {translate('marketplace.how_to_install')}
+        </a>
+      </div>
+    );
+  }
+
+  renderStatusAlert() {
+    const { editionStatus } = this.props;
     const { installationStatus, nextEditionKey } = editionStatus;
     const nextEdition =
       this.props.editions && this.props.editions.find(edition => edition.key === nextEditionKey);
@@ -65,59 +124,9 @@ export default class EditionsStatusNotif extends React.PureComponent<Props, Stat
         );
       case 'AUTOMATIC_READY':
       case 'UNINSTALL_IN_PROGRESS':
-        return (
-          <div className="alert alert-success">
-            <span>
-              {nextEdition ? (
-                translateWithParameters(
-                  'marketplace.status_x.' + installationStatus,
-                  nextEdition.name
-                )
-              ) : (
-                translate('marketplace.status', installationStatus)
-              )}
-            </span>
-            {!readOnly && (
-              <button className="js-restart spacer-left" onClick={this.handleOpenRestart}>
-                {translate('marketplace.restart')}
-              </button>
-            )}
-            {!readOnly &&
-            this.state.openRestart && <RestartForm onClose={this.hanleCloseRestart} />}
-          </div>
-        );
+        return this.renderRestartMsg(nextEdition);
       case 'MANUAL_IN_PROGRESS':
-        return (
-          <div className="alert alert-danger">
-            {nextEdition ? (
-              translateWithParameters(
-                'marketplace.status_x.' + installationStatus,
-                nextEdition.name
-              )
-            ) : (
-              translate('marketplace.status', installationStatus)
-            )}
-            <p className="spacer-left">
-              {nextEdition && (
-                <a
-                  className="button spacer-right"
-                  download={`sonarqube-${nextEdition.name}.zip`}
-                  href={nextEdition.downloadUrl}
-                  target="_blank">
-                  {translate('marketplace.download_package')}
-                </a>
-              )}
-              <a
-                href="https://redirect.sonarsource.com/doc/how-to-install-an-edition.html"
-                target="_blank">
-                {translate('marketplace.how_to_install')}
-              </a>
-            </p>
-            <a className="little-spacer-left" href="https://www.sonarsource.com" target="_blank">
-              {translate('marketplace.how_to_install')}
-            </a>
-          </div>
-        );
+        return this.renderManualMsg(nextEdition);
     }
     return null;
   }
@@ -127,12 +136,9 @@ export default class EditionsStatusNotif extends React.PureComponent<Props, Stat
     return (
       <div>
         {installError && (
-          <div className="alert alert-danger">
+          <div className="alert alert-danger alert-cancel">
             {installError}
-            <a
-              className="pull-right button-link text-danger"
-              href="#"
-              onClick={this.handleDismissError}>
+            <a className="button-link text-danger" href="#" onClick={this.handleDismissError}>
               <CloseIcon />
             </a>
           </div>
index 10def5d07bdaf3eb9588130ad0c4ef172ac159df..58b58f371a7e2afdcb161e4bfe3ed5f5275f611b 100644 (file)
@@ -130,11 +130,11 @@ export default class LicenseEditionSet extends React.PureComponent<Props, State>
         <textarea
           autoFocus={true}
           id="set-license"
-          className="display-block"
-          cols={62}
+          className="display-block input-super-large"
           onChange={this.handleLicenseChange}
           required={true}
-          rows={6}
+          rows={8}
+          style={{ resize: 'none' }}
           value={license}
         />
         {previewStatus && (
index c895d0aedd16baf96c171d77429cb3e35bc16774..501631edadc921a176aca2b730c7109fc06c571c 100644 (file)
@@ -69,47 +69,53 @@ export default class PluginActions extends React.PureComponent<Props, State> {
   handleUninstall = () => this.doPluginAction(uninstallPlugin);
   handleTermsCheck = (checked: boolean) => this.setState({ acceptTerms: checked });
 
+  renderBundled() {
+    const { plugin } = this.props;
+
+    return (
+      <div className="js-actions">
+        {isPluginAvailable(plugin) && (
+          <div>
+            <p className="little-spacer-bottom">
+              {translate('marketplace.available_under_commercial_license')}
+            </p>
+            <a href={plugin.homepageUrl} target="_blank">
+              {translate('marketplace.learn_more')}
+            </a>
+          </div>
+        )}
+        {isPluginInstalled(plugin) && (
+          <p>
+            <CheckIcon className="little-spacer-right" />
+            {translate('marketplace.installed')}
+          </p>
+        )}
+        {isPluginInstalled(plugin) &&
+        plugin.updates &&
+        plugin.updates.length > 0 && (
+          <div className="spacer-top button-group">
+            {plugin.updates.map((update, idx) => (
+              <PluginUpdateButton
+                key={idx}
+                onClick={this.handleUpdate}
+                update={update}
+                disabled={this.state.loading}
+              />
+            ))}
+          </div>
+        )}
+      </div>
+    );
+  }
+
   render() {
     const { plugin } = this.props;
-    const { loading } = this.state;
 
     if (plugin.editionBundled) {
-      return (
-        <div className="js-actions">
-          {isPluginAvailable(plugin) && (
-            <div>
-              <p className="little-spacer-bottom">
-                {translate('marketplace.available_under_commercial_license')}
-              </p>
-              <a href={plugin.homepageUrl} target="_blank">
-                {translate('marketplace.learn_more')}
-              </a>
-            </div>
-          )}
-          {isPluginInstalled(plugin) && (
-            <p>
-              <CheckIcon className="little-spacer-right" />
-              {translate('marketplace.installed')}
-            </p>
-          )}
-          {isPluginInstalled(plugin) &&
-          plugin.updates &&
-          plugin.updates.length > 0 && (
-            <div className="spacer-top button-group">
-              {plugin.updates.map((update, idx) => (
-                <PluginUpdateButton
-                  key={idx}
-                  onClick={this.handleUpdate}
-                  update={update}
-                  disabled={loading}
-                />
-              ))}
-            </div>
-          )}
-        </div>
-      );
+      return this.renderBundled();
     }
 
+    const { loading } = this.state;
     return (
       <div className="js-actions">
         {isPluginAvailable(plugin) &&
index 705d02f390750eea3bbff92b0532f41502c0a3b9..37b76edc83e4a0758a0cfe634bdd172dbb8983b6 100644 (file)
@@ -57,6 +57,7 @@ export default class UninstallEditionForm extends React.PureComponent<Props, Sta
       .then(() => {
         this.props.updateEditionStatus({
           ...this.props.editionStatus,
+          currentEditionKey: undefined,
           installationStatus: 'UNINSTALL_IN_PROGRESS'
         });
         this.props.onClose();
index 3f52d0cce13322520da796352a4f56f9cb15c823..be7f50ce171714f606ef92bc59ee060dbf3fdab1 100644 (file)
@@ -53,7 +53,7 @@ it('should update the edition status after uninstall', async () => {
   expect(uninstallEdition).toHaveBeenCalled();
   await new Promise(setImmediate);
   expect(updateEditionStatus).toHaveBeenCalledWith({
-    currentEditionKey: 'foo',
+    currentEditionKey: undefined,
     installationStatus: 'UNINSTALL_IN_PROGRESS'
   });
 });
index 7b8a8eaa902327d4206a8a6b36dc379c93482a57..b7dbb58a5627c8cfd39a6b6e4045834aff206648 100644 (file)
@@ -123,7 +123,7 @@ exports[`should display installed badge 1`] = `
     className="marketplace-edition-badge badge badge-normal-size"
   >
     <CheckIcon
-      className="little-spacer-right text-text-top"
+      className="little-spacer-right text-bottom"
       size={14}
     />
     marketplace.installed
index e52e16c6fa7c49bf773670d0a8c15c8556b892ef..a76a31c61d26fc5e009d419ad441e42bd42036a6 100644 (file)
@@ -36,11 +36,11 @@ exports[`should display an in progress notif 1`] = `
 exports[`should display install errors 1`] = `
 <div>
   <div
-    className="alert alert-danger"
+    className="alert alert-danger alert-cancel"
   >
     Foo error
     <a
-      className="pull-right button-link text-danger"
+      className="button-link text-danger"
       href="#"
       onClick={[Function]}
     >
index 2e49983f7d39570014f0b5f5df0f0de8298dd89a..4534dd328d2dd296f1a3f0a9ff92f55dcacc1433 100644 (file)
@@ -39,12 +39,16 @@ exports[`should display correctly 1`] = `
   </label>
   <textarea
     autoFocus={true}
-    className="display-block"
-    cols={62}
+    className="display-block input-super-large"
     id="set-license"
     onChange={[Function]}
     required={true}
-    rows={6}
+    rows={8}
+    style={
+      Object {
+        "resize": "none",
+      }
+    }
     value=""
   />
   <a
index 1f9216c1c2e3b1bacf2a9895df72ce285aa34400..31835b2ec56810c908b6446f766d63f04d3a3f28 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 { memoize } from 'lodash';
+import { memoize, sortBy } from 'lodash';
 import { Plugin, PluginAvailable, PluginInstalled, PluginPending } from '../../api/plugins';
 import { Edition, EditionsPerVersion } from '../../api/marketplace';
 import { cleanQuery, parseAsString, RawQuery, serializeString } from '../../helpers/query';
@@ -52,6 +52,15 @@ export function filterPlugins(plugins: Plugin[], search: string): Plugin[] {
   });
 }
 
+export function getEditionsForLastVersion(editions: EditionsPerVersion): Edition[] {
+  const sortedVersion = sortBy(Object.keys(editions), [
+    (version: string) => -Number(version.split('.')[0]),
+    (version: string) => -Number(version.split('.')[1] || 0),
+    (version: string) => -Number(version.split('.')[2] || 0)
+  ]);
+  return editions[sortedVersion[0]];
+}
+
 export function getEditionsForVersion(
   editions: EditionsPerVersion,
   version: string
index 01bdc89ce1c6ea50962d802eb830aa91b73a6797..1cc1c516fe2611eb167b51b44ca6a3c075072daf 100644 (file)
   .alert-emphasis-variant(#3c763d, #dff0d8, #d6e9c6);
 }
 
-.page-notifs .alert {
+.alert-cancel {
   display: flex;
   justify-content: space-between;
+}
+
+.page-notifs .alert {
   padding: 8px 10px;
 }
 
index b9736b5be47e7191d2d828bfff0db0c8be16db74..7af87b0afb83e62d9f006075ca73ef8a82628ce8 100644 (file)
@@ -2097,14 +2097,14 @@ marketplace.uninstall=Uninstall
 marketplace.i_accept_the=I accept the
 marketplace.commercial_edition=Commercial Edition
 marketplace.terms_and_conditions=Terms and Conditions
-marketplace.editions_unavailable=Explore our Commercial Editions: advanced feature packs brought to you by SonarSource on {url}
+marketplace.editions_unavailable=Explore our Commercial Editions on {url}: advanced feature packs brought to you by SonarSource
 marketplace.release_notes=Release Notes
 marketplace.status.AUTOMATIC_IN_PROGRESS=Updating your installation... Please wait...
-marketplace.status.AUTOMATIC_READY=Commercial Edition successfully installed. Please restart Server to activate your new features.
-marketplace.status.UNINSTALL_IN_PROGRESS=Commercial Edition successfully uninstalled. Please restart Server to remove the features.
+marketplace.status.AUTOMATIC_READY=Commercial Edition successfully installed. Please restart the server to activate your new features.
+marketplace.status.UNINSTALL_IN_PROGRESS=Commercial Edition successfully uninstalled. Please restart the server to remove the features.
 marketplace.status.MANUAL_IN_PROGRESS=Can't install Commercial Edition because of internet access issue. Please manually install the package in your SonarQube's plugins folder.
-marketplace.status_x.AUTOMATIC_READY={0} successfully installed. Please restart Server to activate your new features.
-marketplace.status_X.UNINSTALL_IN_PROGRESS={0} successfully uninstalled. Please restart Server to remove the features.
+marketplace.status_x.AUTOMATIC_READY={0} successfully installed. Please resstart the server to activate your new features.
+marketplace.status_X.UNINSTALL_IN_PROGRESS={0} successfully uninstalled. Please restart the server to remove the features.
 marketplace.status_x.MANUAL_IN_PROGRESS=Can't install {0} because of internet access issue. 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}