@@ -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, |
@@ -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; | |||
} | |||
@@ -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; | |||
} | |||
@@ -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; | |||
} |
@@ -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; | |||
} | |||
@@ -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; |
@@ -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> | |||
))} |
@@ -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)} |
@@ -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> | |||
); |
@@ -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 { |
@@ -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 { |
@@ -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; |
@@ -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; |
@@ -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; |
@@ -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; |
@@ -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 { |
@@ -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; |
@@ -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 { |
@@ -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 { |
@@ -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; |
@@ -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' }, |
@@ -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} | |||
/> | |||
); | |||
} |
@@ -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> | |||
`; |
@@ -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 => ({ |
@@ -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 | |||
}; | |||
} |
@@ -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; | |||
} |