]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9935 Add some unit tests
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>
Fri, 13 Oct 2017 13:45:56 +0000 (15:45 +0200)
committerGrégoire Aubert <gregoire.aubert@sonarsource.com>
Mon, 23 Oct 2017 15:01:13 +0000 (08:01 -0700)
39 files changed:
server/sonar-web/src/main/js/apps/marketplace/PluginActions.tsx [deleted file]
server/sonar-web/src/main/js/apps/marketplace/PluginAvailable.tsx [deleted file]
server/sonar-web/src/main/js/apps/marketplace/PluginChangeLog.tsx [deleted file]
server/sonar-web/src/main/js/apps/marketplace/PluginChangeLogButton.tsx [deleted file]
server/sonar-web/src/main/js/apps/marketplace/PluginChangeLogItem.tsx [deleted file]
server/sonar-web/src/main/js/apps/marketplace/PluginDescription.tsx [deleted file]
server/sonar-web/src/main/js/apps/marketplace/PluginInstalled.tsx [deleted file]
server/sonar-web/src/main/js/apps/marketplace/PluginLicense.tsx [deleted file]
server/sonar-web/src/main/js/apps/marketplace/PluginOrganization.tsx [deleted file]
server/sonar-web/src/main/js/apps/marketplace/PluginStatus.tsx [deleted file]
server/sonar-web/src/main/js/apps/marketplace/PluginUpdateButton.tsx [deleted file]
server/sonar-web/src/main/js/apps/marketplace/PluginUpdateItem.tsx [deleted file]
server/sonar-web/src/main/js/apps/marketplace/PluginUpdates.tsx [deleted file]
server/sonar-web/src/main/js/apps/marketplace/PluginUrls.tsx [deleted file]
server/sonar-web/src/main/js/apps/marketplace/PluginsList.tsx
server/sonar-web/src/main/js/apps/marketplace/__tests__/Footer-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/marketplace/__tests__/PendingActions-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/Footer-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/PendingActions-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/marketplace/components/PluginActions.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/marketplace/components/PluginAvailable.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/marketplace/components/PluginChangeLog.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/marketplace/components/PluginChangeLogButton.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/marketplace/components/PluginChangeLogItem.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/marketplace/components/PluginDescription.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/marketplace/components/PluginInstalled.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/marketplace/components/PluginLicense.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/marketplace/components/PluginOrganization.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/marketplace/components/PluginStatus.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/marketplace/components/PluginUpdateButton.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/marketplace/components/PluginUpdateItem.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/marketplace/components/PluginUpdates.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/marketplace/components/PluginUrls.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/marketplace/components/__tests__/PluginDescription-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/marketplace/components/__tests__/PluginLicense-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/marketplace/components/__tests__/PluginUrls-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/PluginDescription-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/PluginLicense-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/PluginUrls-test.tsx.snap [new file with mode: 0644]

diff --git a/server/sonar-web/src/main/js/apps/marketplace/PluginActions.tsx b/server/sonar-web/src/main/js/apps/marketplace/PluginActions.tsx
deleted file mode 100644 (file)
index f2fce9f..0000000
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * 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 Checkbox from '../../../components/controls/Checkbox';
-import CheckIcon from '../../../components/icons-components/CheckIcon';
-import PluginUpdateButton from './PluginUpdateButton';
-import { Plugin, installPlugin, updatePlugin, uninstallPlugin } from '../../../api/plugins';
-import { isPluginAvailable, isPluginInstalled } from '../utils';
-import { translate } from '../../../helpers/l10n';
-
-interface Props {
-  plugin: Plugin;
-  refreshPending: () => void;
-}
-
-interface State {
-  acceptTerms: boolean;
-  loading: boolean;
-}
-
-export default class PluginActions extends React.PureComponent<Props, State> {
-  mounted: boolean;
-  state: State = { acceptTerms: false, loading: false };
-
-  componentDidMount() {
-    this.mounted = true;
-  }
-
-  componentWillUnmount() {
-    this.mounted = false;
-  }
-
-  doPluginAction = (apiAction: (data: { key: string }) => Promise<void | Response>) => {
-    this.setState({ loading: true });
-    apiAction({ key: this.props.plugin.key }).then(
-      () => {
-        this.props.refreshPending();
-        if (this.mounted) {
-          this.setState({ loading: false });
-        }
-      },
-      () => {
-        if (this.mounted) {
-          this.setState({ loading: false });
-        }
-      }
-    );
-  };
-
-  handleInstall = () => this.doPluginAction(installPlugin);
-  handleUpdate = () => this.doPluginAction(updatePlugin);
-  handleUninstall = () => this.doPluginAction(uninstallPlugin);
-  handleTermsCheck = (checked: boolean) => this.setState({ acceptTerms: checked });
-
-  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 (
-      <div className="js-actions">
-        {isPluginAvailable(plugin) &&
-        plugin.termsAndConditionsUrl && (
-          <p className="little-spacer-bottom">
-            <Checkbox
-              checked={this.state.acceptTerms}
-              className="js-terms"
-              id={'plugin-terms-' + plugin.key}
-              onCheck={this.handleTermsCheck}>
-              <label className="little-spacer-left" htmlFor={'plugin-terms-' + plugin.key}>
-                {translate('marketplace.i_accept_the')}
-              </label>
-            </Checkbox>
-            <a
-              className="js-plugin-terms nowrap little-spacer-left"
-              href={plugin.termsAndConditionsUrl}
-              target="_blank">
-              {translate('marketplace.terms_and_conditions')}
-            </a>
-          </p>
-        )}
-        {loading && <i className="spinner spacer-right" />}
-        {isPluginInstalled(plugin) && (
-          <div className="button-group">
-            {plugin.updates &&
-              plugin.updates.map((update, idx) => (
-                <PluginUpdateButton
-                  key={idx}
-                  onClick={this.handleUpdate}
-                  update={update}
-                  disabled={loading}
-                />
-              ))}
-            <button
-              className="js-uninstall button-red"
-              disabled={loading}
-              onClick={this.handleUninstall}>
-              {translate('marketplace.uninstall')}
-            </button>
-          </div>
-        )}
-        {isPluginAvailable(plugin) && (
-          <button
-            className="js-install"
-            disabled={loading || (plugin.termsAndConditionsUrl != null && !this.state.acceptTerms)}
-            onClick={this.handleInstall}>
-            {translate('marketplace.install')}
-          </button>
-        )}
-      </div>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/marketplace/PluginAvailable.tsx b/server/sonar-web/src/main/js/apps/marketplace/PluginAvailable.tsx
deleted file mode 100644 (file)
index c80f596..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * 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 PluginChangeLogButton from './PluginChangeLogButton';
-import PluginDescription from './PluginDescription';
-import PluginLicense from './PluginLicense';
-import PluginOrganization from './PluginOrganization';
-import PluginStatus from './PluginStatus';
-import PluginUrls from './PluginUrls';
-import { PluginAvailable } from '../../api/plugins';
-import { translateWithParameters } from '../../helpers/l10n';
-import { Query } from './utils';
-
-interface Props {
-  plugin: PluginAvailable;
-  refreshPending: () => void;
-  status?: string;
-  updateQuery: (newQuery: Partial<Query>) => void;
-}
-
-export default function PluginAvailable({ plugin, refreshPending, status, updateQuery }: Props) {
-  return (
-    <tr>
-      <PluginDescription plugin={plugin} updateQuery={updateQuery} />
-      <td className="text-top big-spacer-right">
-        <ul>
-          <li className="diplay-flex-row little-spacer-bottom">
-            <div className="pull-left spacer-right">
-              <span className="badge badge-success">{plugin.release.version}</span>
-            </div>
-            <div>
-              {plugin.release.description}
-              <PluginChangeLogButton release={plugin.release} update={plugin.update} />
-              {plugin.update.requires.length > 0 && (
-                <p className="little-spacer-top">
-                  <strong>
-                    {translateWithParameters(
-                      'marketplace.installing_this_plugin_will_also_install_x',
-                      plugin.update.requires.map(requiredPlugin => requiredPlugin.name).join(', ')
-                    )}
-                  </strong>
-                </p>
-              )}
-            </div>
-          </li>
-        </ul>
-      </td>
-
-      <td className="text-top width-20 big-spacer-right">
-        <ul>
-          <PluginUrls plugin={plugin} />
-          <PluginLicense license={plugin.license} />
-          <PluginOrganization plugin={plugin} />
-        </ul>
-      </td>
-
-      <PluginStatus plugin={plugin} status={status} refreshPending={refreshPending} />
-    </tr>
-  );
-}
diff --git a/server/sonar-web/src/main/js/apps/marketplace/PluginChangeLog.tsx b/server/sonar-web/src/main/js/apps/marketplace/PluginChangeLog.tsx
deleted file mode 100644 (file)
index 21690e4..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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 BubblePopup from '../../components/common/BubblePopup';
-import PluginChangeLogItem from './PluginChangeLogItem';
-import { Release, Update } from '../../api/plugins';
-import { translate } from '../../helpers/l10n';
-
-interface Props {
-  popupPosition?: any;
-  release: Release;
-  update: Update;
-}
-
-export default function PluginChangeLog({ popupPosition, release, update }: Props) {
-  return (
-    <BubblePopup position={popupPosition} customClass="bubble-popup-bottom-right">
-      <div className="abs-width-300 bubble-popup-container">
-        <div className="bubble-popup-title">{translate('changelog')}</div>
-        <ul className="js-plugin-changelog-list">
-          {update.previousUpdates &&
-            update.previousUpdates.map(
-              previousUpdate =>
-                previousUpdate.release ? (
-                  <PluginChangeLogItem
-                    key={previousUpdate.release.version}
-                    release={previousUpdate.release}
-                    update={previousUpdate}
-                  />
-                ) : null
-            )}
-          <PluginChangeLogItem release={release} update={update} />
-        </ul>
-      </div>
-    </BubblePopup>
-  );
-}
diff --git a/server/sonar-web/src/main/js/apps/marketplace/PluginChangeLogButton.tsx b/server/sonar-web/src/main/js/apps/marketplace/PluginChangeLogButton.tsx
deleted file mode 100644 (file)
index 61b309c..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * 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 BubblePopupHelper from '../../components/common/BubblePopupHelper';
-import PluginChangeLog from './PluginChangeLog';
-import { Release, Update } from '../../api/plugins';
-
-interface Props {
-  release: Release;
-  update: Update;
-}
-
-interface State {
-  changelogOpen: boolean;
-}
-
-export default class PluginChangeLogButton extends React.PureComponent<Props, State> {
-  state: State = { changelogOpen: false };
-
-  handleChangelogClick = (event: React.SyntheticEvent<HTMLButtonElement>) => {
-    event.preventDefault();
-    event.stopPropagation();
-    this.toggleChangelog();
-  };
-
-  toggleChangelog = (show?: boolean) => {
-    if (show !== undefined) {
-      this.setState({ changelogOpen: show });
-    } else {
-      this.setState(state => ({ changelogOpen: !state.changelogOpen }));
-    }
-  };
-
-  render() {
-    return (
-      <div className="display-inline-block little-spacer-left">
-        <button
-          className="button-link js-changelog issue-rule icon-ellipsis-h"
-          onClick={this.handleChangelogClick}
-        />
-        <BubblePopupHelper
-          isOpen={this.state.changelogOpen}
-          position="bottomright"
-          popup={<PluginChangeLog release={this.props.release} update={this.props.update} />}
-          togglePopup={this.toggleChangelog}
-        />
-      </div>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/marketplace/PluginChangeLogItem.tsx b/server/sonar-web/src/main/js/apps/marketplace/PluginChangeLogItem.tsx
deleted file mode 100644 (file)
index 163c816..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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 DateFormatter from '../../components/intl/DateFormatter';
-import Tooltip from '../../components/controls/Tooltip';
-import { Release, Update } from '../../api/plugins';
-import { translate, translateWithParameters } from '../../helpers/l10n';
-
-interface Props {
-  release: Release;
-  update: Update;
-}
-
-export default function PluginChangeLogItem({ release, update }: Props) {
-  return (
-    <li className="big-spacer-bottom">
-      <div className="little-spacer-bottom">
-        {update.status === 'COMPATIBLE' || !update.status ? (
-          <span className="js-plugin-changelog-version badge badge-success spacer-right">
-            {release.version}
-          </span>
-        ) : (
-          <Tooltip overlay={translateWithParameters('marketplace.status', update.status)}>
-            <span className="js-plugin-changelog-version badge badge-warning spacer-right">
-              {release.version}
-            </span>
-          </Tooltip>
-        )}
-        <span className="js-plugin-changelog-date note spacer-right">
-          <DateFormatter date={release.date} />
-        </span>
-        {release.changeLogUrl && (
-          <a className="js-plugin-changelog-link" href={release.changeLogUrl} target="_blank">
-            {translate('update_center.release_notes')}
-          </a>
-        )}
-      </div>
-      <div className="js-plugin-changelog-description">{release.description}</div>
-    </li>
-  );
-}
diff --git a/server/sonar-web/src/main/js/apps/marketplace/PluginDescription.tsx b/server/sonar-web/src/main/js/apps/marketplace/PluginDescription.tsx
deleted file mode 100644 (file)
index 8de1f3b..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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 { Plugin } from '../../api/plugins';
-import { Query } from './utils';
-
-interface Props {
-  plugin: Plugin;
-  updateQuery: (newQuery: Partial<Query>) => void;
-}
-
-export default class PluginDescription extends React.PureComponent<Props> {
-  handleCategoryClick = (e: React.SyntheticEvent<HTMLAnchorElement>) => {
-    e.preventDefault();
-    this.props.updateQuery({ search: this.props.plugin.category });
-  };
-
-  render() {
-    const { plugin } = this.props;
-    return (
-      <td className="text-top width-20 big-spacer-right">
-        <div>
-          <strong className="js-plugin-name">{plugin.name}</strong>
-          {plugin.category && (
-            <a
-              className="js-plugin-category badge spacer-left"
-              href="#"
-              onClick={this.handleCategoryClick}>
-              {plugin.category}
-            </a>
-          )}
-        </div>
-        <div className="js-plugin-description little-spacer-top">{plugin.description}</div>
-      </td>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/marketplace/PluginInstalled.tsx b/server/sonar-web/src/main/js/apps/marketplace/PluginInstalled.tsx
deleted file mode 100644 (file)
index b9225f3..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * 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 PluginDescription from './PluginDescription';
-import PluginLicense from './PluginLicense';
-import PluginOrganization from './PluginOrganization';
-import PluginStatus from './PluginStatus';
-import PluginUpdates from './PluginUpdates';
-import PluginUrls from './PluginUrls';
-import { PluginInstalled } from '../../api/plugins';
-import { translate } from '../../helpers/l10n';
-import { Query } from './utils';
-
-interface Props {
-  plugin: PluginInstalled;
-  refreshPending: () => void;
-  status?: string;
-  updateQuery: (newQuery: Partial<Query>) => void;
-}
-
-export default function PluginInstalled({ plugin, refreshPending, status, updateQuery }: Props) {
-  return (
-    <tr>
-      <PluginDescription plugin={plugin} updateQuery={updateQuery} />
-      <td className="text-top big-spacer-right">
-        <ul>
-          <li className="little-spacer-bottom">
-            <strong className="js-plugin-installed-version little-spacer-right">
-              {plugin.version}
-            </strong>
-            {translate('marketplace._installed')}
-          </li>
-          <PluginUpdates updates={plugin.updates} />
-        </ul>
-      </td>
-
-      <td className="text-top width-20 big-spacer-right">
-        <ul>
-          <PluginUrls plugin={plugin} />
-          <PluginLicense license={plugin.license} />
-          <PluginOrganization plugin={plugin} />
-        </ul>
-      </td>
-
-      <PluginStatus plugin={plugin} status={status} refreshPending={refreshPending} />
-    </tr>
-  );
-}
diff --git a/server/sonar-web/src/main/js/apps/marketplace/PluginLicense.tsx b/server/sonar-web/src/main/js/apps/marketplace/PluginLicense.tsx
deleted file mode 100644 (file)
index 550ba92..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * 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 { FormattedMessage } from 'react-intl';
-import { translate } from '../../helpers/l10n';
-
-interface Props {
-  license?: string;
-}
-
-export default function PluginLicense({ license }: Props) {
-  if (!license) {
-    return null;
-  }
-  return (
-    <li className="little-spacer-bottom text-limited" title={license}>
-      <FormattedMessage
-        defaultMessage={translate('marketplace.licensed_under_x')}
-        id="marketplace.licensed_under_x"
-        values={{
-          license: <span className="js-plugin-license">{license}</span>
-        }}
-      />
-    </li>
-  );
-}
diff --git a/server/sonar-web/src/main/js/apps/marketplace/PluginOrganization.tsx b/server/sonar-web/src/main/js/apps/marketplace/PluginOrganization.tsx
deleted file mode 100644 (file)
index 505ebd1..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * 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 { FormattedMessage } from 'react-intl';
-import { translate } from '../../helpers/l10n';
-import { Plugin } from '../../api/plugins';
-
-interface Props {
-  plugin: Plugin;
-}
-
-export default function PluginOrganization({ plugin }: Props) {
-  if (!plugin.organizationName) {
-    return null;
-  }
-  return (
-    <li className="little-spacer-bottom">
-      <FormattedMessage
-        defaultMessage={translate('marketplace.developed_by_x')}
-        id="marketplace.developed_by_x"
-        values={{
-          organization: plugin.organizationUrl ? (
-            <a className="js-plugin-organization" href={plugin.organizationUrl} target="_blank">
-              {plugin.organizationName}
-            </a>
-          ) : (
-            <span className="js-plugin-organization">{plugin.organizationName}</span>
-          )
-        }}
-      />
-    </li>
-  );
-}
diff --git a/server/sonar-web/src/main/js/apps/marketplace/PluginStatus.tsx b/server/sonar-web/src/main/js/apps/marketplace/PluginStatus.tsx
deleted file mode 100644 (file)
index 298206f..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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 { Plugin } from '../../api/plugins';
-import PluginActions from './PluginActions';
-import { translate } from '../../helpers/l10n';
-
-interface Props {
-  plugin: Plugin;
-  refreshPending: () => void;
-  status?: string;
-}
-
-export default function PluginStatus({ plugin, refreshPending, status }: Props) {
-  return (
-    <td className="text-top text-right width-20">
-      {status === 'installing' && (
-        <p className="text-success">{translate('marketplace.install_pending')}</p>
-      )}
-
-      {status === 'updating' && (
-        <p className="text-success">{translate('marketplace.update_pending')}</p>
-      )}
-
-      {status === 'removing' && (
-        <p className="text-danger">{translate('marketplace.uninstall_pending')}</p>
-      )}
-
-      {status == null && (
-        <div>
-          <i className="js-spinner spinner hidden" />
-          <PluginActions plugin={plugin} refreshPending={refreshPending} />
-        </div>
-      )}
-    </td>
-  );
-}
diff --git a/server/sonar-web/src/main/js/apps/marketplace/PluginUpdateButton.tsx b/server/sonar-web/src/main/js/apps/marketplace/PluginUpdateButton.tsx
deleted file mode 100644 (file)
index 53c154e..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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 { Update } from '../../api/plugins';
-import { translateWithParameters } from '../../helpers/l10n';
-
-interface Props {
-  disabled: boolean;
-  onClick: (update: Update) => void;
-  update: Update;
-}
-
-export default class PluginUpdateButton extends React.PureComponent<Props> {
-  handleClick = () => this.props.onClick(this.props.update);
-
-  render() {
-    const { disabled, update } = this.props;
-    if (update.status !== 'COMPATIBLE' || !update.release) {
-      return null;
-    }
-    return (
-      <button className="js-update" onClick={this.handleClick} disabled={disabled}>
-        {translateWithParameters('marketplace.update_to_x', update.release.version)}
-      </button>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/marketplace/PluginUpdateItem.tsx b/server/sonar-web/src/main/js/apps/marketplace/PluginUpdateItem.tsx
deleted file mode 100644 (file)
index ea5d581..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * 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 PluginChangeLogButton from './PluginChangeLogButton';
-import Tooltip from '../../components/controls/Tooltip';
-import { Release, Update } from '../../api/plugins';
-import { translate } from '../../helpers/l10n';
-
-interface Props {
-  update: Update;
-  release: Release;
-}
-
-interface State {
-  changelogOpen: boolean;
-}
-
-export default class PluginUpdateItem extends React.PureComponent<Props, State> {
-  state: State = { changelogOpen: false };
-
-  handleChangelogClick = (event: React.SyntheticEvent<HTMLButtonElement>) => {
-    event.preventDefault();
-    event.stopPropagation();
-    this.toggleChangelog();
-  };
-
-  toggleChangelog = (show?: boolean) => {
-    if (show !== undefined) {
-      this.setState({ changelogOpen: show });
-    } else {
-      this.setState(state => ({ changelogOpen: !state.changelogOpen }));
-    }
-  };
-
-  render() {
-    const { release, update } = this.props;
-    return (
-      <li key={release.version} className="diplay-flex-row little-spacer-bottom">
-        <div className="pull-left spacer-right">
-          {update.status === 'COMPATIBLE' ? (
-            <span className="js-update-version badge badge-success">{release.version}</span>
-          ) : (
-            <Tooltip overlay={translate('marketplace.status', update.status)}>
-              <span className="js-update-version badge badge-warning">{release.version}</span>
-            </Tooltip>
-          )}
-        </div>
-        <div>
-          {release.description}
-          <PluginChangeLogButton release={release} update={update} />
-        </div>
-      </li>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/marketplace/PluginUpdates.tsx b/server/sonar-web/src/main/js/apps/marketplace/PluginUpdates.tsx
deleted file mode 100644 (file)
index b662651..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * 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 PluginUpdateItem from './PluginUpdateItem';
-import { Update } from '../../api/plugins';
-import { translate } from '../../helpers/l10n';
-
-interface Props {
-  updates?: Update[];
-}
-
-export default function PluginUpdates({ updates }: Props) {
-  if (!updates || updates.length <= 0) {
-    return null;
-  }
-  return (
-    <li className="spacer-top">
-      <strong>{translate('marketplace.updates')}:</strong>
-      <ul className="little-spacer-top">
-        {updates.map(
-          update =>
-            update.release ? (
-              <PluginUpdateItem
-                key={update.release.version}
-                release={update.release}
-                update={update}
-              />
-            ) : null
-        )}
-      </ul>
-    </li>
-  );
-}
diff --git a/server/sonar-web/src/main/js/apps/marketplace/PluginUrls.tsx b/server/sonar-web/src/main/js/apps/marketplace/PluginUrls.tsx
deleted file mode 100644 (file)
index adcdd81..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * 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 { Plugin } from '../../api/plugins';
-import { translate } from '../../helpers/l10n';
-
-interface Props {
-  plugin: Plugin;
-}
-
-export default function PluginUrls({ plugin }: Props) {
-  if (!plugin.homepageUrl && !plugin.issueTrackerUrl) {
-    return null;
-  }
-  return (
-    <li className="little-spacer-bottom">
-      <ul className="list-inline">
-        {plugin.homepageUrl && (
-          <li>
-            <a className="js-plugin-homepage" href={plugin.homepageUrl} target="_blank">
-              {translate('marketplace.homepage')}
-            </a>
-          </li>
-        )}
-        {plugin.issueTrackerUrl && (
-          <li>
-            <a className="js-plugin-issues" href={plugin.issueTrackerUrl} target="_blank">
-              {translate('marketplace.issue_tracker')}
-            </a>
-          </li>
-        )}
-      </ul>
-    </li>
-  );
-}
index 0e51d154342b6d24c0871cc02a0851d4fb43704f..df07149f7005926785ceea368fd7df3bdd489156 100644 (file)
@@ -18,8 +18,8 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
-import PluginAvailable from './PluginAvailable';
-import PluginInstalled from './PluginInstalled';
+import PluginAvailable from './components/PluginAvailable';
+import PluginInstalled from './components/PluginInstalled';
 import { isPluginAvailable, isPluginInstalled, Query } from './utils';
 import { Plugin, PluginPending } from '../../api/plugins';
 
diff --git a/server/sonar-web/src/main/js/apps/marketplace/__tests__/Footer-test.tsx b/server/sonar-web/src/main/js/apps/marketplace/__tests__/Footer-test.tsx
new file mode 100644 (file)
index 0000000..58e9f4b
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * 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 Footer from '../Footer';
+
+it('should display the number of plugins', () => {
+  expect(shallow(<Footer total={3} />)).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/marketplace/__tests__/PendingActions-test.tsx b/server/sonar-web/src/main/js/apps/marketplace/__tests__/PendingActions-test.tsx
new file mode 100644 (file)
index 0000000..9d9d15c
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * 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 PendingActions from '../PendingActions';
+
+jest.mock('../../../api/plugins', () => ({
+  cancelPendingPlugins: jest.fn(() => Promise.resolve())
+}));
+
+const cancelPendingPlugins = require('../../../api/plugins').cancelPendingPlugins as jest.Mock<any>;
+
+beforeEach(() => {
+  cancelPendingPlugins.mockClear();
+});
+
+it('should display pending actions', () => {
+  expect(getWrapper()).toMatchSnapshot();
+});
+
+it('should not display nothing', () => {
+  expect(getWrapper({ pending: { installing: [], updating: [], removing: [] } })).toMatchSnapshot();
+});
+
+it('should open the restart form', () => {
+  const wrapper = getWrapper();
+  click(wrapper.find('.js-restart'));
+  expect(wrapper.find('RestartForm')).toHaveLength(1);
+});
+
+it('should cancel all pending and refresh them', async () => {
+  const refreshPending = jest.fn();
+  const wrapper = getWrapper({ refreshPending });
+  click(wrapper.find('.js-cancel-all'));
+  expect(cancelPendingPlugins).toHaveBeenCalled();
+  await new Promise(setImmediate);
+
+  expect(refreshPending).toHaveBeenCalled();
+});
+
+function getWrapper(props = {}) {
+  return shallow(
+    <PendingActions
+      pending={{
+        installing: [
+          {
+            key: 'foo',
+            name: 'Foo',
+            description: 'foo description',
+            version: 'fooversion',
+            implementationBuild: 'foobuild'
+          },
+          {
+            key: 'bar',
+            name: 'Bar',
+            description: 'bar description',
+            version: 'barversion',
+            implementationBuild: 'barbuild'
+          }
+        ],
+        updating: [],
+        removing: [
+          {
+            key: 'baz',
+            name: 'Baz',
+            description: 'baz description',
+            version: 'bazversion',
+            implementationBuild: 'bazbuild'
+          }
+        ]
+      }}
+      refreshPending={() => {}}
+      {...props}
+    />
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/Footer-test.tsx.snap b/server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/Footer-test.tsx.snap
new file mode 100644 (file)
index 0000000..164d3bd
--- /dev/null
@@ -0,0 +1,9 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should display the number of plugins 1`] = `
+<footer
+  className="spacer-top note text-center"
+>
+  x_show.3
+</footer>
+`;
diff --git a/server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/PendingActions-test.tsx.snap b/server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/PendingActions-test.tsx.snap
new file mode 100644 (file)
index 0000000..36604e8
--- /dev/null
@@ -0,0 +1,67 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should display pending actions 1`] = `
+<div
+  className="js-pending panel panel-warning big-spacer-bottom"
+>
+  <div
+    className="display-inline-block"
+  >
+    <p>
+      marketplace.sonarqube_needs_to_be_restarted_to
+    </p>
+    <ul
+      className="list-styled spacer-top"
+    >
+      <li>
+        <FormattedMessage
+          defaultMessage="marketplace.install_x_plugins"
+          id="marketplace.install_x_plugins"
+          values={
+            Object {
+              "nb": <strong
+                className="text-success big little-spacer-left little-spacer-right"
+            >
+                2
+            </strong>,
+            }
+          }
+        />
+      </li>
+      <li>
+        <FormattedMessage
+          defaultMessage="marketplace.uninstall_x_plugins"
+          id="marketplace.uninstall_x_plugins"
+          values={
+            Object {
+              "nb": <strong
+                className="text-danger big little-spacer-left little-spacer-right"
+            >
+                1
+            </strong>,
+            }
+          }
+        />
+      </li>
+    </ul>
+  </div>
+  <div
+    className="pull-right button-group"
+  >
+    <button
+      className="js-restart"
+      onClick={[Function]}
+    >
+      marketplace.restart
+    </button>
+    <button
+      className="js-cancel-all button-red"
+      onClick={[Function]}
+    >
+      marketplace.revert
+    </button>
+  </div>
+</div>
+`;
+
+exports[`should not display nothing 1`] = `null`;
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/PluginActions.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/PluginActions.tsx
new file mode 100644 (file)
index 0000000..f2fce9f
--- /dev/null
@@ -0,0 +1,166 @@
+/*
+ * 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 Checkbox from '../../../components/controls/Checkbox';
+import CheckIcon from '../../../components/icons-components/CheckIcon';
+import PluginUpdateButton from './PluginUpdateButton';
+import { Plugin, installPlugin, updatePlugin, uninstallPlugin } from '../../../api/plugins';
+import { isPluginAvailable, isPluginInstalled } from '../utils';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+  plugin: Plugin;
+  refreshPending: () => void;
+}
+
+interface State {
+  acceptTerms: boolean;
+  loading: boolean;
+}
+
+export default class PluginActions extends React.PureComponent<Props, State> {
+  mounted: boolean;
+  state: State = { acceptTerms: false, loading: false };
+
+  componentDidMount() {
+    this.mounted = true;
+  }
+
+  componentWillUnmount() {
+    this.mounted = false;
+  }
+
+  doPluginAction = (apiAction: (data: { key: string }) => Promise<void | Response>) => {
+    this.setState({ loading: true });
+    apiAction({ key: this.props.plugin.key }).then(
+      () => {
+        this.props.refreshPending();
+        if (this.mounted) {
+          this.setState({ loading: false });
+        }
+      },
+      () => {
+        if (this.mounted) {
+          this.setState({ loading: false });
+        }
+      }
+    );
+  };
+
+  handleInstall = () => this.doPluginAction(installPlugin);
+  handleUpdate = () => this.doPluginAction(updatePlugin);
+  handleUninstall = () => this.doPluginAction(uninstallPlugin);
+  handleTermsCheck = (checked: boolean) => this.setState({ acceptTerms: checked });
+
+  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 (
+      <div className="js-actions">
+        {isPluginAvailable(plugin) &&
+        plugin.termsAndConditionsUrl && (
+          <p className="little-spacer-bottom">
+            <Checkbox
+              checked={this.state.acceptTerms}
+              className="js-terms"
+              id={'plugin-terms-' + plugin.key}
+              onCheck={this.handleTermsCheck}>
+              <label className="little-spacer-left" htmlFor={'plugin-terms-' + plugin.key}>
+                {translate('marketplace.i_accept_the')}
+              </label>
+            </Checkbox>
+            <a
+              className="js-plugin-terms nowrap little-spacer-left"
+              href={plugin.termsAndConditionsUrl}
+              target="_blank">
+              {translate('marketplace.terms_and_conditions')}
+            </a>
+          </p>
+        )}
+        {loading && <i className="spinner spacer-right" />}
+        {isPluginInstalled(plugin) && (
+          <div className="button-group">
+            {plugin.updates &&
+              plugin.updates.map((update, idx) => (
+                <PluginUpdateButton
+                  key={idx}
+                  onClick={this.handleUpdate}
+                  update={update}
+                  disabled={loading}
+                />
+              ))}
+            <button
+              className="js-uninstall button-red"
+              disabled={loading}
+              onClick={this.handleUninstall}>
+              {translate('marketplace.uninstall')}
+            </button>
+          </div>
+        )}
+        {isPluginAvailable(plugin) && (
+          <button
+            className="js-install"
+            disabled={loading || (plugin.termsAndConditionsUrl != null && !this.state.acceptTerms)}
+            onClick={this.handleInstall}>
+            {translate('marketplace.install')}
+          </button>
+        )}
+      </div>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/PluginAvailable.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/PluginAvailable.tsx
new file mode 100644 (file)
index 0000000..a6badc5
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * 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 PluginChangeLogButton from './PluginChangeLogButton';
+import PluginDescription from './PluginDescription';
+import PluginLicense from './PluginLicense';
+import PluginOrganization from './PluginOrganization';
+import PluginStatus from './PluginStatus';
+import PluginUrls from './PluginUrls';
+import { PluginAvailable } from '../../../api/plugins';
+import { translateWithParameters } from '../../../helpers/l10n';
+import { Query } from '../utils';
+
+interface Props {
+  plugin: PluginAvailable;
+  refreshPending: () => void;
+  status?: string;
+  updateQuery: (newQuery: Partial<Query>) => void;
+}
+
+export default function PluginAvailable({ plugin, refreshPending, status, updateQuery }: Props) {
+  return (
+    <tr>
+      <PluginDescription plugin={plugin} updateQuery={updateQuery} />
+      <td className="text-top big-spacer-right">
+        <ul>
+          <li className="diplay-flex-row little-spacer-bottom">
+            <div className="pull-left spacer-right">
+              <span className="badge badge-success">{plugin.release.version}</span>
+            </div>
+            <div>
+              {plugin.release.description}
+              <PluginChangeLogButton release={plugin.release} update={plugin.update} />
+              {plugin.update.requires.length > 0 && (
+                <p className="little-spacer-top">
+                  <strong>
+                    {translateWithParameters(
+                      'marketplace.installing_this_plugin_will_also_install_x',
+                      plugin.update.requires.map(requiredPlugin => requiredPlugin.name).join(', ')
+                    )}
+                  </strong>
+                </p>
+              )}
+            </div>
+          </li>
+        </ul>
+      </td>
+
+      <td className="text-top width-20 big-spacer-right">
+        <ul>
+          <PluginUrls plugin={plugin} />
+          <PluginLicense license={plugin.license} />
+          <PluginOrganization plugin={plugin} />
+        </ul>
+      </td>
+
+      <PluginStatus plugin={plugin} status={status} refreshPending={refreshPending} />
+    </tr>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/PluginChangeLog.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/PluginChangeLog.tsx
new file mode 100644 (file)
index 0000000..8bf0eb6
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * 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 BubblePopup from '../../../components/common/BubblePopup';
+import PluginChangeLogItem from './PluginChangeLogItem';
+import { Release, Update } from '../../../api/plugins';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+  popupPosition?: any;
+  release: Release;
+  update: Update;
+}
+
+export default function PluginChangeLog({ popupPosition, release, update }: Props) {
+  return (
+    <BubblePopup position={popupPosition} customClass="bubble-popup-bottom-right">
+      <div className="abs-width-300 bubble-popup-container">
+        <div className="bubble-popup-title">{translate('changelog')}</div>
+        <ul className="js-plugin-changelog-list">
+          {update.previousUpdates &&
+            update.previousUpdates.map(
+              previousUpdate =>
+                previousUpdate.release ? (
+                  <PluginChangeLogItem
+                    key={previousUpdate.release.version}
+                    release={previousUpdate.release}
+                    update={previousUpdate}
+                  />
+                ) : null
+            )}
+          <PluginChangeLogItem release={release} update={update} />
+        </ul>
+      </div>
+    </BubblePopup>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/PluginChangeLogButton.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/PluginChangeLogButton.tsx
new file mode 100644 (file)
index 0000000..45269e4
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * 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 BubblePopupHelper from '../../../components/common/BubblePopupHelper';
+import PluginChangeLog from './PluginChangeLog';
+import { Release, Update } from '../../../api/plugins';
+
+interface Props {
+  release: Release;
+  update: Update;
+}
+
+interface State {
+  changelogOpen: boolean;
+}
+
+export default class PluginChangeLogButton extends React.PureComponent<Props, State> {
+  state: State = { changelogOpen: false };
+
+  handleChangelogClick = (event: React.SyntheticEvent<HTMLButtonElement>) => {
+    event.preventDefault();
+    event.stopPropagation();
+    this.toggleChangelog();
+  };
+
+  toggleChangelog = (show?: boolean) => {
+    if (show !== undefined) {
+      this.setState({ changelogOpen: show });
+    } else {
+      this.setState(state => ({ changelogOpen: !state.changelogOpen }));
+    }
+  };
+
+  render() {
+    return (
+      <div className="display-inline-block little-spacer-left">
+        <button
+          className="button-link js-changelog issue-rule icon-ellipsis-h"
+          onClick={this.handleChangelogClick}
+        />
+        <BubblePopupHelper
+          isOpen={this.state.changelogOpen}
+          position="bottomright"
+          popup={<PluginChangeLog release={this.props.release} update={this.props.update} />}
+          togglePopup={this.toggleChangelog}
+        />
+      </div>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/PluginChangeLogItem.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/PluginChangeLogItem.tsx
new file mode 100644 (file)
index 0000000..5093609
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * 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 DateFormatter from '../../../components/intl/DateFormatter';
+import Tooltip from '../../../components/controls/Tooltip';
+import { Release, Update } from '../../../api/plugins';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
+
+interface Props {
+  release: Release;
+  update: Update;
+}
+
+export default function PluginChangeLogItem({ release, update }: Props) {
+  return (
+    <li className="big-spacer-bottom">
+      <div className="little-spacer-bottom">
+        {update.status === 'COMPATIBLE' || !update.status ? (
+          <span className="js-plugin-changelog-version badge badge-success spacer-right">
+            {release.version}
+          </span>
+        ) : (
+          <Tooltip overlay={translateWithParameters('marketplace.status', update.status)}>
+            <span className="js-plugin-changelog-version badge badge-warning spacer-right">
+              {release.version}
+            </span>
+          </Tooltip>
+        )}
+        <span className="js-plugin-changelog-date note spacer-right">
+          <DateFormatter date={release.date} />
+        </span>
+        {release.changeLogUrl && (
+          <a className="js-plugin-changelog-link" href={release.changeLogUrl} target="_blank">
+            {translate('update_center.release_notes')}
+          </a>
+        )}
+      </div>
+      <div className="js-plugin-changelog-description">{release.description}</div>
+    </li>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/PluginDescription.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/PluginDescription.tsx
new file mode 100644 (file)
index 0000000..9ee5b5d
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * 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 { Plugin } from '../../../api/plugins';
+import { Query } from '../utils';
+
+interface Props {
+  plugin: Plugin;
+  updateQuery: (newQuery: Partial<Query>) => void;
+}
+
+export default class PluginDescription extends React.PureComponent<Props> {
+  handleCategoryClick = (e: React.SyntheticEvent<HTMLAnchorElement>) => {
+    e.preventDefault();
+    this.props.updateQuery({ search: this.props.plugin.category });
+  };
+
+  render() {
+    const { plugin } = this.props;
+    return (
+      <td className="text-top width-20 big-spacer-right">
+        <div>
+          <strong className="js-plugin-name">{plugin.name}</strong>
+          {plugin.category && (
+            <a
+              className="js-plugin-category badge spacer-left"
+              href="#"
+              onClick={this.handleCategoryClick}>
+              {plugin.category}
+            </a>
+          )}
+        </div>
+        <div className="js-plugin-description little-spacer-top">{plugin.description}</div>
+      </td>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/PluginInstalled.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/PluginInstalled.tsx
new file mode 100644 (file)
index 0000000..6dc6908
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * 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 PluginDescription from './PluginDescription';
+import PluginLicense from './PluginLicense';
+import PluginOrganization from './PluginOrganization';
+import PluginStatus from './PluginStatus';
+import PluginUpdates from './PluginUpdates';
+import PluginUrls from './PluginUrls';
+import { PluginInstalled } from '../../../api/plugins';
+import { translate } from '../../../helpers/l10n';
+import { Query } from '../utils';
+
+interface Props {
+  plugin: PluginInstalled;
+  refreshPending: () => void;
+  status?: string;
+  updateQuery: (newQuery: Partial<Query>) => void;
+}
+
+export default function PluginInstalled({ plugin, refreshPending, status, updateQuery }: Props) {
+  return (
+    <tr>
+      <PluginDescription plugin={plugin} updateQuery={updateQuery} />
+      <td className="text-top big-spacer-right">
+        <ul>
+          <li className="little-spacer-bottom">
+            <strong className="js-plugin-installed-version little-spacer-right">
+              {plugin.version}
+            </strong>
+            {translate('marketplace._installed')}
+          </li>
+          <PluginUpdates updates={plugin.updates} />
+        </ul>
+      </td>
+
+      <td className="text-top width-20 big-spacer-right">
+        <ul>
+          <PluginUrls plugin={plugin} />
+          <PluginLicense license={plugin.license} />
+          <PluginOrganization plugin={plugin} />
+        </ul>
+      </td>
+
+      <PluginStatus plugin={plugin} status={status} refreshPending={refreshPending} />
+    </tr>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/PluginLicense.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/PluginLicense.tsx
new file mode 100644 (file)
index 0000000..0ee76f8
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * 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 { FormattedMessage } from 'react-intl';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+  license?: string;
+}
+
+export default function PluginLicense({ license }: Props) {
+  if (!license) {
+    return null;
+  }
+  return (
+    <li className="little-spacer-bottom text-limited" title={license}>
+      <FormattedMessage
+        defaultMessage={translate('marketplace.licensed_under_x')}
+        id="marketplace.licensed_under_x"
+        values={{
+          license: <span className="js-plugin-license">{license}</span>
+        }}
+      />
+    </li>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/PluginOrganization.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/PluginOrganization.tsx
new file mode 100644 (file)
index 0000000..e992630
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * 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 { FormattedMessage } from 'react-intl';
+import { translate } from '../../../helpers/l10n';
+import { Plugin } from '../../../api/plugins';
+
+interface Props {
+  plugin: Plugin;
+}
+
+export default function PluginOrganization({ plugin }: Props) {
+  if (!plugin.organizationName) {
+    return null;
+  }
+  return (
+    <li className="little-spacer-bottom">
+      <FormattedMessage
+        defaultMessage={translate('marketplace.developed_by_x')}
+        id="marketplace.developed_by_x"
+        values={{
+          organization: plugin.organizationUrl ? (
+            <a className="js-plugin-organization" href={plugin.organizationUrl} target="_blank">
+              {plugin.organizationName}
+            </a>
+          ) : (
+            <span className="js-plugin-organization">{plugin.organizationName}</span>
+          )
+        }}
+      />
+    </li>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/PluginStatus.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/PluginStatus.tsx
new file mode 100644 (file)
index 0000000..d797cbb
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * 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 { Plugin } from '../../../api/plugins';
+import PluginActions from './PluginActions';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+  plugin: Plugin;
+  refreshPending: () => void;
+  status?: string;
+}
+
+export default function PluginStatus({ plugin, refreshPending, status }: Props) {
+  return (
+    <td className="text-top text-right width-20">
+      {status === 'installing' && (
+        <p className="text-success">{translate('marketplace.install_pending')}</p>
+      )}
+
+      {status === 'updating' && (
+        <p className="text-success">{translate('marketplace.update_pending')}</p>
+      )}
+
+      {status === 'removing' && (
+        <p className="text-danger">{translate('marketplace.uninstall_pending')}</p>
+      )}
+
+      {status == null && (
+        <div>
+          <i className="js-spinner spinner hidden" />
+          <PluginActions plugin={plugin} refreshPending={refreshPending} />
+        </div>
+      )}
+    </td>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/PluginUpdateButton.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/PluginUpdateButton.tsx
new file mode 100644 (file)
index 0000000..41028a6
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * 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 { Update } from '../../../api/plugins';
+import { translateWithParameters } from '../../../helpers/l10n';
+
+interface Props {
+  disabled: boolean;
+  onClick: (update: Update) => void;
+  update: Update;
+}
+
+export default class PluginUpdateButton extends React.PureComponent<Props> {
+  handleClick = () => this.props.onClick(this.props.update);
+
+  render() {
+    const { disabled, update } = this.props;
+    if (update.status !== 'COMPATIBLE' || !update.release) {
+      return null;
+    }
+    return (
+      <button className="js-update" onClick={this.handleClick} disabled={disabled}>
+        {translateWithParameters('marketplace.update_to_x', update.release.version)}
+      </button>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/PluginUpdateItem.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/PluginUpdateItem.tsx
new file mode 100644 (file)
index 0000000..453ab27
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * 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 PluginChangeLogButton from './PluginChangeLogButton';
+import Tooltip from '../../../components/controls/Tooltip';
+import { Release, Update } from '../../../api/plugins';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+  update: Update;
+  release: Release;
+}
+
+interface State {
+  changelogOpen: boolean;
+}
+
+export default class PluginUpdateItem extends React.PureComponent<Props, State> {
+  state: State = { changelogOpen: false };
+
+  handleChangelogClick = (event: React.SyntheticEvent<HTMLButtonElement>) => {
+    event.preventDefault();
+    event.stopPropagation();
+    this.toggleChangelog();
+  };
+
+  toggleChangelog = (show?: boolean) => {
+    if (show !== undefined) {
+      this.setState({ changelogOpen: show });
+    } else {
+      this.setState(state => ({ changelogOpen: !state.changelogOpen }));
+    }
+  };
+
+  render() {
+    const { release, update } = this.props;
+    return (
+      <li key={release.version} className="diplay-flex-row little-spacer-bottom">
+        <div className="pull-left spacer-right">
+          {update.status === 'COMPATIBLE' ? (
+            <span className="js-update-version badge badge-success">{release.version}</span>
+          ) : (
+            <Tooltip overlay={translate('marketplace.status', update.status)}>
+              <span className="js-update-version badge badge-warning">{release.version}</span>
+            </Tooltip>
+          )}
+        </div>
+        <div>
+          {release.description}
+          <PluginChangeLogButton release={release} update={update} />
+        </div>
+      </li>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/PluginUpdates.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/PluginUpdates.tsx
new file mode 100644 (file)
index 0000000..805495b
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * 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 PluginUpdateItem from './PluginUpdateItem';
+import { Update } from '../../../api/plugins';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+  updates?: Update[];
+}
+
+export default function PluginUpdates({ updates }: Props) {
+  if (!updates || updates.length <= 0) {
+    return null;
+  }
+  return (
+    <li className="spacer-top">
+      <strong>{translate('marketplace.updates')}:</strong>
+      <ul className="little-spacer-top">
+        {updates.map(
+          update =>
+            update.release ? (
+              <PluginUpdateItem
+                key={update.release.version}
+                release={update.release}
+                update={update}
+              />
+            ) : null
+        )}
+      </ul>
+    </li>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/PluginUrls.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/PluginUrls.tsx
new file mode 100644 (file)
index 0000000..576b891
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * 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 { Plugin } from '../../../api/plugins';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+  plugin: Plugin;
+}
+
+export default function PluginUrls({ plugin }: Props) {
+  if (!plugin.homepageUrl && !plugin.issueTrackerUrl) {
+    return null;
+  }
+  return (
+    <li className="little-spacer-bottom">
+      <ul className="list-inline">
+        {plugin.homepageUrl && (
+          <li>
+            <a className="js-plugin-homepage" href={plugin.homepageUrl} target="_blank">
+              {translate('marketplace.homepage')}
+            </a>
+          </li>
+        )}
+        {plugin.issueTrackerUrl && (
+          <li>
+            <a className="js-plugin-issues" href={plugin.issueTrackerUrl} target="_blank">
+              {translate('marketplace.issue_tracker')}
+            </a>
+          </li>
+        )}
+      </ul>
+    </li>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/PluginDescription-test.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/PluginDescription-test.tsx
new file mode 100644 (file)
index 0000000..0b525f2
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * 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 PluginDescription from '../PluginDescription';
+
+it('should display the description and category', () => {
+  expect(getWrapper()).toMatchSnapshot();
+});
+
+it('should not display any category', () => {
+  expect(
+    getWrapper({ plugin: { key: 'foo', name: 'Foo', description: 'foo description' } })
+  ).toMatchSnapshot();
+});
+
+it('should update query when clicking on category', () => {
+  const updateQuery = jest.fn();
+  const wrapper = getWrapper({ updateQuery });
+  click(wrapper.find('.js-plugin-category'));
+  expect(updateQuery).toHaveBeenCalledWith({ search: 'foocategory' });
+});
+
+function getWrapper(props = {}) {
+  return shallow(
+    <PluginDescription
+      plugin={{
+        key: 'foo',
+        name: 'Foo',
+        description: 'foo description',
+        category: 'foocategory'
+      }}
+      updateQuery={() => {}}
+      {...props}
+    />
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/PluginLicense-test.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/PluginLicense-test.tsx
new file mode 100644 (file)
index 0000000..1031b90
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * 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 PluginLicense from '../PluginLicense';
+
+it('should display the license field', () => {
+  expect(shallow(<PluginLicense license="SonarSource license" />)).toMatchSnapshot();
+});
+
+it('should not display anything', () => {
+  expect(shallow(<PluginLicense />)).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/PluginUrls-test.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/PluginUrls-test.tsx
new file mode 100644 (file)
index 0000000..cbddc53
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * 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 PluginUrls from '../PluginUrls';
+
+it('should display the urls', () => {
+  expect(getWrapper()).toMatchSnapshot();
+});
+
+it('should display only one url', () => {
+  expect(getWrapper({ issueTrackerUrl: undefined })).toMatchSnapshot();
+});
+
+it('should not display anything', () => {
+  expect(getWrapper({ homepageUrl: undefined, issueTrackerUrl: undefined })).toMatchSnapshot();
+});
+
+function getWrapper(plugin = {}) {
+  return shallow(
+    <PluginUrls
+      plugin={{
+        key: 'foo',
+        name: 'Foo',
+        description: 'foo description',
+        homepageUrl: 'homepageurl',
+        issueTrackerUrl: 'issuetrackerurl',
+        ...plugin
+      }}
+    />
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/PluginDescription-test.tsx.snap b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/PluginDescription-test.tsx.snap
new file mode 100644 (file)
index 0000000..6120da3
--- /dev/null
@@ -0,0 +1,46 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should display the description and category 1`] = `
+<td
+  className="text-top width-20 big-spacer-right"
+>
+  <div>
+    <strong
+      className="js-plugin-name"
+    >
+      Foo
+    </strong>
+    <a
+      className="js-plugin-category badge spacer-left"
+      href="#"
+      onClick={[Function]}
+    >
+      foocategory
+    </a>
+  </div>
+  <div
+    className="js-plugin-description little-spacer-top"
+  >
+    foo description
+  </div>
+</td>
+`;
+
+exports[`should not display any category 1`] = `
+<td
+  className="text-top width-20 big-spacer-right"
+>
+  <div>
+    <strong
+      className="js-plugin-name"
+    >
+      Foo
+    </strong>
+  </div>
+  <div
+    className="js-plugin-description little-spacer-top"
+  >
+    foo description
+  </div>
+</td>
+`;
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/PluginLicense-test.tsx.snap b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/PluginLicense-test.tsx.snap
new file mode 100644 (file)
index 0000000..f37ffec
--- /dev/null
@@ -0,0 +1,24 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should display the license field 1`] = `
+<li
+  className="little-spacer-bottom text-limited"
+  title="SonarSource license"
+>
+  <FormattedMessage
+    defaultMessage="marketplace.licensed_under_x"
+    id="marketplace.licensed_under_x"
+    values={
+      Object {
+        "license": <span
+          className="js-plugin-license"
+      >
+          SonarSource license
+      </span>,
+      }
+    }
+  />
+</li>
+`;
+
+exports[`should not display anything 1`] = `null`;
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/PluginUrls-test.tsx.snap b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/PluginUrls-test.tsx.snap
new file mode 100644 (file)
index 0000000..f1a0884
--- /dev/null
@@ -0,0 +1,52 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should display only one url 1`] = `
+<li
+  className="little-spacer-bottom"
+>
+  <ul
+    className="list-inline"
+  >
+    <li>
+      <a
+        className="js-plugin-homepage"
+        href="homepageurl"
+        target="_blank"
+      >
+        marketplace.homepage
+      </a>
+    </li>
+  </ul>
+</li>
+`;
+
+exports[`should display the urls 1`] = `
+<li
+  className="little-spacer-bottom"
+>
+  <ul
+    className="list-inline"
+  >
+    <li>
+      <a
+        className="js-plugin-homepage"
+        href="homepageurl"
+        target="_blank"
+      >
+        marketplace.homepage
+      </a>
+    </li>
+    <li>
+      <a
+        className="js-plugin-issues"
+        href="issuetrackerurl"
+        target="_blank"
+      >
+        marketplace.issue_tracker
+      </a>
+    </li>
+  </ul>
+</li>
+`;
+
+exports[`should not display anything 1`] = `null`;