import UninstallEditionForm from './components/UninstallEditionForm';
import { Edition, EditionStatus } from '../../api/marketplace';
import { translate } from '../../helpers/l10n';
+import { sortEditions } from './utils';
export interface Props {
canInstall: boolean;
handleOpenUninstallForm = () => this.setState({ openUninstallForm: true });
handleCloseUninstallForm = () => this.setState({ openUninstallForm: false });
+ renderForms(sortedEditions: Edition[], installedIdx?: number) {
+ const { canInstall, canUninstall, editionStatus } = this.props;
+ const { installEdition, openUninstallForm } = this.state;
+ const installEditionIdx =
+ installEdition && sortedEditions.findIndex(edition => edition.key === installEdition.key);
+
+ if (canInstall && installEdition) {
+ return (
+ <LicenseEditionForm
+ edition={installEdition}
+ editions={sortedEditions}
+ isDowngrade={
+ installedIdx !== undefined &&
+ installEditionIdx !== undefined &&
+ installEditionIdx < installedIdx
+ }
+ onClose={this.handleCloseLicenseForm}
+ updateEditionStatus={this.props.updateEditionStatus}
+ />
+ );
+ }
+
+ if (canUninstall && openUninstallForm && editionStatus && editionStatus.currentEditionKey) {
+ return (
+ <UninstallEditionForm
+ edition={sortedEditions.find(edition => edition.key === editionStatus.currentEditionKey)}
+ editionStatus={editionStatus}
+ onClose={this.handleCloseUninstallForm}
+ updateEditionStatus={this.props.updateEditionStatus}
+ />
+ );
+ }
+
+ return null;
+ }
+
render() {
const { canInstall, canUninstall, editions, editionStatus, loading } = this.props;
- const { installEdition, openUninstallForm } = this.state;
+
if (loading) {
return <i className="big-spacer-bottom spinner" />;
}
);
}
+ const sortedEditions = sortEditions(editions);
+ const installedIdx =
+ editionStatus &&
+ sortedEditions.findIndex(edition => edition.key === editionStatus.currentEditionKey);
return (
<div className="spacer-bottom marketplace-editions">
- {editions.map(edition => (
+ {sortedEditions.map((edition, idx) => (
<EditionBox
canInstall={canInstall}
canUninstall={canUninstall}
edition={edition}
editionStatus={editionStatus}
+ isDowngrade={installedIdx !== undefined && idx < installedIdx}
key={edition.key}
onInstall={this.handleOpenLicenseForm}
onUninstall={this.handleOpenUninstallForm}
/>
))}
- {canInstall &&
- installEdition && (
- <LicenseEditionForm
- edition={installEdition}
- editions={editions}
- onClose={this.handleCloseLicenseForm}
- updateEditionStatus={this.props.updateEditionStatus}
- />
- )}
-
- {canUninstall &&
- openUninstallForm &&
- editionStatus &&
- editionStatus.currentEditionKey && (
- <UninstallEditionForm
- edition={editions.find(edition => edition.key === editionStatus.currentEditionKey)}
- editionStatus={editionStatus}
- onClose={this.handleCloseUninstallForm}
- updateEditionStatus={this.props.updateEditionStatus}
- />
- )}
+ {this.renderForms(sortedEditions, installedIdx)}
</div>
);
}
import { EditionStatus } from '../../../api/marketplace';
const DEFAULT_STATUS: EditionStatus = {
- currentEditionKey: 'foo',
+ currentEditionKey: 'developer',
nextEditionKey: '',
installationStatus: 'NONE'
};
const DEFAULT_EDITIONS = [
{
- key: 'foo',
- name: 'Foo',
- textDescription: 'Foo desc',
+ key: 'developer',
+ name: 'Developer Edition',
+ textDescription: 'foo',
downloadUrl: 'download_url',
homeUrl: 'more_url',
requestUrl: 'license_url'
},
{
- key: 'bar',
- name: 'Bar',
- textDescription: 'Bar desc',
+ key: 'comunity',
+ name: 'Comunity Edition',
+ textDescription: 'bar',
downloadUrl: 'download_url',
homeUrl: 'more_url',
requestUrl: 'license_url'
}
];
-it('should display the edition boxes', () => {
+it('should display the edition boxes correctly', () => {
const wrapper = getWrapper({ editions: DEFAULT_EDITIONS, loading: true });
expect(wrapper).toMatchSnapshot();
wrapper.setProps({ loading: false });
</div>
`;
-exports[`should display the edition boxes 1`] = `
+exports[`should display the edition boxes correctly 1`] = `
<i
className="big-spacer-bottom spinner"
/>
`;
-exports[`should display the edition boxes 2`] = `
+exports[`should display the edition boxes correctly 2`] = `
<div
className="spacer-bottom marketplace-editions"
>
Object {
"downloadUrl": "download_url",
"homeUrl": "more_url",
- "key": "foo",
- "name": "Foo",
+ "key": "comunity",
+ "name": "Comunity Edition",
"requestUrl": "license_url",
- "textDescription": "Foo desc",
+ "textDescription": "bar",
}
}
editionStatus={
Object {
- "currentEditionKey": "foo",
+ "currentEditionKey": "developer",
"installationStatus": "NONE",
"nextEditionKey": "",
}
}
+ isDowngrade={true}
onInstall={[Function]}
onUninstall={[Function]}
/>
Object {
"downloadUrl": "download_url",
"homeUrl": "more_url",
- "key": "bar",
- "name": "Bar",
+ "key": "developer",
+ "name": "Developer Edition",
"requestUrl": "license_url",
- "textDescription": "Bar desc",
+ "textDescription": "foo",
}
}
editionStatus={
Object {
- "currentEditionKey": "foo",
+ "currentEditionKey": "developer",
"installationStatus": "NONE",
"nextEditionKey": "",
}
}
+ isDowngrade={false}
onInstall={[Function]}
onUninstall={[Function]}
/>
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import CheckIcon from '../../../components/icons-components/CheckIcon';
+import EditionBoxBadge from './EditionBoxBadge';
import { Edition, EditionStatus } from '../../../api/marketplace';
import { translate } from '../../../helpers/l10n';
canUninstall: boolean;
edition: Edition;
editionStatus?: EditionStatus;
+ isDowngrade?: boolean;
onInstall: (edition: Edition) => void;
onUninstall: () => void;
}
export default class EditionBox extends React.PureComponent<Props> {
handleInstall = () => this.props.onInstall(this.props.edition);
- renderBadge(isInstalled?: boolean, installInProgress?: boolean) {
- const { edition, editionStatus } = this.props;
- const installReady = editionStatus && editionStatus.installationStatus === 'AUTOMATIC_READY';
- const isInstalling =
- installInProgress && editionStatus && editionStatus.nextEditionKey === edition.key;
- if (isInstalling) {
+ renderActions(isInstalled?: boolean, installInProgress?: boolean) {
+ const { canInstall, canUninstall, editionStatus } = this.props;
+ const uninstallInProgress =
+ editionStatus && editionStatus.installationStatus === 'UNINSTALL_IN_PROGRESS';
+
+ if (canInstall && !isInstalled) {
return (
- <span className="marketplace-edition-badge badge badge-normal-size">
- {installReady ? translate('marketplace.pending') : translate('marketplace.installing')}
- </span>
+ <button disabled={installInProgress || uninstallInProgress} onClick={this.handleInstall}>
+ {this.props.isDowngrade ? (
+ translate('marketplace.downgrade')
+ ) : (
+ translate('marketplace.upgrade')
+ )}
+ </button>
);
}
- if (isInstalled) {
+ if (canUninstall && isInstalled) {
return (
- <span className="marketplace-edition-badge badge badge-normal-size">
- <CheckIcon size={14} className="little-spacer-right text-bottom" />
- {translate('marketplace.installed')}
- </span>
+ <button
+ className="button-red"
+ disabled={installInProgress || uninstallInProgress}
+ onClick={this.props.onUninstall}>
+ {translate('marketplace.uninstall')}
+ </button>
);
}
+
return null;
}
render() {
- const { canInstall, canUninstall, edition, editionStatus } = this.props;
+ const { edition, editionStatus } = this.props;
const isInstalled = editionStatus && editionStatus.currentEditionKey === edition.key;
- const uninstallInProgress =
- editionStatus && editionStatus.installationStatus === 'UNINSTALL_IN_PROGRESS';
const installInProgress =
editionStatus &&
['AUTOMATIC_IN_PROGRESS', 'AUTOMATIC_READY'].includes(editionStatus.installationStatus);
return (
<div className="boxed-group boxed-group-inner marketplace-edition">
- {this.renderBadge(isInstalled, installInProgress)}
+ {editionStatus && <EditionBoxBadge editionKey={edition.key} status={editionStatus} />}
<div>
<h3 className="spacer-bottom">{edition.name}</h3>
<p>{edition.textDescription}</p>
<a href={edition.homeUrl} target="_blank">
{translate('marketplace.learn_more')}
</a>
- {canUninstall &&
- isInstalled && (
- <button
- className="button-red"
- disabled={installInProgress || uninstallInProgress}
- onClick={this.props.onUninstall}>
- {translate('marketplace.uninstall')}
- </button>
- )}
- {canInstall &&
- !isInstalled && (
- <button
- disabled={installInProgress || uninstallInProgress}
- onClick={this.handleInstall}>
- {translate('marketplace.install')}
- </button>
- )}
+ {this.renderActions(isInstalled, installInProgress)}
</div>
</div>
);
--- /dev/null
+/*
+ * 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 CheckIcon from '../../../components/icons-components/CheckIcon';
+import { EditionStatus } from '../../../api/marketplace';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+ editionKey: string;
+ status: EditionStatus;
+}
+
+export default function EditionBoxBadge({ editionKey, status }: Props) {
+ const inProgress = ['AUTOMATIC_IN_PROGRESS', 'AUTOMATIC_READY'].includes(
+ status.installationStatus
+ );
+ const isInstalling = inProgress && status.nextEditionKey === editionKey;
+
+ if (isInstalling) {
+ const installReady = status.installationStatus === 'AUTOMATIC_READY';
+ return (
+ <span className="marketplace-edition-badge badge badge-normal-size">
+ {installReady ? translate('marketplace.pending') : translate('marketplace.installing')}
+ </span>
+ );
+ }
+
+ const isInstalled = status.currentEditionKey === editionKey;
+ if (isInstalled) {
+ return (
+ <span className="marketplace-edition-badge badge badge-normal-size">
+ <CheckIcon size={14} className="little-spacer-right text-bottom" />
+ {translate('marketplace.installed')}
+ </span>
+ );
+ }
+
+ return null;
+}
export interface Props {
edition: Edition;
editions: Edition[];
+ isDowngrade: boolean;
onClose: () => void;
updateEditionStatus: (editionStatus: EditionStatus) => void;
}
};
render() {
- const { edition } = this.props;
+ const { edition, isDowngrade } = this.props;
const { submitting, status } = this.state;
- const header = translateWithParameters('marketplace.install_x', edition.name);
+
+ const header = isDowngrade
+ ? translateWithParameters('marketplace.downgrade_to_x', edition.name)
+ : translateWithParameters('marketplace.upgrade_to_x', edition.name);
return (
<Modal
isOpen={true}
expect(getWrapper()).toMatchSnapshot();
});
-it('should display installed badge', () => {
+it('should disable upgrade button', () => {
expect(
getWrapper({
editionStatus: {
- currentEditionKey: 'foo',
- nextEditionKey: '',
- installationStatus: 'NONE'
- }
- })
- ).toMatchSnapshot();
-});
-
-it('should display installing badge', () => {
- expect(
- getWrapper({
- editionStatus: {
- currentEditionKey: 'foo',
+ currentEditionKey: '',
nextEditionKey: 'foo',
installationStatus: 'AUTOMATIC_IN_PROGRESS'
}
})
).toMatchSnapshot();
-});
-
-it('should display pending badge', () => {
expect(
getWrapper({
editionStatus: {
).toMatchSnapshot();
});
-it('should disable install button', () => {
+it('should disable uninstall button', () => {
expect(
getWrapper({
editionStatus: {
- currentEditionKey: '',
+ currentEditionKey: 'foo',
nextEditionKey: 'foo',
installationStatus: 'AUTOMATIC_IN_PROGRESS'
}
})
).toMatchSnapshot();
- expect(
- getWrapper({
- editionStatus: {
- currentEditionKey: '',
- nextEditionKey: 'foo',
- installationStatus: 'AUTOMATIC_READY'
- }
- })
- ).toMatchSnapshot();
});
-it('should disable uninstall button', () => {
+it('should correctly hide upgrade/uninstall buttons', () => {
+ expect(getWrapper({ canInstall: false })).toMatchSnapshot();
expect(
getWrapper({
+ canUninstall: false,
editionStatus: {
currentEditionKey: 'foo',
- nextEditionKey: 'foo',
- installationStatus: 'AUTOMATIC_IN_PROGRESS'
+ nextEditionKey: '',
+ installationStatus: 'NONE'
}
})
).toMatchSnapshot();
});
+it('should display a downgrade button', () => {
+ expect(getWrapper({ isDowngrade: true })).toMatchSnapshot();
+});
+
function getWrapper(props = {}) {
return shallow(
<EditionBox
--- /dev/null
+/*
+ * 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 { EditionStatus } from '../../../../api/marketplace';
+import EditionBoxBadge from '../EditionBoxBadge';
+
+const DEFAULT_STATUS: EditionStatus = {
+ currentEditionKey: '',
+ nextEditionKey: '',
+ installationStatus: 'NONE'
+};
+
+it('should display installed badge', () => {
+ expect(
+ getWrapper({
+ editionStatus: {
+ currentEditionKey: 'foo',
+ nextEditionKey: '',
+ installationStatus: 'NONE'
+ }
+ })
+ ).toMatchSnapshot();
+});
+
+it('should display installing badge', () => {
+ expect(
+ getWrapper({
+ editionStatus: {
+ currentEditionKey: 'foo',
+ nextEditionKey: 'foo',
+ installationStatus: 'AUTOMATIC_IN_PROGRESS'
+ }
+ })
+ ).toMatchSnapshot();
+});
+
+it('should display pending badge', () => {
+ expect(
+ getWrapper({
+ editionStatus: {
+ currentEditionKey: '',
+ nextEditionKey: 'foo',
+ installationStatus: 'AUTOMATIC_READY'
+ }
+ })
+ ).toMatchSnapshot();
+});
+
+it('should not display a badge', () => {
+ expect(
+ getWrapper({
+ editionStatus: {
+ currentEditionKey: '',
+ nextEditionKey: 'bar',
+ installationStatus: 'AUTOMATIC_READY'
+ }
+ })
+ ).toMatchSnapshot();
+});
+
+function getWrapper(props = {}) {
+ return shallow(<EditionBoxBadge editionKey="foo" status={DEFAULT_STATUS} {...props} />);
+}
<LicenseEditionForm
edition={DEFAULT_EDITION}
editions={[DEFAULT_EDITION]}
+ isDowngrade={false}
onClose={jest.fn()}
updateEditionStatus={jest.fn()}
{...props}
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`should disable install button 1`] = `
+exports[`should correctly hide upgrade/uninstall buttons 1`] = `
<div
className="boxed-group boxed-group-inner marketplace-edition"
>
- <span
- className="marketplace-edition-badge badge badge-normal-size"
- >
- marketplace.installing
- </span>
+ <EditionBoxBadge
+ editionKey="foo"
+ status={
+ Object {
+ "currentEditionKey": "",
+ "installationStatus": "NONE",
+ "nextEditionKey": "",
+ }
+ }
+ />
<div>
<h3
className="spacer-bottom"
>
marketplace.learn_more
</a>
- <button
- disabled={true}
- onClick={[Function]}
- >
- marketplace.install
- </button>
</div>
</div>
`;
-exports[`should disable install button 2`] = `
+exports[`should correctly hide upgrade/uninstall buttons 2`] = `
<div
className="boxed-group boxed-group-inner marketplace-edition"
>
- <span
- className="marketplace-edition-badge badge badge-normal-size"
- >
- marketplace.pending
- </span>
+ <EditionBoxBadge
+ editionKey="foo"
+ status={
+ Object {
+ "currentEditionKey": "foo",
+ "installationStatus": "NONE",
+ "nextEditionKey": "",
+ }
+ }
+ />
<div>
<h3
className="spacer-bottom"
>
marketplace.learn_more
</a>
- <button
- disabled={true}
- onClick={[Function]}
- >
- marketplace.install
- </button>
</div>
</div>
`;
<div
className="boxed-group boxed-group-inner marketplace-edition"
>
- <span
- className="marketplace-edition-badge badge badge-normal-size"
- >
- marketplace.installing
- </span>
+ <EditionBoxBadge
+ editionKey="foo"
+ status={
+ Object {
+ "currentEditionKey": "foo",
+ "installationStatus": "AUTOMATIC_IN_PROGRESS",
+ "nextEditionKey": "foo",
+ }
+ }
+ />
<div>
<h3
className="spacer-bottom"
</div>
`;
-exports[`should display installed badge 1`] = `
+exports[`should disable upgrade button 1`] = `
<div
className="boxed-group boxed-group-inner marketplace-edition"
>
- <span
- className="marketplace-edition-badge badge badge-normal-size"
- >
- <CheckIcon
- className="little-spacer-right text-bottom"
- size={14}
- />
- marketplace.installed
- </span>
+ <EditionBoxBadge
+ editionKey="foo"
+ status={
+ Object {
+ "currentEditionKey": "",
+ "installationStatus": "AUTOMATIC_IN_PROGRESS",
+ "nextEditionKey": "foo",
+ }
+ }
+ />
<div>
<h3
className="spacer-bottom"
marketplace.learn_more
</a>
<button
- className="button-red"
- disabled={false}
+ disabled={true}
onClick={[Function]}
>
- marketplace.uninstall
+ marketplace.upgrade
</button>
</div>
</div>
`;
-exports[`should display installing badge 1`] = `
+exports[`should disable upgrade button 2`] = `
<div
className="boxed-group boxed-group-inner marketplace-edition"
>
- <span
- className="marketplace-edition-badge badge badge-normal-size"
- >
- marketplace.installing
- </span>
+ <EditionBoxBadge
+ editionKey="foo"
+ status={
+ Object {
+ "currentEditionKey": "",
+ "installationStatus": "AUTOMATIC_READY",
+ "nextEditionKey": "foo",
+ }
+ }
+ />
<div>
<h3
className="spacer-bottom"
marketplace.learn_more
</a>
<button
- className="button-red"
disabled={true}
onClick={[Function]}
>
- marketplace.uninstall
+ marketplace.upgrade
</button>
</div>
</div>
`;
-exports[`should display pending badge 1`] = `
+exports[`should display a downgrade button 1`] = `
<div
className="boxed-group boxed-group-inner marketplace-edition"
>
- <span
- className="marketplace-edition-badge badge badge-normal-size"
- >
- marketplace.pending
- </span>
+ <EditionBoxBadge
+ editionKey="foo"
+ status={
+ Object {
+ "currentEditionKey": "",
+ "installationStatus": "NONE",
+ "nextEditionKey": "",
+ }
+ }
+ />
<div>
<h3
className="spacer-bottom"
marketplace.learn_more
</a>
<button
- disabled={true}
+ disabled={false}
onClick={[Function]}
>
- marketplace.install
+ marketplace.downgrade
</button>
</div>
</div>
<div
className="boxed-group boxed-group-inner marketplace-edition"
>
+ <EditionBoxBadge
+ editionKey="foo"
+ status={
+ Object {
+ "currentEditionKey": "",
+ "installationStatus": "NONE",
+ "nextEditionKey": "",
+ }
+ }
+ />
<div>
<h3
className="spacer-bottom"
disabled={false}
onClick={[Function]}
>
- marketplace.install
+ marketplace.upgrade
</button>
</div>
</div>
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should display installed badge 1`] = `null`;
+
+exports[`should display installing badge 1`] = `null`;
+
+exports[`should display pending badge 1`] = `null`;
+
+exports[`should not display a badge 1`] = `null`;
bodyOpenClassName="ReactModal__Body--open"
className="modal"
closeTimeoutMS={0}
- contentLabel="marketplace.install_x.Foo"
+ contentLabel="marketplace.upgrade_to_x.Foo"
isOpen={true}
onRequestClose={[Function]}
overlayClassName="modal-overlay"
className="modal-head"
>
<h2>
- marketplace.install_x.Foo
+ marketplace.upgrade_to_x.Foo
</h2>
</header>
<LicenseEditionSet
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { memoize } from 'lodash';
+import { memoize, sortBy } from 'lodash';
import { Plugin, PluginAvailable, PluginInstalled, PluginPending } from '../../api/plugins';
import { cleanQuery, parseAsString, RawQuery, serializeString } from '../../helpers/query';
+import { Edition } from '../../api/marketplace';
export interface Query {
filter: string;
search?: string;
}
-export const DEFAULT_FILTER = 'all';
-
export function isPluginAvailable(plugin: Plugin): plugin is PluginAvailable {
return (plugin as any).release !== undefined;
}
});
}
+const EDITIONS_ORDER = ['community', 'developer', 'enterprise', 'datacenter'];
+export function sortEditions(editions: Edition[]): Edition[] {
+ return sortBy(editions, edition => EDITIONS_ORDER.indexOf(edition.key));
+}
+
+export const DEFAULT_FILTER = 'all';
export const parseQuery = memoize((urlQuery: RawQuery): Query => ({
filter: parseAsString(urlQuery['filter']) || DEFAULT_FILTER,
search: parseAsString(urlQuery['search'])
marketplace.restart=Restart
marketplace.revert=Revert
marketplace.install=Install
-marketplace.install_x=Install {0}
+marketplace.upgrade_to_x=Upgrade to {0}
+marketplace.downgrade_to_x=Downgrade to {0}
marketplace.installed=Installed
marketplace.installing=Installing...
+marketplace.upgrade=Upgrade
+marketplace.downgrade=Downgrade
marketplace.uninstall_x=Uninstall {0}
marketplace.uninstall_x_confirmation=Are you sure you want to uninstall {0} and get back to the Community Edition?
marketplace.pending=Pending...