diff options
Diffstat (limited to 'server/sonar-web/src/main/js/apps/marketplace')
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']) |