aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWouter Admiraal <wouter.admiraal@sonarsource.com>2020-06-15 13:06:06 +0200
committersonartech <sonartech@sonarsource.com>2020-06-19 20:04:42 +0000
commitb66618cf03868a56c3fee751a82c1de13c674e79 (patch)
treeaba2679c2de36bfd306950219db3049634641c4e
parent8cbfbbe99dab20d6ecf3477d64888e474dbe6428 (diff)
downloadsonarqube-b66618cf03868a56c3fee751a82c1de13c674e79.tar.gz
sonarqube-b66618cf03868a56c3fee751a82c1de13c674e79.zip
SONAR-13386 Don't show 'plugin will get installed' if it's already installed
-rw-r--r--server/sonar-web/src/main/js/api/plugins.ts74
-rw-r--r--server/sonar-web/src/main/js/app/components/AdminContainer.tsx5
-rw-r--r--server/sonar-web/src/main/js/app/components/AdminContext.tsx4
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/settings/PendingPluginsActionNotif.tsx5
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/App.tsx7
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/PluginsList.tsx25
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/PluginActions.tsx16
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/PluginAvailable.tsx18
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/PluginChangeLog.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/PluginChangeLogButton.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/PluginChangeLogItem.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/PluginDescription.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/PluginInstalled.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/PluginOrganization.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/PluginStatus.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/PluginUpdateButton.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/PluginUpdateItem.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/PluginUpdates.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/PluginUrls.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/__tests__/PluginActions-test.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/__tests__/PluginAvailable-test.tsx64
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/PluginAvailable-test.tsx.snap546
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/utils.ts14
-rw-r--r--server/sonar-web/src/main/js/helpers/mocks/plugins.ts68
-rw-r--r--server/sonar-web/src/main/js/types/plugins.ts84
26 files changed, 838 insertions, 126 deletions
diff --git a/server/sonar-web/src/main/js/api/plugins.ts b/server/sonar-web/src/main/js/api/plugins.ts
index 7a848e582ad..4e898155f52 100644
--- a/server/sonar-web/src/main/js/api/plugins.ts
+++ b/server/sonar-web/src/main/js/api/plugins.ts
@@ -21,68 +21,16 @@ import { findLastIndex } from 'lodash';
import { getJSON, post } from 'sonar-ui-common/helpers/request';
import { isDefined } from 'sonar-ui-common/helpers/types';
import throwGlobalError from '../app/utils/throwGlobalError';
-
-export interface Plugin {
- key: string;
- name: string;
- category?: string;
- description?: string;
- editionBundled?: boolean;
- license?: string;
- organizationName?: string;
- homepageUrl?: string;
- organizationUrl?: string;
- issueTrackerUrl?: string;
- termsAndConditionsUrl?: string;
-}
-
-export interface Release {
- version: string;
- date: string;
- description?: string;
- changeLogUrl?: string;
-}
-
-export interface Update {
- status: string;
- release?: Release;
- requires: Plugin[];
- previousUpdates?: Update[];
-}
-
-export interface PluginPendingResult {
- installing: PluginPending[];
- updating: PluginPending[];
- removing: PluginPending[];
-}
-
-export interface PluginAvailable extends Plugin {
- release: Release;
- update: Update;
-}
-
-export interface PluginPending extends Plugin {
- version: string;
- implementationBuild: string;
-}
-
-export interface PluginInstalled extends PluginPending {
- documentationPath?: string;
- filename: string;
- hash: string;
- sonarLintSupported: boolean;
- updatedAt: number;
- updates?: Update[];
-}
+import { AvailablePlugin, InstalledPlugin, PendingPluginResult, Update } from '../types/plugins';
export function getAvailablePlugins(): Promise<{
- plugins: PluginAvailable[];
+ plugins: AvailablePlugin[];
updateCenterRefresh: string;
}> {
return getJSON('/api/plugins/available').catch(throwGlobalError);
}
-export function getPendingPlugins(): Promise<PluginPendingResult> {
+export function getPendingPlugins(): Promise<PendingPluginResult> {
return getJSON('/api/plugins/pending').catch(throwGlobalError);
}
@@ -108,22 +56,22 @@ function addChangelog(update: Update, updates?: Update[]) {
return { ...update, previousUpdates };
}
-export function getInstalledPlugins(): Promise<PluginInstalled[]> {
+export function getInstalledPlugins(): Promise<InstalledPlugin[]> {
return getJSON('/api/plugins/installed', { f: 'category' }).then(
({ plugins }) => plugins,
throwGlobalError
);
}
-export function getInstalledPluginsWithUpdates(): Promise<PluginInstalled[]> {
+export function getInstalledPluginsWithUpdates(): Promise<InstalledPlugin[]> {
return Promise.all([
getJSON('/api/plugins/installed', { f: 'category' }),
getJSON('/api/plugins/updates')
])
.then(([installed, updates]) =>
- installed.plugins.map((plugin: PluginInstalled) => {
- const updatePlugin: PluginInstalled = updates.plugins.find(
- (p: PluginInstalled) => p.key === plugin.key
+ installed.plugins.map((plugin: InstalledPlugin) => {
+ const updatePlugin: InstalledPlugin = updates.plugins.find(
+ (p: InstalledPlugin) => p.key === plugin.key
);
if (updatePlugin) {
return {
@@ -140,14 +88,14 @@ export function getInstalledPluginsWithUpdates(): Promise<PluginInstalled[]> {
.catch(throwGlobalError);
}
-export function getPluginUpdates(): Promise<PluginInstalled[]> {
+export function getPluginUpdates(): Promise<InstalledPlugin[]> {
return Promise.all([getJSON('/api/plugins/updates'), getJSON('/api/plugins/installed')])
.then(([updates, installed]) =>
- updates.plugins.map((updatePlugin: PluginInstalled) => {
+ updates.plugins.map((updatePlugin: InstalledPlugin) => {
const updates = getLastUpdates(updatePlugin.updates).map(update =>
addChangelog(update, updatePlugin.updates)
);
- const plugin = installed.plugins.find((p: PluginInstalled) => p.key === updatePlugin.key);
+ const plugin = installed.plugins.find((p: InstalledPlugin) => p.key === updatePlugin.key);
if (plugin) {
return {
...plugin,
diff --git a/server/sonar-web/src/main/js/app/components/AdminContainer.tsx b/server/sonar-web/src/main/js/app/components/AdminContainer.tsx
index 43c2465e9e2..232a9fa9722 100644
--- a/server/sonar-web/src/main/js/app/components/AdminContainer.tsx
+++ b/server/sonar-web/src/main/js/app/components/AdminContainer.tsx
@@ -22,11 +22,12 @@ import { Helmet } from 'react-helmet-async';
import { connect } from 'react-redux';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { getSettingsNavigation } from '../../api/nav';
-import { getPendingPlugins, PluginPendingResult } from '../../api/plugins';
+import { getPendingPlugins } from '../../api/plugins';
import { getSystemStatus, waitSystemUPStatus } from '../../api/system';
import handleRequiredAuthorization from '../../app/utils/handleRequiredAuthorization';
import { setAdminPages } from '../../store/appState';
import { getAppState, Store } from '../../store/rootReducer';
+import { PendingPluginResult } from '../../types/plugins';
import AdminContext, { defaultPendingPlugins, defaultSystemStatus } from './AdminContext';
import SettingsNav from './nav/settings/SettingsNav';
@@ -37,7 +38,7 @@ interface Props {
}
interface State {
- pendingPlugins: PluginPendingResult;
+ pendingPlugins: PendingPluginResult;
systemStatus: T.SysStatus;
}
diff --git a/server/sonar-web/src/main/js/app/components/AdminContext.tsx b/server/sonar-web/src/main/js/app/components/AdminContext.tsx
index 5f41575bbc6..9b251813e27 100644
--- a/server/sonar-web/src/main/js/app/components/AdminContext.tsx
+++ b/server/sonar-web/src/main/js/app/components/AdminContext.tsx
@@ -18,12 +18,12 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { PluginPendingResult } from '../../api/plugins';
+import { PendingPluginResult } from '../../types/plugins';
export interface AdminContextInterface {
fetchSystemStatus: () => void;
fetchPendingPlugins: () => void;
- pendingPlugins: PluginPendingResult;
+ pendingPlugins: PendingPluginResult;
systemStatus: T.SysStatus;
}
diff --git a/server/sonar-web/src/main/js/app/components/nav/settings/PendingPluginsActionNotif.tsx b/server/sonar-web/src/main/js/app/components/nav/settings/PendingPluginsActionNotif.tsx
index 21db1cfde35..8b57882fab4 100644
--- a/server/sonar-web/src/main/js/app/components/nav/settings/PendingPluginsActionNotif.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/settings/PendingPluginsActionNotif.tsx
@@ -22,13 +22,14 @@ import { FormattedMessage } from 'react-intl';
import { Button } from 'sonar-ui-common/components/controls/buttons';
import { Alert } from 'sonar-ui-common/components/ui/Alert';
import { translate } from 'sonar-ui-common/helpers/l10n';
-import { cancelPendingPlugins, PluginPendingResult } from '../../../../api/plugins';
+import { cancelPendingPlugins } from '../../../../api/plugins';
import InstanceMessage from '../../../../components/common/InstanceMessage';
import RestartButton from '../../../../components/common/RestartButton';
+import { PendingPluginResult } from '../../../../types/plugins';
interface Props {
fetchSystemStatus: () => void;
- pending: PluginPendingResult;
+ pending: PendingPluginResult;
refreshPending: () => void;
systemStatus: T.SysStatus;
}
diff --git a/server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.tsx b/server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.tsx
index 854e32fda3d..5bbda188e0e 100644
--- a/server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.tsx
@@ -26,7 +26,7 @@ import ContextNavBar from 'sonar-ui-common/components/ui/ContextNavBar';
import NavBarTabs from 'sonar-ui-common/components/ui/NavBarTabs';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { getBaseUrl } from 'sonar-ui-common/helpers/urls';
-import { PluginPendingResult } from '../../../../api/plugins';
+import { PendingPluginResult } from '../../../../types/plugins';
import { rawSizes } from '../../../theme';
import PendingPluginsActionNotif from './PendingPluginsActionNotif';
import SystemRestartNotif from './SystemRestartNotif';
@@ -37,7 +37,7 @@ interface Props {
fetchSystemStatus: () => void;
location: {};
organizationsEnabled?: boolean;
- pendingPlugins: PluginPendingResult;
+ pendingPlugins: PendingPluginResult;
systemStatus: T.SysStatus;
}
diff --git a/server/sonar-web/src/main/js/apps/marketplace/App.tsx b/server/sonar-web/src/main/js/apps/marketplace/App.tsx
index 6f54f90f4c8..be0278ceb20 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/App.tsx
+++ b/server/sonar-web/src/main/js/apps/marketplace/App.tsx
@@ -25,13 +25,12 @@ import {
getAvailablePlugins,
getInstalledPlugins,
getInstalledPluginsWithUpdates,
- getPluginUpdates,
- Plugin,
- PluginPendingResult
+ getPluginUpdates
} from '../../api/plugins';
import Suggestions from '../../app/components/embed-docs-modal/Suggestions';
import { Location, Router, withRouter } from '../../components/hoc/withRouter';
import { EditionKey } from '../../types/editions';
+import { PendingPluginResult, Plugin } from '../../types/plugins';
import EditionBoxes from './EditionBoxes';
import Footer from './Footer';
import Header from './Header';
@@ -43,7 +42,7 @@ import { filterPlugins, parseQuery, Query, serializeQuery } from './utils';
interface Props {
currentEdition?: EditionKey;
fetchPendingPlugins: () => void;
- pendingPlugins: PluginPendingResult;
+ pendingPlugins: PendingPluginResult;
location: Location;
router: Pick<Router, 'push'>;
standaloneMode?: boolean;
diff --git a/server/sonar-web/src/main/js/apps/marketplace/PluginsList.tsx b/server/sonar-web/src/main/js/apps/marketplace/PluginsList.tsx
index 8d864fcdf59..4c1933ac587 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/PluginsList.tsx
+++ b/server/sonar-web/src/main/js/apps/marketplace/PluginsList.tsx
@@ -18,17 +18,22 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { Plugin, PluginPending } from '../../api/plugins';
+import {
+ InstalledPlugin,
+ isAvailablePlugin,
+ isInstalledPlugin,
+ PendingPlugin,
+ Plugin
+} from '../../types/plugins';
import PluginAvailable from './components/PluginAvailable';
import PluginInstalled from './components/PluginInstalled';
-import { isPluginAvailable, isPluginInstalled } from './utils';
interface Props {
plugins: Plugin[];
pending: {
- installing: PluginPending[];
- updating: PluginPending[];
- removing: PluginPending[];
+ installing: PendingPlugin[];
+ updating: PendingPlugin[];
+ removing: PendingPlugin[];
};
readOnly: boolean;
refreshPending: () => void;
@@ -49,9 +54,9 @@ export default class PluginsList extends React.PureComponent<Props> {
return undefined;
};
- renderPlugin = (plugin: Plugin) => {
+ renderPlugin = (plugin: Plugin, installedPlugins: InstalledPlugin[]) => {
const status = this.getPluginStatus(plugin);
- if (isPluginInstalled(plugin)) {
+ if (isInstalledPlugin(plugin)) {
return (
<PluginInstalled
plugin={plugin}
@@ -61,9 +66,10 @@ export default class PluginsList extends React.PureComponent<Props> {
/>
);
}
- if (isPluginAvailable(plugin)) {
+ if (isAvailablePlugin(plugin)) {
return (
<PluginAvailable
+ installedPlugins={installedPlugins}
plugin={plugin}
readOnly={this.props.readOnly}
refreshPending={this.props.refreshPending}
@@ -75,13 +81,14 @@ export default class PluginsList extends React.PureComponent<Props> {
};
render() {
+ const installedPlugins = this.props.plugins.filter(isInstalledPlugin);
return (
<div className="boxed-group boxed-group-inner" id="marketplace-plugins">
<ul>
{this.props.plugins.map(plugin => (
<li className="panel panel-vertical" key={plugin.key}>
<table className="marketplace-plugin-table">
- <tbody>{this.renderPlugin(plugin)}</tbody>
+ <tbody>{this.renderPlugin(plugin, installedPlugins)}</tbody>
</table>
</li>
))}
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
index 57b237d965f..06319354ad5 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/components/PluginActions.tsx
+++ b/server/sonar-web/src/main/js/apps/marketplace/components/PluginActions.tsx
@@ -22,8 +22,8 @@ import { Button } from 'sonar-ui-common/components/controls/buttons';
import Checkbox from 'sonar-ui-common/components/controls/Checkbox';
import CheckIcon from 'sonar-ui-common/components/icons/CheckIcon';
import { translate } from 'sonar-ui-common/helpers/l10n';
-import { installPlugin, Plugin, uninstallPlugin, updatePlugin } from '../../../api/plugins';
-import { isPluginAvailable, isPluginInstalled } from '../utils';
+import { installPlugin, uninstallPlugin, updatePlugin } from '../../../api/plugins';
+import { isAvailablePlugin, isInstalledPlugin, Plugin } from '../../../types/plugins';
import PluginUpdateButton from './PluginUpdateButton';
interface Props {
@@ -75,7 +75,7 @@ export default class PluginActions extends React.PureComponent<Props, State> {
return (
<div className="js-actions">
- {isPluginAvailable(plugin) && (
+ {isAvailablePlugin(plugin) && (
<div>
<p className="little-spacer-bottom">
{translate('marketplace.available_under_commercial_license')}
@@ -85,13 +85,13 @@ export default class PluginActions extends React.PureComponent<Props, State> {
</a>
</div>
)}
- {isPluginInstalled(plugin) && (
+ {isInstalledPlugin(plugin) && (
<p>
<CheckIcon className="little-spacer-right" />
{translate('marketplace.installed')}
</p>
)}
- {isPluginInstalled(plugin) && plugin.updates && plugin.updates.length > 0 && (
+ {isInstalledPlugin(plugin) && plugin.updates && plugin.updates.length > 0 && (
<div className="spacer-top">
{plugin.updates.map((update, idx) => (
<PluginUpdateButton
@@ -117,7 +117,7 @@ export default class PluginActions extends React.PureComponent<Props, State> {
const { loading } = this.state;
return (
<div className="js-actions">
- {isPluginAvailable(plugin) && plugin.termsAndConditionsUrl && (
+ {isAvailablePlugin(plugin) && plugin.termsAndConditionsUrl && (
<p className="little-spacer-bottom">
<Checkbox
checked={this.state.acceptTerms}
@@ -138,7 +138,7 @@ export default class PluginActions extends React.PureComponent<Props, State> {
</p>
)}
{loading && <i className="spinner spacer-right little-spacer-top little-spacer-bottom" />}
- {isPluginInstalled(plugin) && (
+ {isInstalledPlugin(plugin) && (
<div className="display-inlin-block">
{plugin.updates &&
plugin.updates.map((update, idx) => (
@@ -157,7 +157,7 @@ export default class PluginActions extends React.PureComponent<Props, State> {
</Button>
</div>
)}
- {isPluginAvailable(plugin) && (
+ {isAvailablePlugin(plugin) && (
<Button
className="js-install"
disabled={loading || (plugin.termsAndConditionsUrl != null && !this.state.acceptTerms)}
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
index 6b166a46eba..985e4212f9e 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/components/PluginAvailable.tsx
+++ b/server/sonar-web/src/main/js/apps/marketplace/components/PluginAvailable.tsx
@@ -19,7 +19,7 @@
*/
import * as React from 'react';
import { translateWithParameters } from 'sonar-ui-common/helpers/l10n';
-import { PluginAvailable as IPluginAvailable } from '../../../api/plugins';
+import { AvailablePlugin, InstalledPlugin } from '../../../types/plugins';
import PluginChangeLogButton from './PluginChangeLogButton';
import PluginDescription from './PluginDescription';
import PluginLicense from './PluginLicense';
@@ -27,14 +27,17 @@ import PluginOrganization from './PluginOrganization';
import PluginStatus from './PluginStatus';
import PluginUrls from './PluginUrls';
-interface Props {
- plugin: IPluginAvailable;
+export interface PluginAvailableProps {
+ installedPlugins: InstalledPlugin[];
+ plugin: AvailablePlugin;
readOnly: boolean;
refreshPending: () => void;
status?: string;
}
-export default function PluginAvailable({ plugin, readOnly, refreshPending, status }: Props) {
+export default function PluginAvailable(props: PluginAvailableProps) {
+ const { installedPlugins, plugin, readOnly, status } = props;
+ const installedPluginKeys = installedPlugins.map(({ key }) => key);
return (
<tr>
<PluginDescription plugin={plugin} />
@@ -52,7 +55,10 @@ export default function PluginAvailable({ plugin, readOnly, refreshPending, stat
<strong>
{translateWithParameters(
'marketplace.installing_this_plugin_will_also_install_x',
- plugin.update.requires.map(requiredPlugin => requiredPlugin.name).join(', ')
+ plugin.update.requires
+ .filter(({ key }) => !installedPluginKeys.includes(key))
+ .map(requiredPlugin => requiredPlugin.name)
+ .join(', ')
)}
</strong>
</p>
@@ -71,7 +77,7 @@ export default function PluginAvailable({ plugin, readOnly, refreshPending, stat
</td>
{!readOnly && (
- <PluginStatus plugin={plugin} refreshPending={refreshPending} status={status} />
+ <PluginStatus plugin={plugin} refreshPending={props.refreshPending} status={status} />
)}
</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
index 95fff94e48e..9891560e82d 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/components/PluginChangeLog.tsx
+++ b/server/sonar-web/src/main/js/apps/marketplace/components/PluginChangeLog.tsx
@@ -19,7 +19,7 @@
*/
import * as React from 'react';
import { translate } from 'sonar-ui-common/helpers/l10n';
-import { Release, Update } from '../../../api/plugins';
+import { Release, Update } from '../../../types/plugins';
import PluginChangeLogItem from './PluginChangeLogItem';
export interface Props {
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
index 5824380100a..402a4665ed4 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/components/PluginChangeLogButton.tsx
+++ b/server/sonar-web/src/main/js/apps/marketplace/components/PluginChangeLogButton.tsx
@@ -21,7 +21,7 @@ import * as React from 'react';
import { ButtonLink } from 'sonar-ui-common/components/controls/buttons';
import Dropdown from 'sonar-ui-common/components/controls/Dropdown';
import EllipsisIcon from 'sonar-ui-common/components/icons/EllipsisIcon';
-import { Release, Update } from '../../../api/plugins';
+import { Release, Update } from '../../../types/plugins';
import PluginChangeLog from './PluginChangeLog';
interface Props {
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
index 7e0da5e02ee..0e45bc31d72 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/components/PluginChangeLogItem.tsx
+++ b/server/sonar-web/src/main/js/apps/marketplace/components/PluginChangeLogItem.tsx
@@ -21,7 +21,7 @@ import * as React from 'react';
import Tooltip from 'sonar-ui-common/components/controls/Tooltip';
import DateFormatter from 'sonar-ui-common/components/intl/DateFormatter';
import { translate } from 'sonar-ui-common/helpers/l10n';
-import { Release, Update } from '../../../api/plugins';
+import { Release, Update } from '../../../types/plugins';
interface Props {
release: Release;
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
index e964a0a0e71..a099350e7a7 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/components/PluginDescription.tsx
+++ b/server/sonar-web/src/main/js/apps/marketplace/components/PluginDescription.tsx
@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { Plugin } from '../../../api/plugins';
+import { Plugin } from '../../../types/plugins';
interface Props {
plugin: Plugin;
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
index 7c3070e9804..e3859f7c25b 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/components/PluginInstalled.tsx
+++ b/server/sonar-web/src/main/js/apps/marketplace/components/PluginInstalled.tsx
@@ -19,7 +19,7 @@
*/
import * as React from 'react';
import { translate } from 'sonar-ui-common/helpers/l10n';
-import { PluginInstalled as IPluginInstalled } from '../../../api/plugins';
+import { InstalledPlugin } from '../../../types/plugins';
import PluginDescription from './PluginDescription';
import PluginLicense from './PluginLicense';
import PluginOrganization from './PluginOrganization';
@@ -28,7 +28,7 @@ import PluginUpdates from './PluginUpdates';
import PluginUrls from './PluginUrls';
interface Props {
- plugin: IPluginInstalled;
+ plugin: InstalledPlugin;
readOnly: boolean;
refreshPending: () => void;
status?: string;
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
index 204fbb5c216..b889cbd82bd 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/components/PluginOrganization.tsx
+++ b/server/sonar-web/src/main/js/apps/marketplace/components/PluginOrganization.tsx
@@ -20,7 +20,7 @@
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import { translate } from 'sonar-ui-common/helpers/l10n';
-import { Plugin } from '../../../api/plugins';
+import { Plugin } from '../../../types/plugins';
export interface PluginOrganizationProps {
plugin: Plugin;
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
index 41b4ddf0ee7..a7a603336d8 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/components/PluginStatus.tsx
+++ b/server/sonar-web/src/main/js/apps/marketplace/components/PluginStatus.tsx
@@ -19,7 +19,7 @@
*/
import * as React from 'react';
import { translate } from 'sonar-ui-common/helpers/l10n';
-import { Plugin } from '../../../api/plugins';
+import { Plugin } from '../../../types/plugins';
import PluginActions from './PluginActions';
interface Props {
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
index a7d094504b2..e34e3f37bad 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/components/PluginUpdateButton.tsx
+++ b/server/sonar-web/src/main/js/apps/marketplace/components/PluginUpdateButton.tsx
@@ -20,7 +20,7 @@
import * as React from 'react';
import { Button } from 'sonar-ui-common/components/controls/buttons';
import { translateWithParameters } from 'sonar-ui-common/helpers/l10n';
-import { Update } from '../../../api/plugins';
+import { Update } from '../../../types/plugins';
interface Props {
disabled: boolean;
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
index a1e1e36cfcb..f6ef8eff74e 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/components/PluginUpdateItem.tsx
+++ b/server/sonar-web/src/main/js/apps/marketplace/components/PluginUpdateItem.tsx
@@ -20,7 +20,7 @@
import * as React from 'react';
import Tooltip from 'sonar-ui-common/components/controls/Tooltip';
import { translate } from 'sonar-ui-common/helpers/l10n';
-import { Release, Update } from '../../../api/plugins';
+import { Release, Update } from '../../../types/plugins';
import PluginChangeLogButton from './PluginChangeLogButton';
interface Props {
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
index ab76582882a..0d6064d7e33 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/components/PluginUpdates.tsx
+++ b/server/sonar-web/src/main/js/apps/marketplace/components/PluginUpdates.tsx
@@ -19,7 +19,7 @@
*/
import * as React from 'react';
import { translate } from 'sonar-ui-common/helpers/l10n';
-import { Update } from '../../../api/plugins';
+import { Update } from '../../../types/plugins';
import PluginUpdateItem from './PluginUpdateItem';
interface Props {
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
index 6a13802c3f9..f5e20c2fdc7 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/components/PluginUrls.tsx
+++ b/server/sonar-web/src/main/js/apps/marketplace/components/PluginUrls.tsx
@@ -19,7 +19,7 @@
*/
import * as React from 'react';
import { translate } from 'sonar-ui-common/helpers/l10n';
-import { Plugin } from '../../../api/plugins';
+import { Plugin } from '../../../types/plugins';
interface Props {
plugin: Plugin;
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/PluginActions-test.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/PluginActions-test.tsx
index b549e44431c..2d2c245a9fa 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/PluginActions-test.tsx
+++ b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/PluginActions-test.tsx
@@ -19,10 +19,10 @@
*/
import { shallow } from 'enzyme';
import * as React from 'react';
-import { PluginAvailable, PluginInstalled } from '../../../../api/plugins';
+import { AvailablePlugin, InstalledPlugin } from '../../../../types/plugins';
import PluginActions from '../PluginActions';
-const installedPlugin: PluginInstalled = {
+const installedPlugin: InstalledPlugin = {
key: 'foo',
name: 'Foo',
filename: 'foo.zip',
@@ -35,7 +35,7 @@ const installedPlugin: PluginInstalled = {
version: '7.7'
};
-const availablePlugin: PluginAvailable = {
+const availablePlugin: AvailablePlugin = {
key: 'foo',
name: 'Foo',
release: { version: '7.7', date: 'date' },
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/PluginAvailable-test.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/PluginAvailable-test.tsx
new file mode 100644
index 00000000000..360592ea24c
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/PluginAvailable-test.tsx
@@ -0,0 +1,64 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import {
+ mockAvailablePlugin,
+ mockInstalledPlugin,
+ mockPlugin,
+ mockUpdate
+} from '../../../../helpers/mocks/plugins';
+import PluginAvailable, { PluginAvailableProps } from '../PluginAvailable';
+
+it('should render correctly', () => {
+ expect(shallowRender()).toMatchSnapshot('default');
+ expect(shallowRender({ readOnly: true })).toMatchSnapshot('read only');
+ expect(
+ shallowRender({
+ plugin: mockAvailablePlugin({
+ update: mockUpdate({ requires: [mockPlugin()] })
+ })
+ })
+ ).toMatchSnapshot('has requirements');
+ const installed = mockInstalledPlugin({ key: 'sonar-bar', name: 'Sonar Bar' });
+ expect(
+ shallowRender({
+ installedPlugins: [installed],
+ plugin: mockAvailablePlugin({
+ update: mockUpdate({
+ requires: [mockPlugin(), installed]
+ })
+ })
+ })
+ ).toMatchSnapshot('has requirements, some of them already met');
+});
+
+function shallowRender(props: Partial<PluginAvailableProps> = {}) {
+ return shallow<PluginAvailableProps>(
+ <PluginAvailable
+ installedPlugins={[]}
+ plugin={mockAvailablePlugin()}
+ readOnly={false}
+ refreshPending={jest.fn()}
+ {...props}
+ />
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/PluginAvailable-test.tsx.snap b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/PluginAvailable-test.tsx.snap
new file mode 100644
index 00000000000..9222f0091d3
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/PluginAvailable-test.tsx.snap
@@ -0,0 +1,546 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly: default 1`] = `
+<tr>
+ <PluginDescription
+ plugin={
+ Object {
+ "key": "sonar-foo",
+ "name": "Sonar Foo",
+ "release": Object {
+ "date": "2020-01-01",
+ "version": "8.2",
+ },
+ "update": Object {
+ "requires": Array [],
+ "status": "available",
+ },
+ }
+ }
+ />
+ <td
+ className="text-top big-spacer-right"
+ >
+ <ul>
+ <li
+ className="display-flex-row little-spacer-bottom"
+ >
+ <div
+ className="pull-left spacer-right"
+ >
+ <span
+ className="badge badge-success"
+ >
+ 8.2
+ </span>
+ </div>
+ <div>
+ <PluginChangeLogButton
+ release={
+ Object {
+ "date": "2020-01-01",
+ "version": "8.2",
+ }
+ }
+ update={
+ Object {
+ "requires": Array [],
+ "status": "available",
+ }
+ }
+ />
+ </div>
+ </li>
+ </ul>
+ </td>
+ <td
+ className="text-top width-20"
+ >
+ <ul>
+ <PluginUrls
+ plugin={
+ Object {
+ "key": "sonar-foo",
+ "name": "Sonar Foo",
+ "release": Object {
+ "date": "2020-01-01",
+ "version": "8.2",
+ },
+ "update": Object {
+ "requires": Array [],
+ "status": "available",
+ },
+ }
+ }
+ />
+ <PluginLicense />
+ <PluginOrganization
+ plugin={
+ Object {
+ "key": "sonar-foo",
+ "name": "Sonar Foo",
+ "release": Object {
+ "date": "2020-01-01",
+ "version": "8.2",
+ },
+ "update": Object {
+ "requires": Array [],
+ "status": "available",
+ },
+ }
+ }
+ />
+ </ul>
+ </td>
+ <PluginStatus
+ plugin={
+ Object {
+ "key": "sonar-foo",
+ "name": "Sonar Foo",
+ "release": Object {
+ "date": "2020-01-01",
+ "version": "8.2",
+ },
+ "update": Object {
+ "requires": Array [],
+ "status": "available",
+ },
+ }
+ }
+ refreshPending={[MockFunction]}
+ />
+</tr>
+`;
+
+exports[`should render correctly: has requirements 1`] = `
+<tr>
+ <PluginDescription
+ plugin={
+ Object {
+ "key": "sonar-foo",
+ "name": "Sonar Foo",
+ "release": Object {
+ "date": "2020-01-01",
+ "version": "8.2",
+ },
+ "update": Object {
+ "requires": Array [
+ Object {
+ "key": "sonar-foo",
+ "name": "Sonar Foo",
+ },
+ ],
+ "status": "available",
+ },
+ }
+ }
+ />
+ <td
+ className="text-top big-spacer-right"
+ >
+ <ul>
+ <li
+ className="display-flex-row little-spacer-bottom"
+ >
+ <div
+ className="pull-left spacer-right"
+ >
+ <span
+ className="badge badge-success"
+ >
+ 8.2
+ </span>
+ </div>
+ <div>
+ <PluginChangeLogButton
+ release={
+ Object {
+ "date": "2020-01-01",
+ "version": "8.2",
+ }
+ }
+ update={
+ Object {
+ "requires": Array [
+ Object {
+ "key": "sonar-foo",
+ "name": "Sonar Foo",
+ },
+ ],
+ "status": "available",
+ }
+ }
+ />
+ <p
+ className="little-spacer-top"
+ >
+ <strong>
+ marketplace.installing_this_plugin_will_also_install_x.Sonar Foo
+ </strong>
+ </p>
+ </div>
+ </li>
+ </ul>
+ </td>
+ <td
+ className="text-top width-20"
+ >
+ <ul>
+ <PluginUrls
+ plugin={
+ Object {
+ "key": "sonar-foo",
+ "name": "Sonar Foo",
+ "release": Object {
+ "date": "2020-01-01",
+ "version": "8.2",
+ },
+ "update": Object {
+ "requires": Array [
+ Object {
+ "key": "sonar-foo",
+ "name": "Sonar Foo",
+ },
+ ],
+ "status": "available",
+ },
+ }
+ }
+ />
+ <PluginLicense />
+ <PluginOrganization
+ plugin={
+ Object {
+ "key": "sonar-foo",
+ "name": "Sonar Foo",
+ "release": Object {
+ "date": "2020-01-01",
+ "version": "8.2",
+ },
+ "update": Object {
+ "requires": Array [
+ Object {
+ "key": "sonar-foo",
+ "name": "Sonar Foo",
+ },
+ ],
+ "status": "available",
+ },
+ }
+ }
+ />
+ </ul>
+ </td>
+ <PluginStatus
+ plugin={
+ Object {
+ "key": "sonar-foo",
+ "name": "Sonar Foo",
+ "release": Object {
+ "date": "2020-01-01",
+ "version": "8.2",
+ },
+ "update": Object {
+ "requires": Array [
+ Object {
+ "key": "sonar-foo",
+ "name": "Sonar Foo",
+ },
+ ],
+ "status": "available",
+ },
+ }
+ }
+ refreshPending={[MockFunction]}
+ />
+</tr>
+`;
+
+exports[`should render correctly: has requirements, some of them already met 1`] = `
+<tr>
+ <PluginDescription
+ plugin={
+ Object {
+ "key": "sonar-foo",
+ "name": "Sonar Foo",
+ "release": Object {
+ "date": "2020-01-01",
+ "version": "8.2",
+ },
+ "update": Object {
+ "requires": Array [
+ Object {
+ "key": "sonar-foo",
+ "name": "Sonar Foo",
+ },
+ Object {
+ "filename": "sonar-bar-1.0.jar",
+ "hash": "hash",
+ "implementationBuild": "1.0.0.1234",
+ "key": "sonar-bar",
+ "name": "Sonar Bar",
+ "sonarLintSupported": false,
+ "updatedAt": 100,
+ "version": "1.0",
+ },
+ ],
+ "status": "available",
+ },
+ }
+ }
+ />
+ <td
+ className="text-top big-spacer-right"
+ >
+ <ul>
+ <li
+ className="display-flex-row little-spacer-bottom"
+ >
+ <div
+ className="pull-left spacer-right"
+ >
+ <span
+ className="badge badge-success"
+ >
+ 8.2
+ </span>
+ </div>
+ <div>
+ <PluginChangeLogButton
+ release={
+ Object {
+ "date": "2020-01-01",
+ "version": "8.2",
+ }
+ }
+ update={
+ Object {
+ "requires": Array [
+ Object {
+ "key": "sonar-foo",
+ "name": "Sonar Foo",
+ },
+ Object {
+ "filename": "sonar-bar-1.0.jar",
+ "hash": "hash",
+ "implementationBuild": "1.0.0.1234",
+ "key": "sonar-bar",
+ "name": "Sonar Bar",
+ "sonarLintSupported": false,
+ "updatedAt": 100,
+ "version": "1.0",
+ },
+ ],
+ "status": "available",
+ }
+ }
+ />
+ <p
+ className="little-spacer-top"
+ >
+ <strong>
+ marketplace.installing_this_plugin_will_also_install_x.Sonar Foo
+ </strong>
+ </p>
+ </div>
+ </li>
+ </ul>
+ </td>
+ <td
+ className="text-top width-20"
+ >
+ <ul>
+ <PluginUrls
+ plugin={
+ Object {
+ "key": "sonar-foo",
+ "name": "Sonar Foo",
+ "release": Object {
+ "date": "2020-01-01",
+ "version": "8.2",
+ },
+ "update": Object {
+ "requires": Array [
+ Object {
+ "key": "sonar-foo",
+ "name": "Sonar Foo",
+ },
+ Object {
+ "filename": "sonar-bar-1.0.jar",
+ "hash": "hash",
+ "implementationBuild": "1.0.0.1234",
+ "key": "sonar-bar",
+ "name": "Sonar Bar",
+ "sonarLintSupported": false,
+ "updatedAt": 100,
+ "version": "1.0",
+ },
+ ],
+ "status": "available",
+ },
+ }
+ }
+ />
+ <PluginLicense />
+ <PluginOrganization
+ plugin={
+ Object {
+ "key": "sonar-foo",
+ "name": "Sonar Foo",
+ "release": Object {
+ "date": "2020-01-01",
+ "version": "8.2",
+ },
+ "update": Object {
+ "requires": Array [
+ Object {
+ "key": "sonar-foo",
+ "name": "Sonar Foo",
+ },
+ Object {
+ "filename": "sonar-bar-1.0.jar",
+ "hash": "hash",
+ "implementationBuild": "1.0.0.1234",
+ "key": "sonar-bar",
+ "name": "Sonar Bar",
+ "sonarLintSupported": false,
+ "updatedAt": 100,
+ "version": "1.0",
+ },
+ ],
+ "status": "available",
+ },
+ }
+ }
+ />
+ </ul>
+ </td>
+ <PluginStatus
+ plugin={
+ Object {
+ "key": "sonar-foo",
+ "name": "Sonar Foo",
+ "release": Object {
+ "date": "2020-01-01",
+ "version": "8.2",
+ },
+ "update": Object {
+ "requires": Array [
+ Object {
+ "key": "sonar-foo",
+ "name": "Sonar Foo",
+ },
+ Object {
+ "filename": "sonar-bar-1.0.jar",
+ "hash": "hash",
+ "implementationBuild": "1.0.0.1234",
+ "key": "sonar-bar",
+ "name": "Sonar Bar",
+ "sonarLintSupported": false,
+ "updatedAt": 100,
+ "version": "1.0",
+ },
+ ],
+ "status": "available",
+ },
+ }
+ }
+ refreshPending={[MockFunction]}
+ />
+</tr>
+`;
+
+exports[`should render correctly: read only 1`] = `
+<tr>
+ <PluginDescription
+ plugin={
+ Object {
+ "key": "sonar-foo",
+ "name": "Sonar Foo",
+ "release": Object {
+ "date": "2020-01-01",
+ "version": "8.2",
+ },
+ "update": Object {
+ "requires": Array [],
+ "status": "available",
+ },
+ }
+ }
+ />
+ <td
+ className="text-top big-spacer-right"
+ >
+ <ul>
+ <li
+ className="display-flex-row little-spacer-bottom"
+ >
+ <div
+ className="pull-left spacer-right"
+ >
+ <span
+ className="badge badge-success"
+ >
+ 8.2
+ </span>
+ </div>
+ <div>
+ <PluginChangeLogButton
+ release={
+ Object {
+ "date": "2020-01-01",
+ "version": "8.2",
+ }
+ }
+ update={
+ Object {
+ "requires": Array [],
+ "status": "available",
+ }
+ }
+ />
+ </div>
+ </li>
+ </ul>
+ </td>
+ <td
+ className="text-top width-20"
+ >
+ <ul>
+ <PluginUrls
+ plugin={
+ Object {
+ "key": "sonar-foo",
+ "name": "Sonar Foo",
+ "release": Object {
+ "date": "2020-01-01",
+ "version": "8.2",
+ },
+ "update": Object {
+ "requires": Array [],
+ "status": "available",
+ },
+ }
+ }
+ />
+ <PluginLicense />
+ <PluginOrganization
+ plugin={
+ Object {
+ "key": "sonar-foo",
+ "name": "Sonar Foo",
+ "release": Object {
+ "date": "2020-01-01",
+ "version": "8.2",
+ },
+ "update": Object {
+ "requires": Array [],
+ "status": "available",
+ },
+ }
+ }
+ />
+ </ul>
+ </td>
+</tr>
+`;
diff --git a/server/sonar-web/src/main/js/apps/marketplace/utils.ts b/server/sonar-web/src/main/js/apps/marketplace/utils.ts
index 4aa1ad8b878..db6ed4743ee 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/utils.ts
+++ b/server/sonar-web/src/main/js/apps/marketplace/utils.ts
@@ -19,7 +19,7 @@
*/
import { memoize } from 'lodash';
import { cleanQuery, parseAsString, serializeString } from 'sonar-ui-common/helpers/query';
-import { Plugin, PluginAvailable, PluginInstalled, PluginPending } from '../../api/plugins';
+import { Plugin } from '../../types/plugins';
export interface Query {
filter: string;
@@ -43,18 +43,6 @@ export function filterPlugins(plugins: Plugin[], search?: string): Plugin[] {
});
}
-export function isPluginAvailable(plugin: Plugin): plugin is PluginAvailable {
- return (plugin as any).release !== undefined;
-}
-
-export function isPluginInstalled(plugin: Plugin): plugin is PluginInstalled {
- return isPluginPending(plugin) && (plugin as any).updatedAt !== undefined;
-}
-
-export function isPluginPending(plugin: Plugin): plugin is PluginPending {
- return (plugin as any).version !== undefined;
-}
-
export const DEFAULT_FILTER = 'all';
export const parseQuery = memoize(
(urlQuery: T.RawQuery): Query => ({
diff --git a/server/sonar-web/src/main/js/helpers/mocks/plugins.ts b/server/sonar-web/src/main/js/helpers/mocks/plugins.ts
new file mode 100644
index 00000000000..bdaf34528d1
--- /dev/null
+++ b/server/sonar-web/src/main/js/helpers/mocks/plugins.ts
@@ -0,0 +1,68 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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 { AvailablePlugin, InstalledPlugin, Plugin, Release, Update } from '../../types/plugins';
+
+export function mockPlugin(overrides: Partial<Plugin> = {}): Plugin {
+ return {
+ key: 'sonar-foo',
+ name: 'Sonar Foo',
+ ...overrides
+ };
+}
+
+export function mockInstalledPlugin(overrides: Partial<InstalledPlugin> = {}): InstalledPlugin {
+ return {
+ key: 'sonar-bar',
+ name: 'Sonar Bar',
+ version: '1.0',
+ implementationBuild: '1.0.0.1234',
+ filename: 'sonar-bar-1.0.jar',
+ hash: 'hash',
+ sonarLintSupported: false,
+ updatedAt: 100,
+ ...overrides
+ };
+}
+
+export function mockAvailablePlugin(overrides: Partial<AvailablePlugin> = {}): AvailablePlugin {
+ return {
+ release: mockRelease(),
+ update: mockUpdate(),
+ ...mockPlugin(),
+ ...overrides
+ };
+}
+
+export function mockRelease(overrides: Partial<Release> = {}): Release {
+ return {
+ date: '2020-01-01',
+ version: '8.2',
+ ...overrides
+ };
+}
+
+export function mockUpdate(overrides: Partial<Update> = {}): Update {
+ return {
+ status: 'available',
+ requires: [],
+ ...overrides
+ };
+}
diff --git a/server/sonar-web/src/main/js/types/plugins.ts b/server/sonar-web/src/main/js/types/plugins.ts
new file mode 100644
index 00000000000..bea645264d1
--- /dev/null
+++ b/server/sonar-web/src/main/js/types/plugins.ts
@@ -0,0 +1,84 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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.
+ */
+
+export interface Plugin {
+ key: string;
+ name: string;
+ category?: string;
+ description?: string;
+ editionBundled?: boolean;
+ license?: string;
+ organizationName?: string;
+ homepageUrl?: string;
+ organizationUrl?: string;
+ issueTrackerUrl?: string;
+ termsAndConditionsUrl?: string;
+}
+
+export interface PendingPluginResult {
+ installing: PendingPlugin[];
+ updating: PendingPlugin[];
+ removing: PendingPlugin[];
+}
+
+export interface AvailablePlugin extends Plugin {
+ release: Release;
+ update: Update;
+}
+
+export interface PendingPlugin extends Plugin {
+ version: string;
+ implementationBuild: string;
+}
+
+export interface InstalledPlugin extends PendingPlugin {
+ documentationPath?: string;
+ filename: string;
+ hash: string;
+ sonarLintSupported: boolean;
+ updatedAt: number;
+ updates?: Update[];
+}
+
+export interface Release {
+ version: string;
+ date: string;
+ description?: string;
+ changeLogUrl?: string;
+}
+
+export interface Update {
+ status: string;
+ release?: Release;
+ requires: Plugin[];
+ previousUpdates?: Update[];
+}
+
+export function isAvailablePlugin(plugin: Plugin): plugin is AvailablePlugin {
+ return (plugin as any).release !== undefined;
+}
+
+export function isInstalledPlugin(plugin: Plugin): plugin is InstalledPlugin {
+ return isPendingPlugin(plugin) && (plugin as any).updatedAt !== undefined;
+}
+
+export function isPendingPlugin(plugin: Plugin): plugin is PendingPlugin {
+ return (plugin as any).version !== undefined;
+}