aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js/apps/marketplace
diff options
context:
space:
mode:
Diffstat (limited to 'server/sonar-web/src/main/js/apps/marketplace')
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/App.tsx98
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/AppContainer.tsx42
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/EditionBoxes.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/__tests__/EditionBoxes-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/EditionsStatusNotif.tsx150
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/__tests__/EditionsStatusNotif-test.tsx84
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/EditionsStatusNotif-test.tsx.snap61
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/utils.ts31
8 files changed, 51 insertions, 419 deletions
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 c57f3de78fd..4d6f63fd285 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/App.tsx
+++ b/server/sonar-web/src/main/js/apps/marketplace/App.tsx
@@ -22,7 +22,6 @@ import * as PropTypes from 'prop-types';
import { sortBy, uniqBy } from 'lodash';
import Helmet from 'react-helmet';
import Header from './Header';
-import EditionsStatusNotif from './components/EditionsStatusNotif';
import EditionBoxes from './EditionBoxes';
import Footer from './Footer';
import PendingActions from './PendingActions';
@@ -36,31 +35,24 @@ import {
Plugin,
PluginPending
} from '../../api/plugins';
-import { Edition, EditionStatus, getEditionsList, getEditionStatus } from '../../api/marketplace';
+import { Edition, EditionStatus } from '../../api/marketplace';
import { RawQuery } from '../../helpers/query';
import { translate } from '../../helpers/l10n';
-import {
- getEditionsForLastVersion,
- getEditionsForVersion,
- filterPlugins,
- parseQuery,
- Query,
- serializeQuery
-} from './utils';
+import { filterPlugins, parseQuery, Query, serializeQuery } from './utils';
+import './style.css';
export interface Props {
- editionsUrl: string;
+ editions?: Edition[];
+ editionsReadOnly: boolean;
+ editionStatus?: EditionStatus;
+ loadingEditions: boolean;
location: { pathname: string; query: RawQuery };
- sonarqubeVersion: string;
standaloneMode: boolean;
updateCenterActive: boolean;
+ setEditionStatus: (editionStatus: EditionStatus) => void;
}
interface State {
- editions?: Edition[];
- editionsReadOnly: boolean;
- editionStatus?: EditionStatus;
- loadingEditions: boolean;
loadingPlugins: boolean;
pending: {
installing: PluginPending[];
@@ -72,7 +64,6 @@ interface State {
export default class App extends React.PureComponent<Props, State> {
mounted: boolean;
- timer?: NodeJS.Timer;
static contextTypes = {
router: PropTypes.object.isRequired
@@ -81,8 +72,6 @@ export default class App extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
- editionsReadOnly: false,
- loadingEditions: true,
loadingPlugins: true,
pending: {
installing: [],
@@ -95,9 +84,7 @@ export default class App extends React.PureComponent<Props, State> {
componentDidMount() {
this.mounted = true;
- this.fetchEditions();
this.fetchPendingPlugins();
- this.fetchEditionStatus();
this.fetchQueryPlugins();
}
@@ -154,55 +141,6 @@ export default class App extends React.PureComponent<Props, State> {
() => {}
);
- fetchEditionStatus = () =>
- getEditionStatus().then(
- editionStatus => {
- if (this.mounted) {
- this.updateEditionStatus(editionStatus);
- }
- },
- () => {}
- );
-
- fetchEditions = () => {
- this.setState({ loadingEditions: true });
- getEditionsList(this.props.editionsUrl).then(
- editionsPerVersion => {
- if (this.mounted) {
- const newState = {
- editions: getEditionsForVersion(editionsPerVersion, this.props.sonarqubeVersion),
- editionsReadOnly: false,
- loadingEditions: false
- };
- if (!newState.editions) {
- newState.editions = getEditionsForLastVersion(editionsPerVersion);
- newState.editionsReadOnly = true;
- }
- this.setState(newState);
- }
- },
- () => {
- if (this.mounted) {
- this.setState({ loadingEditions: false });
- }
- }
- );
- };
-
- updateEditionStatus = (editionStatus: EditionStatus) => {
- this.setState({ editionStatus });
- if (this.timer) {
- global.clearTimeout(this.timer);
- this.timer = undefined;
- }
- if (editionStatus.installationStatus === 'AUTOMATIC_IN_PROGRESS') {
- this.timer = global.setTimeout(() => {
- this.fetchEditionStatus();
- this.timer = undefined;
- }, 2000);
- }
- };
-
updateQuery = (newQuery: Partial<Query>) => {
const query = serializeQuery({ ...parseQuery(this.props.location.query), ...newQuery });
this.context.router.push({ pathname: this.props.location.pathname, query });
@@ -215,8 +153,8 @@ export default class App extends React.PureComponent<Props, State> {
};
render() {
- const { standaloneMode } = this.props;
- const { editions, editionStatus, loadingPlugins, plugins, pending } = this.state;
+ const { editions, editionStatus, standaloneMode } = this.props;
+ const { loadingPlugins, plugins, pending } = this.state;
const query = parseQuery(this.props.location.query);
const filteredPlugins = query.search ? filterPlugins(plugins, query.search) : plugins;
@@ -224,14 +162,6 @@ export default class App extends React.PureComponent<Props, State> {
<div className="page page-limited" id="marketplace-page">
<Helmet title={translate('marketplace.page')} />
<div className="page-notifs">
- {editionStatus && (
- <EditionsStatusNotif
- editions={editions}
- editionStatus={editionStatus}
- readOnly={!standaloneMode}
- updateEditionStatus={this.updateEditionStatus}
- />
- )}
{standaloneMode && (
<PendingActions refreshPending={this.fetchPendingPlugins} pending={pending} />
)}
@@ -239,13 +169,11 @@ export default class App extends React.PureComponent<Props, State> {
<Header />
<EditionBoxes
editions={editions}
- loading={this.state.loadingEditions}
+ loading={this.props.loadingEditions}
editionStatus={editionStatus}
- editionsUrl={this.props.editionsUrl}
- readOnly={!standaloneMode || this.state.editionsReadOnly}
- sonarqubeVersion={this.props.sonarqubeVersion}
+ readOnly={!standaloneMode || this.props.editionsReadOnly}
updateCenterActive={this.props.updateCenterActive}
- updateEditionStatus={this.updateEditionStatus}
+ updateEditionStatus={this.props.setEditionStatus}
/>
<Search
query={query}
diff --git a/server/sonar-web/src/main/js/apps/marketplace/AppContainer.tsx b/server/sonar-web/src/main/js/apps/marketplace/AppContainer.tsx
index 90fac954650..6d2b46b010d 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/AppContainer.tsx
+++ b/server/sonar-web/src/main/js/apps/marketplace/AppContainer.tsx
@@ -19,15 +19,47 @@
*/
import { connect } from 'react-redux';
import App from './App';
-import { getAppState, getGlobalSettingValue } from '../../store/rootReducer';
-import './style.css';
+import {
+ getAppState,
+ getGlobalSettingValue,
+ getMarketplaceState,
+ getMarketplaceEditions,
+ getMarketplaceEditionStatus
+} from '../../store/rootReducer';
+import { Edition, EditionStatus } from '../../api/marketplace';
+import { setEditionStatus } from '../../store/marketplace/actions';
+import { RawQuery } from '../../helpers/query';
+
+interface OwnProps {
+ location: { pathname: string; query: RawQuery };
+}
+
+interface StateToProps {
+ editions?: Edition[];
+ editionsReadOnly: boolean;
+ editionStatus?: EditionStatus;
+ loadingEditions: boolean;
+ standaloneMode: boolean;
+ updateCenterActive: boolean;
+}
+
+interface DispatchToProps {
+ setEditionStatus: (editionStatus: EditionStatus) => void;
+}
const mapStateToProps = (state: any) => ({
- editionsUrl: (getGlobalSettingValue(state, 'sonar.editions.jsonUrl') || {}).value,
- sonarqubeVersion: getAppState(state).version,
+ editions: getMarketplaceEditions(state),
+ editionsReadOnly: getMarketplaceState(state).readOnly,
+ editionStatus: getMarketplaceEditionStatus(state),
+ loadingEditions: getMarketplaceState(state).loading,
standaloneMode: getAppState(state).standalone,
updateCenterActive:
(getGlobalSettingValue(state, 'sonar.updatecenter.activate') || {}).value === 'true'
});
-export default connect(mapStateToProps)(App as any);
+const mapDispatchToProps = { setEditionStatus };
+
+export default connect<StateToProps, DispatchToProps, OwnProps>(
+ mapStateToProps,
+ mapDispatchToProps
+)(App);
diff --git a/server/sonar-web/src/main/js/apps/marketplace/EditionBoxes.tsx b/server/sonar-web/src/main/js/apps/marketplace/EditionBoxes.tsx
index e9b7fa6f246..30370c1d3b2 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/EditionBoxes.tsx
+++ b/server/sonar-web/src/main/js/apps/marketplace/EditionBoxes.tsx
@@ -28,10 +28,8 @@ import { translate } from '../../helpers/l10n';
export interface Props {
editions?: Edition[];
editionStatus?: EditionStatus;
- editionsUrl: string;
loading: boolean;
readOnly: boolean;
- sonarqubeVersion: string;
updateCenterActive: boolean;
updateEditionStatus: (editionStatus: EditionStatus) => void;
}
diff --git a/server/sonar-web/src/main/js/apps/marketplace/__tests__/EditionBoxes-test.tsx b/server/sonar-web/src/main/js/apps/marketplace/__tests__/EditionBoxes-test.tsx
index eeff50797ce..c0347bf9ad4 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/__tests__/EditionBoxes-test.tsx
+++ b/server/sonar-web/src/main/js/apps/marketplace/__tests__/EditionBoxes-test.tsx
@@ -70,9 +70,7 @@ function getWrapper(props = {}) {
<EditionBoxes
loading={false}
editionStatus={DEFAULT_STATUS}
- editionsUrl=""
readOnly={false}
- sonarqubeVersion="6.7.5"
updateCenterActive={true}
updateEditionStatus={jest.fn()}
{...props}
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/EditionsStatusNotif.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/EditionsStatusNotif.tsx
deleted file mode 100644
index f21e8a30588..00000000000
--- a/server/sonar-web/src/main/js/apps/marketplace/components/EditionsStatusNotif.tsx
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import * as React from 'react';
-import RestartForm from '../../../components/common/RestartForm';
-import CloseIcon from '../../../components/icons-components/CloseIcon';
-import { dismissErrorMessage, Edition, EditionStatus } from '../../../api/marketplace';
-import { translate, translateWithParameters } from '../../../helpers/l10n';
-
-interface Props {
- editions?: Edition[];
- editionStatus: EditionStatus;
- readOnly: boolean;
- updateEditionStatus: (editionStatus: EditionStatus) => void;
-}
-
-interface State {
- openRestart: boolean;
-}
-
-export default class EditionsStatusNotif extends React.PureComponent<Props, State> {
- state: State = { openRestart: false };
-
- handleOpenRestart = () => this.setState({ openRestart: true });
- hanleCloseRestart = () => this.setState({ openRestart: false });
-
- handleDismissError = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
- event.preventDefault();
- dismissErrorMessage().then(
- () =>
- this.props.updateEditionStatus({ ...this.props.editionStatus, installError: undefined }),
- () => {}
- );
- };
-
- renderRestartMsg(edition?: Edition) {
- const { editionStatus, readOnly } = this.props;
- return (
- <div className="alert alert-success">
- <span>
- {edition ? (
- translateWithParameters(
- 'marketplace.status_x.' + editionStatus.installationStatus,
- edition.name
- )
- ) : (
- translate('marketplace.status', editionStatus.installationStatus)
- )}
- </span>
- {!readOnly && (
- <button className="js-restart spacer-left" onClick={this.handleOpenRestart}>
- {translate('marketplace.restart')}
- </button>
- )}
- {!readOnly && this.state.openRestart && <RestartForm onClose={this.hanleCloseRestart} />}
- </div>
- );
- }
-
- renderManualMsg(edition?: Edition) {
- const { editionStatus } = this.props;
- return (
- <div className="alert alert-danger">
- {edition ? (
- translateWithParameters(
- 'marketplace.status_x.' + editionStatus.installationStatus,
- edition.name
- )
- ) : (
- translate('marketplace.status', editionStatus.installationStatus)
- )}
- <p className="spacer-left">
- {edition && (
- <a
- className="button spacer-right"
- download={`sonarqube-${edition.name}.zip`}
- href={edition.downloadUrl}
- target="_blank">
- {translate('marketplace.download_package')}
- </a>
- )}
- <a
- href="https://redirect.sonarsource.com/doc/how-to-install-an-edition.html"
- target="_blank">
- {translate('marketplace.how_to_install')}
- </a>
- </p>
- <a className="little-spacer-left" href="https://www.sonarsource.com" target="_blank">
- {translate('marketplace.how_to_install')}
- </a>
- </div>
- );
- }
-
- renderStatusAlert() {
- const { editionStatus } = this.props;
- const { installationStatus, nextEditionKey } = editionStatus;
- const nextEdition =
- this.props.editions && this.props.editions.find(edition => edition.key === nextEditionKey);
-
- switch (installationStatus) {
- case 'AUTOMATIC_IN_PROGRESS':
- return (
- <div className="alert alert-info">
- <i className="spinner spacer-right text-bottom" />
- <span>{translate('marketplace.status.AUTOMATIC_IN_PROGRESS')}</span>
- </div>
- );
- case 'AUTOMATIC_READY':
- case 'UNINSTALL_IN_PROGRESS':
- return this.renderRestartMsg(nextEdition);
- case 'MANUAL_IN_PROGRESS':
- return this.renderManualMsg(nextEdition);
- }
- return null;
- }
-
- render() {
- const { installError } = this.props.editionStatus;
- return (
- <div>
- {installError && (
- <div className="alert alert-danger alert-cancel">
- {installError}
- <a className="button-link text-danger" href="#" onClick={this.handleDismissError}>
- <CloseIcon />
- </a>
- </div>
- )}
- {this.renderStatusAlert()}
- </div>
- );
- }
-}
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/EditionsStatusNotif-test.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/EditionsStatusNotif-test.tsx
deleted file mode 100644
index 4275351147c..00000000000
--- a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/EditionsStatusNotif-test.tsx
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import * as React from 'react';
-import { shallow } from 'enzyme';
-import { click } from '../../../../helpers/testUtils';
-import EditionsStatusNotif from '../EditionsStatusNotif';
-
-jest.mock('../../../../api/marketplace', () => ({
- dismissErrorMessage: jest.fn(() => Promise.resolve())
-}));
-
-const dismissMsg = require('../../../../api/marketplace').dismissErrorMessage as jest.Mock<any>;
-
-beforeEach(() => {
- dismissMsg.mockClear();
-});
-
-it('should display an in progress notif', () => {
- const wrapper = shallow(
- <EditionsStatusNotif
- editionStatus={{ installationStatus: 'AUTOMATIC_IN_PROGRESS' }}
- readOnly={false}
- updateEditionStatus={jest.fn()}
- />
- );
- expect(wrapper).toMatchSnapshot();
-});
-
-it('should display a ready notification', () => {
- const wrapper = shallow(
- <EditionsStatusNotif
- editionStatus={{ installationStatus: 'AUTOMATIC_READY' }}
- readOnly={false}
- updateEditionStatus={jest.fn()}
- />
- );
- expect(wrapper).toMatchSnapshot();
-});
-
-it('should display install errors', () => {
- const wrapper = shallow(
- <EditionsStatusNotif
- editionStatus={{ installationStatus: 'AUTOMATIC_IN_PROGRESS', installError: 'Foo error' }}
- readOnly={false}
- updateEditionStatus={jest.fn()}
- />
- );
- expect(wrapper).toMatchSnapshot();
-});
-
-it('should allow to dismiss install errors', async () => {
- const updateEditionStatus = jest.fn();
- const wrapper = shallow(
- <EditionsStatusNotif
- editionStatus={{ installationStatus: 'NONE', installError: 'Foo error' }}
- readOnly={false}
- updateEditionStatus={updateEditionStatus}
- />
- );
- click(wrapper.find('a'));
- expect(dismissMsg).toHaveBeenCalled();
- await new Promise(setImmediate);
- expect(updateEditionStatus).toHaveBeenCalledWith({
- installationStatus: 'NONE',
- installError: undefined
- });
-});
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/EditionsStatusNotif-test.tsx.snap b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/EditionsStatusNotif-test.tsx.snap
deleted file mode 100644
index a76a31c61d2..00000000000
--- a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/EditionsStatusNotif-test.tsx.snap
+++ /dev/null
@@ -1,61 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should display a ready notification 1`] = `
-<div>
- <div
- className="alert alert-success"
- >
- <span>
- marketplace.status.AUTOMATIC_READY
- </span>
- <button
- className="js-restart spacer-left"
- onClick={[Function]}
- >
- marketplace.restart
- </button>
- </div>
-</div>
-`;
-
-exports[`should display an in progress notif 1`] = `
-<div>
- <div
- className="alert alert-info"
- >
- <i
- className="spinner spacer-right text-bottom"
- />
- <span>
- marketplace.status.AUTOMATIC_IN_PROGRESS
- </span>
- </div>
-</div>
-`;
-
-exports[`should display install errors 1`] = `
-<div>
- <div
- className="alert alert-danger alert-cancel"
- >
- Foo error
- <a
- className="button-link text-danger"
- href="#"
- onClick={[Function]}
- >
- <CloseIcon />
- </a>
- </div>
- <div
- className="alert alert-info"
- >
- <i
- className="spinner spacer-right text-bottom"
- />
- <span>
- marketplace.status.AUTOMATIC_IN_PROGRESS
- </span>
- </div>
-</div>
-`;
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 31835b2ec56..780fafb3af8 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/utils.ts
+++ b/server/sonar-web/src/main/js/apps/marketplace/utils.ts
@@ -17,9 +17,8 @@
* 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, sortBy } from 'lodash';
+import { memoize } from 'lodash';
import { Plugin, PluginAvailable, PluginInstalled, PluginPending } from '../../api/plugins';
-import { Edition, EditionsPerVersion } from '../../api/marketplace';
import { cleanQuery, parseAsString, RawQuery, serializeString } from '../../helpers/query';
export interface Query {
@@ -52,34 +51,6 @@ export function filterPlugins(plugins: Plugin[], search: string): Plugin[] {
});
}
-export function getEditionsForLastVersion(editions: EditionsPerVersion): Edition[] {
- const sortedVersion = sortBy(Object.keys(editions), [
- (version: string) => -Number(version.split('.')[0]),
- (version: string) => -Number(version.split('.')[1] || 0),
- (version: string) => -Number(version.split('.')[2] || 0)
- ]);
- return editions[sortedVersion[0]];
-}
-
-export function getEditionsForVersion(
- editions: EditionsPerVersion,
- version: string
-): Edition[] | undefined {
- const minorVersion = version.match(/\d+\.\d+.\d+/);
- if (minorVersion) {
- if (editions[minorVersion[0]]) {
- return editions[minorVersion[0]];
- }
- }
- const majorVersion = version.match(/\d+\.\d+/);
- if (majorVersion) {
- if (editions[majorVersion[0]]) {
- return editions[majorVersion[0]];
- }
- }
- return undefined;
-}
-
export const parseQuery = memoize((urlQuery: RawQuery): Query => ({
filter: parseAsString(urlQuery['filter']) || DEFAULT_FILTER,
search: parseAsString(urlQuery['search'])