diff options
author | Grégoire Aubert <gregoire.aubert@sonarsource.com> | 2018-05-28 13:10:23 +0200 |
---|---|---|
committer | SonarTech <sonartech@sonarsource.com> | 2018-06-12 20:20:58 +0200 |
commit | bdde08fff517a28f6f639e724b810a3679fdc452 (patch) | |
tree | 88cd623b8cc7518c677327ffdacf8e3ed5014643 /server/sonar-web | |
parent | 1dc4a86ad69635505e99f3cefe16e6542a7d4dfb (diff) | |
download | sonarqube-bdde08fff517a28f6f639e724b810a3679fdc452.tar.gz sonarqube-bdde08fff517a28f6f639e724b810a3679fdc452.zip |
SONAR-10696 Remove ability to upgrade/downgrade an edition from Marketplace (#269)
* SONAR-10699 Remove upgrade/downgrade buttons
* SONAR-10697 Drop edition.json support in the marketplace
* SONAR-10717 Drop 'sonar.editions.jsonUrl' property
* SONAR-10702 Edition's "Learn more" links redirect to the form page with arguments
* SONAR-10698 Get Edition data from the doc
* SONAR-10700 Remove LicenseEditionForm and LicenseEditionSet from Marketplace
Diffstat (limited to 'server/sonar-web')
38 files changed, 291 insertions, 2315 deletions
diff --git a/server/sonar-web/src/main/js/api/marketplace.ts b/server/sonar-web/src/main/js/api/marketplace.ts index c04fe5160ae..238efa8b3a3 100644 --- a/server/sonar-web/src/main/js/api/marketplace.ts +++ b/server/sonar-web/src/main/js/api/marketplace.ts @@ -17,45 +17,17 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { checkStatus, corsRequest, getJSON, parseJSON, post, postJSON } from '../helpers/request'; +import { getJSON, postJSON } from '../helpers/request'; import throwGlobalError from '../app/utils/throwGlobalError'; -export interface Edition { - key: string; - name: string; - textDescription: string; - homeUrl: string; - licenseRequestUrl: string; - downloadUrl: string; -} - -export interface EditionsPerVersion { - [version: string]: Edition[]; -} - export interface EditionStatus { currentEditionKey?: string; - nextEditionKey?: string; - installError?: string; - installationStatus: - | 'NONE' - | 'AUTOMATIC_IN_PROGRESS' - | 'MANUAL_IN_PROGRESS' - | 'AUTOMATIC_READY' - | 'UNINSTALL_IN_PROGRESS'; } export function getEditionStatus(): Promise<EditionStatus> { return getJSON('/api/editions/status'); } -export function getEditionsList(url: string): Promise<EditionsPerVersion> { - return corsRequest(url) - .submit() - .then(checkStatus) - .then(parseJSON); -} - export function getLicensePreview(data: { license: string; }): Promise<{ @@ -72,11 +44,3 @@ export function getFormData(): Promise<{ serverId: string; ncloc: number }> { export function applyLicense(data: { license: string }): Promise<EditionStatus> { return postJSON('/api/editions/apply_license', data).catch(throwGlobalError); } - -export function uninstallEdition(): Promise<void | Response> { - return post('/api/editions/uninstall').catch(throwGlobalError); -} - -export function dismissErrorMessage(): Promise<void | Response> { - return post('/api/editions/clear_error_message').catch(throwGlobalError); -} diff --git a/server/sonar-web/src/main/js/app/components/AdminContainer.tsx b/server/sonar-web/src/main/js/app/components/AdminContainer.tsx index c81e450022d..3347e3e922f 100644 --- a/server/sonar-web/src/main/js/app/components/AdminContainer.tsx +++ b/server/sonar-web/src/main/js/app/components/AdminContainer.tsx @@ -22,20 +22,10 @@ import * as PropTypes from 'prop-types'; import Helmet from 'react-helmet'; import { connect } from 'react-redux'; import SettingsNav from './nav/settings/SettingsNav'; -import { - getAppState, - getGlobalSettingValue, - getMarketplaceEditionStatus, - getMarketplacePendingPlugins -} from '../../store/rootReducer'; +import { getAppState, getMarketplacePendingPlugins } from '../../store/rootReducer'; import { getSettingsNavigation } from '../../api/nav'; -import { EditionStatus, getEditionStatus } from '../../api/marketplace'; import { setAdminPages } from '../../store/appState/duck'; -import { - fetchEditions, - setEditionStatus, - fetchPendingPlugins -} from '../../store/marketplace/actions'; +import { fetchCurrentEdition, fetchPendingPlugins } from '../../store/marketplace/actions'; import { translate } from '../../helpers/l10n'; import { Extension } from '../types'; import { PluginPendingResult } from '../../api/plugins'; @@ -47,23 +37,22 @@ interface StateProps { organizationsEnabled: boolean; version: string; }; - editionStatus?: EditionStatus; - editionsUrl: string; pendingPlugins: PluginPendingResult; } -interface DispatchProps { - fetchEditions: (url: string, version: string) => void; +interface DispatchToProps { fetchPendingPlugins: () => void; + fetchCurrentEdition: () => void; setAdminPages: (adminPages: Extension[]) => void; - setEditionStatus: (editionStatus: EditionStatus) => void; } interface OwnProps { location: {}; } -class AdminContainer extends React.PureComponent<StateProps & DispatchProps & OwnProps> { +type Props = StateProps & DispatchToProps & OwnProps; + +class AdminContainer extends React.PureComponent<Props> { static contextTypes = { canAdmin: PropTypes.bool.isRequired }; @@ -73,17 +62,13 @@ class AdminContainer extends React.PureComponent<StateProps & DispatchProps & Ow handleRequiredAuthorization(); } else { this.fetchNavigationSettings(); - this.props.fetchEditions(this.props.editionsUrl, this.props.appState.version); - this.fetchEditionStatus(); + this.props.fetchCurrentEdition(); } } fetchNavigationSettings = () => getSettingsNavigation().then(r => this.props.setAdminPages(r.extensions), () => {}); - fetchEditionStatus = () => - getEditionStatus().then(editionStatus => this.props.setEditionStatus(editionStatus), () => {}); - render() { const { adminPages, organizationsEnabled } = this.props.appState; @@ -98,7 +83,6 @@ class AdminContainer extends React.PureComponent<StateProps & DispatchProps & Ow <div> <Helmet defaultTitle={defaultTitle} titleTemplate={'%s - ' + defaultTitle} /> <SettingsNav - editionStatus={this.props.editionStatus} extensions={adminPages} fetchPendingPlugins={this.props.fetchPendingPlugins} location={this.props.location} @@ -113,16 +97,15 @@ class AdminContainer extends React.PureComponent<StateProps & DispatchProps & Ow const mapStateToProps = (state: any): StateProps => ({ appState: getAppState(state), - editionStatus: getMarketplaceEditionStatus(state), - editionsUrl: (getGlobalSettingValue(state, 'sonar.editions.jsonUrl') || {}).value, pendingPlugins: getMarketplacePendingPlugins(state) }); -const mapDispatchToProps: DispatchProps = { - setAdminPages, - setEditionStatus, - fetchEditions, - fetchPendingPlugins +const mapDispatchToProps: DispatchToProps = { + fetchCurrentEdition, + fetchPendingPlugins, + setAdminPages }; -export default connect(mapStateToProps, mapDispatchToProps)(AdminContainer); +export default connect<StateProps, DispatchToProps, OwnProps>(mapStateToProps, mapDispatchToProps)( + AdminContainer +); diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.tsx b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.tsx index f5ca55b6f83..8f9d34a94fb 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.tsx @@ -81,7 +81,7 @@ export default class ComponentNav extends React.PureComponent<Props> { } return ( <ContextNavBar - height={notifComponent ? theme.contextNavHeightRaw + 20 : theme.contextNavHeightRaw} + height={notifComponent ? theme.contextNavHeightRaw + 30 : theme.contextNavHeightRaw} id="context-navigation" notif={notifComponent}> <div className="navbar-context-justified"> diff --git a/server/sonar-web/src/main/js/app/components/nav/settings/SettingsEditionsNotif.tsx b/server/sonar-web/src/main/js/app/components/nav/settings/SettingsEditionsNotif.tsx deleted file mode 100644 index c4f1af5e32b..00000000000 --- a/server/sonar-web/src/main/js/app/components/nav/settings/SettingsEditionsNotif.tsx +++ /dev/null @@ -1,179 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 { FormattedMessage } from 'react-intl'; -import NavBarNotif from '../../../../components/nav/NavBarNotif'; -import RestartForm from '../../../../components/common/RestartForm'; -import { Button } from '../../../../components/ui/buttons'; -import { dismissErrorMessage, Edition, EditionStatus } from '../../../../api/marketplace'; -import { translate, translateWithParameters } from '../../../../helpers/l10n'; - -interface Props { - editions?: Edition[]; - editionStatus: EditionStatus; - preventRestart: boolean; - setEditionStatus: (editionStatus: EditionStatus) => void; -} - -interface State { - openRestart: boolean; -} - -export default class SettingsEditionsNotif extends React.PureComponent<Props, State> { - state: State = { openRestart: false }; - - handleOpenRestart = () => this.setState({ openRestart: true }); - hanleCloseRestart = () => this.setState({ openRestart: false }); - - handleDismissError = () => - dismissErrorMessage().then( - () => this.props.setEditionStatus({ ...this.props.editionStatus, installError: undefined }), - () => {} - ); - - renderStatusMsg(edition?: Edition) { - const { editionStatus } = this.props; - return ( - <NavBarNotif className="alert alert-info"> - <i className="spinner spacer-right" /> - <span> - {edition - ? translateWithParameters( - 'marketplace.edition_status_x.' + editionStatus.installationStatus, - edition.name - ) - : translate('marketplace.edition_status', editionStatus.installationStatus)} - </span> - </NavBarNotif> - ); - } - - renderRestartMsg(edition?: Edition) { - const { editionStatus, preventRestart } = this.props; - return ( - <NavBarNotif className="alert alert-success"> - <span> - {edition - ? translateWithParameters( - 'marketplace.edition_status_x.' + editionStatus.installationStatus, - edition.name - ) - : translate('marketplace.edition_status', editionStatus.installationStatus)} - </span> - {edition && - edition.key === 'datacenter' && ( - <span className="little-spacer-left"> - <FormattedMessage - defaultMessage={translate('marketplace.see_documentation_to_enable_cluster')} - id="marketplace.see_documentation_to_enable_cluster" - values={{ - url: ( - <a - href="https://redirect.sonarsource.com/doc/data-center-edition.html" - rel="noopener noreferrer" - target="_blank"> - {edition.name} - </a> - ) - }} - /> - </span> - )} - {!preventRestart && ( - <Button className="js-restart spacer-left" onClick={this.handleOpenRestart}> - {translate('marketplace.restart')} - </Button> - )} - {!preventRestart && - this.state.openRestart && <RestartForm onClose={this.hanleCloseRestart} />} - </NavBarNotif> - ); - } - - renderManualMsg(edition?: Edition) { - const { editionStatus } = this.props; - return ( - <NavBarNotif className="alert alert-danger"> - {edition - ? translateWithParameters( - 'marketplace.edition_status_x.' + editionStatus.installationStatus, - edition.name - ) - : translate('marketplace.edition_status', editionStatus.installationStatus)} - <a - className="spacer-left" - href={ - edition && edition.key === 'datacenter' - ? 'https://redirect.sonarsource.com/doc/data-center-edition.html' - : 'https://redirect.sonarsource.com/doc/how-to-install-an-edition.html' - } - target="_blank"> - {translate('marketplace.how_to_install')} - </a> - {edition && ( - <a - className="button spacer-left" - download={`sonarqube-${edition.name}.zip`} - href={edition.downloadUrl} - target="_blank"> - {translate('marketplace.download_package')} - </a> - )} - </NavBarNotif> - ); - } - - renderStatusAlert() { - const { currentEditionKey, installationStatus, nextEditionKey } = this.props.editionStatus; - const nextEdition = - this.props.editions && this.props.editions.find(edition => edition.key === nextEditionKey); - const currentEdition = - this.props.editions && - this.props.editions.find( - edition => - edition.key === currentEditionKey || (!currentEditionKey && edition.key === 'community') - ); - - switch (installationStatus) { - case 'AUTOMATIC_IN_PROGRESS': - return this.renderStatusMsg(nextEdition); - case 'AUTOMATIC_READY': - return this.renderRestartMsg(nextEdition); - case 'UNINSTALL_IN_PROGRESS': - return this.renderRestartMsg(currentEdition); - case 'MANUAL_IN_PROGRESS': - return this.renderManualMsg(nextEdition); - } - return null; - } - - render() { - const { installError } = this.props.editionStatus; - if (installError) { - return ( - <NavBarNotif className="alert alert-danger" onCancel={this.handleDismissError}> - {installError} - </NavBarNotif> - ); - } - - return this.renderStatusAlert(); - } -} diff --git a/server/sonar-web/src/main/js/app/components/nav/settings/SettingsEditionsNotifContainer.tsx b/server/sonar-web/src/main/js/app/components/nav/settings/SettingsEditionsNotifContainer.tsx deleted file mode 100644 index 8173ffdb62a..00000000000 --- a/server/sonar-web/src/main/js/app/components/nav/settings/SettingsEditionsNotifContainer.tsx +++ /dev/null @@ -1,49 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 { connect } from 'react-redux'; -import SettingsEditionsNotif from './SettingsEditionsNotif'; -import { getAppState, getMarketplaceEditions } from '../../../../store/rootReducer'; -import { Edition, EditionStatus } from '../../../../api/marketplace'; -import { setEditionStatus } from '../../../../store/marketplace/actions'; - -interface OwnProps { - editionStatus: EditionStatus; -} - -interface StateToProps { - editions?: Edition[]; - preventRestart: boolean; -} - -interface DispatchToProps { - setEditionStatus: (editionStatus: EditionStatus) => void; -} - -const mapStateToProps = (state: any): StateToProps => ({ - editions: getMarketplaceEditions(state), - preventRestart: !getAppState(state).standalone -}); - -const mapDispatchToProps = { setEditionStatus }; - -export default connect<StateToProps, DispatchToProps, OwnProps>( - mapStateToProps, - mapDispatchToProps -)(SettingsEditionsNotif); diff --git a/server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.tsx b/server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.tsx index 397603fe68d..08b49b3099f 100644 --- a/server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.tsx @@ -20,20 +20,17 @@ import * as React from 'react'; import * as classNames from 'classnames'; import { IndexLink, Link } from 'react-router'; -import SettingsEditionsNotifContainer from './SettingsEditionsNotifContainer'; import PendingPluginsActionNotif from './PendingPluginsActionNotif'; import * as theme from '../../../../app/theme'; import ContextNavBar from '../../../../components/nav/ContextNavBar'; +import Dropdown from '../../../../components/controls/Dropdown'; import NavBarTabs from '../../../../components/nav/NavBarTabs'; -import { EditionStatus } from '../../../../api/marketplace'; import { Extension } from '../../../types'; -import { translate } from '../../../../helpers/l10n'; -import Dropdown from '../../../../components/controls/Dropdown'; import { PluginPendingResult } from '../../../../api/plugins'; import DropdownIcon from '../../../../components/icons-components/DropdownIcon'; +import { translate } from '../../../../helpers/l10n'; interface Props { - editionStatus?: EditionStatus; extensions: Extension[]; fetchPendingPlugins: () => void; location: {}; @@ -232,23 +229,16 @@ export default class SettingsNav extends React.PureComponent<Props> { } render() { - const { editionStatus, extensions, pendingPlugins } = this.props; + const { extensions, pendingPlugins } = this.props; const hasSupportExtension = extensions.find(extension => extension.key === 'license/support'); + const totalPendingPlugins = + pendingPlugins.installing.length + + pendingPlugins.removing.length + + pendingPlugins.updating.length; - const notifComponents = []; - if ( - editionStatus && - (editionStatus.installError || editionStatus.installationStatus !== 'NONE') - ) { - notifComponents.push(<SettingsEditionsNotifContainer editionStatus={editionStatus} />); - } - - if ( - pendingPlugins.installing.length > 0 || - pendingPlugins.removing.length > 0 || - pendingPlugins.updating.length > 0 - ) { - notifComponents.push( + let notifComponent; + if (totalPendingPlugins > 0) { + notifComponent = ( <PendingPluginsActionNotif pending={pendingPlugins} refreshPending={this.props.fetchPendingPlugins} @@ -256,18 +246,11 @@ export default class SettingsNav extends React.PureComponent<Props> { ); } - const notifContainer = - notifComponents.length > 0 ? ( - <div className="alert-container"> - {notifComponents.map((element, index) => <div key={index}>{element}</div>)} - </div> - ) : null; - return ( <ContextNavBar - height={theme.contextNavHeightRaw + 38 * notifComponents.length} + height={notifComponent ? theme.contextNavHeightRaw + 30 : theme.contextNavHeightRaw} id="context-navigation" - notif={notifContainer}> + notif={notifComponent}> <header className="navbar-context-header"> <h1>{translate('layout.settings')}</h1> </header> diff --git a/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/SettingsEditionsNotif-test.tsx b/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/SettingsEditionsNotif-test.tsx deleted file mode 100644 index 0f40d0bc59a..00000000000 --- a/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/SettingsEditionsNotif-test.tsx +++ /dev/null @@ -1,130 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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. - */ -/* eslint-disable import/order */ -import * as React from 'react'; -import { mount, shallow } from 'enzyme'; -import { click } from '../../../../../helpers/testUtils'; -import SettingsEditionsNotif from '../SettingsEditionsNotif'; - -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( - <SettingsEditionsNotif - editionStatus={{ installationStatus: 'AUTOMATIC_IN_PROGRESS' }} - preventRestart={false} - setEditionStatus={jest.fn()} - /> - ); - expect(wrapper).toMatchSnapshot(); -}); - -it('should display a ready notification', () => { - const wrapper = shallow( - <SettingsEditionsNotif - editionStatus={{ installationStatus: 'AUTOMATIC_READY' }} - preventRestart={false} - setEditionStatus={jest.fn()} - /> - ); - expect(wrapper).toMatchSnapshot(); -}); - -it('should display a manual installation notification', () => { - const wrapper = shallow( - <SettingsEditionsNotif - editionStatus={{ installationStatus: 'MANUAL_IN_PROGRESS', nextEditionKey: 'foo' }} - editions={[ - { - key: 'foo', - name: 'Foo', - textDescription: 'Foo desc', - downloadUrl: 'download_url', - homeUrl: 'more_url', - licenseRequestUrl: 'license_url' - } - ]} - preventRestart={false} - setEditionStatus={jest.fn()} - /> - ); - expect(wrapper).toMatchSnapshot(); -}); - -it('should display install errors', () => { - const wrapper = shallow( - <SettingsEditionsNotif - editionStatus={{ installationStatus: 'AUTOMATIC_IN_PROGRESS', installError: 'Foo error' }} - preventRestart={false} - setEditionStatus={jest.fn()} - /> - ); - expect(wrapper).toMatchSnapshot(); -}); - -it('should allow to dismiss install errors', async () => { - const setEditionStatus = jest.fn(); - const wrapper = mount( - <SettingsEditionsNotif - editionStatus={{ installationStatus: 'NONE', installError: 'Foo error' }} - preventRestart={false} - setEditionStatus={setEditionStatus} - /> - ); - click(wrapper.find('button')); - expect(dismissMsg).toHaveBeenCalled(); - await new Promise(setImmediate); - expect(setEditionStatus).toHaveBeenCalledWith({ - installationStatus: 'NONE', - installError: undefined - }); -}); - -it('should not display the restart button', () => { - const wrapper = shallow( - <SettingsEditionsNotif - editionStatus={{ installationStatus: 'AUTOMATIC_READY' }} - preventRestart={true} - setEditionStatus={jest.fn()} - /> - ); - expect(wrapper.find('button.js-restart').exists()).toBeFalsy(); -}); - -it('should have a link to cluster documentation for datacenter edition', () => { - const editions = [{ key: 'datacenter' }] as any; - const wrapper = shallow( - <SettingsEditionsNotif - editions={editions} - editionStatus={{ installationStatus: 'AUTOMATIC_READY', nextEditionKey: 'datacenter' }} - preventRestart={false} - setEditionStatus={jest.fn()} - /> - ); - expect(wrapper.find('FormattedMessage').exists()).toBeTruthy(); -}); diff --git a/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/SettingsNav-test.tsx b/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/SettingsNav-test.tsx index 256662531cb..71ca8fd83ed 100644 --- a/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/SettingsNav-test.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/SettingsNav-test.tsx @@ -35,3 +35,28 @@ it('should work with extensions', () => { expect(wrapper).toMatchSnapshot(); expect(wrapper.find('Dropdown')).toMatchSnapshot(); }); + +it('should display a pending plugin notif', () => { + const extensions = [{ key: 'foo', name: 'Foo' }]; + const wrapper = shallow( + <SettingsNav + extensions={extensions} + fetchPendingPlugins={() => {}} + location={{}} + organizationsEnabled={false} + pendingPlugins={{ + installing: [ + { + key: 'foo', + name: 'Foo', + version: '1.0', + implementationBuild: '1' + } + ], + removing: [], + updating: [] + }} + /> + ); + expect(wrapper.find('ContextNavBar').prop('notif')).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/__snapshots__/SettingsEditionsNotif-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/__snapshots__/SettingsEditionsNotif-test.tsx.snap deleted file mode 100644 index 051f00fce94..00000000000 --- a/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/__snapshots__/SettingsEditionsNotif-test.tsx.snap +++ /dev/null @@ -1,62 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should display a manual installation notification 1`] = ` -<NavBarNotif - className="alert alert-danger" -> - marketplace.edition_status_x.MANUAL_IN_PROGRESS.Foo - <a - className="spacer-left" - href="https://redirect.sonarsource.com/doc/how-to-install-an-edition.html" - target="_blank" - > - marketplace.how_to_install - </a> - <a - className="button spacer-left" - download="sonarqube-Foo.zip" - href="download_url" - target="_blank" - > - marketplace.download_package - </a> -</NavBarNotif> -`; - -exports[`should display a ready notification 1`] = ` -<NavBarNotif - className="alert alert-success" -> - <span> - marketplace.edition_status.AUTOMATIC_READY - </span> - <Button - className="js-restart spacer-left" - onClick={[Function]} - > - marketplace.restart - </Button> -</NavBarNotif> -`; - -exports[`should display an in progress notif 1`] = ` -<NavBarNotif - className="alert alert-info" -> - <i - className="spinner spacer-right" - /> - <span> - marketplace.edition_status.AUTOMATIC_IN_PROGRESS - </span> -</NavBarNotif> -`; - -exports[`should display install errors 1`] = ` -<NavBarNotif - className="alert alert-danger" - onCancel={[Function]} -> - Foo error -</NavBarNotif> -`; diff --git a/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/__snapshots__/SettingsNav-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/__snapshots__/SettingsNav-test.tsx.snap index 29a88938c4b..56d25c693cf 100644 --- a/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/__snapshots__/SettingsNav-test.tsx.snap +++ b/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/__snapshots__/SettingsNav-test.tsx.snap @@ -1,10 +1,29 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`should display a pending plugin notif 1`] = ` +<PendingPluginsActionNotif + pending={ + Object { + "installing": Array [ + Object { + "implementationBuild": "1", + "key": "foo", + "name": "Foo", + "version": "1.0", + }, + ], + "removing": Array [], + "updating": Array [], + } + } + refreshPending={[Function]} +/> +`; + exports[`should work with extensions 1`] = ` <ContextNavBar height={72} id="context-navigation" - notif={null} > <header className="navbar-context-header" diff --git a/server/sonar-web/src/main/js/app/styles/components/alerts.css b/server/sonar-web/src/main/js/app/styles/components/alerts.css index c8a9efd700c..6cbd7fb0896 100644 --- a/server/sonar-web/src/main/js/app/styles/components/alerts.css +++ b/server/sonar-web/src/main/js/app/styles/components/alerts.css @@ -17,12 +17,6 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -.alert-container .alert { - margin-bottom: 0px; - padding: 0 8px; - line-height: 38px; -} - .alert { display: block; margin-bottom: 8px; diff --git a/server/sonar-web/src/main/js/app/utils/exposeLibraries.ts b/server/sonar-web/src/main/js/app/utils/exposeLibraries.ts index 995a9b15aa7..ed1178b283a 100644 --- a/server/sonar-web/src/main/js/app/utils/exposeLibraries.ts +++ b/server/sonar-web/src/main/js/app/utils/exposeLibraries.ts @@ -27,7 +27,6 @@ import DateFromNow from '../../components/intl/DateFromNow'; import DateFormatter from '../../components/intl/DateFormatter'; import DateTimeFormatter from '../../components/intl/DateTimeFormatter'; import FavoriteContainer from '../../components/controls/FavoriteContainer'; -import LicenseEditionSet from '../../apps/marketplace/components/LicenseEditionSet'; import HomePageSelect from '../../components/controls/HomePageSelect'; import ListFooter from '../../components/controls/ListFooter'; import Modal from '../../components/controls/Modal'; @@ -82,7 +81,6 @@ const exposeLibraries = () => { HelpTooltip, HomePageSelect, Level, - LicenseEditionSet, ListFooter, LockIcon, Modal, 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 8b151058a49..966ab400de0 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/App.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/App.tsx @@ -36,21 +36,16 @@ import { PluginPendingResult, getInstalledPlugins } from '../../api/plugins'; -import { Edition, EditionStatus } from '../../api/marketplace'; import { RawQuery } from '../../helpers/query'; import { translate } from '../../helpers/l10n'; import './style.css'; export interface Props { - editions?: Edition[]; - editionsReadOnly: boolean; - editionStatus?: EditionStatus; + currentEdition?: string; fetchPendingPlugins: () => void; - loadingEditions: boolean; location: { pathname: string; query: RawQuery }; pendingPlugins: PluginPendingResult; standaloneMode: boolean; - setEditionStatus: (editionStatus: EditionStatus) => void; updateCenterActive: boolean; } @@ -66,13 +61,7 @@ export default class App extends React.PureComponent<Props, State> { router: PropTypes.object.isRequired }; - constructor(props: Props) { - super(props); - this.state = { - loadingPlugins: true, - plugins: [] - }; - } + state: State = { loadingPlugins: true, plugins: [] }; componentDidMount() { this.mounted = true; @@ -130,7 +119,7 @@ export default class App extends React.PureComponent<Props, State> { }; render() { - const { editions, editionStatus, standaloneMode, pendingPlugins } = this.props; + const { currentEdition, standaloneMode, pendingPlugins } = this.props; const { loadingPlugins, plugins } = this.state; const query = parseQuery(this.props.location.query); const filteredPlugins = query.search ? filterPlugins(plugins, query.search) : plugins; @@ -140,15 +129,7 @@ export default class App extends React.PureComponent<Props, State> { <Suggestions suggestions="marketplace" /> <Helmet title={translate('marketplace.page')} /> <Header /> - <EditionBoxes - canInstall={standaloneMode && !this.props.editionsReadOnly} - canUninstall={standaloneMode} - editionStatus={editionStatus} - editions={editions} - loading={this.props.loadingEditions} - updateCenterActive={this.props.updateCenterActive} - updateEditionStatus={this.props.setEditionStatus} - /> + <EditionBoxes currentEdition={currentEdition} /> <Search query={query} updateCenterActive={this.props.updateCenterActive} 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 73611432c0b..eabc536fda5 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/AppContainer.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/AppContainer.tsx @@ -22,13 +22,10 @@ import App from './App'; import { getAppState, getGlobalSettingValue, - getMarketplaceState, - getMarketplaceEditions, - getMarketplaceEditionStatus, + getMarketplaceCurrentEdition, getMarketplacePendingPlugins } from '../../store/rootReducer'; -import { Edition, EditionStatus } from '../../api/marketplace'; -import { setEditionStatus, fetchPendingPlugins } from '../../store/marketplace/actions'; +import { fetchPendingPlugins } from '../../store/marketplace/actions'; import { RawQuery } from '../../helpers/query'; import { PluginPendingResult } from '../../api/plugins'; @@ -37,32 +34,27 @@ interface OwnProps { } interface StateToProps { - editions?: Edition[]; - editionsReadOnly: boolean; - editionStatus?: EditionStatus; - loadingEditions: boolean; + currentEdition?: string; pendingPlugins: PluginPendingResult; standaloneMode: boolean; updateCenterActive: boolean; } interface DispatchToProps { - setEditionStatus: (editionStatus: EditionStatus) => void; fetchPendingPlugins: () => void; } -const mapStateToProps = (state: any) => ({ - editions: getMarketplaceEditions(state), - editionsReadOnly: getMarketplaceState(state).readOnly, - editionStatus: getMarketplaceEditionStatus(state), - loadingEditions: getMarketplaceState(state).loading, - pendingPlugins: getMarketplacePendingPlugins(state), - standaloneMode: getAppState(state).standalone, - updateCenterActive: - (getGlobalSettingValue(state, 'sonar.updatecenter.activate') || {}).value === 'true' -}); +const mapStateToProps = (state: any) => { + return { + currentEdition: getMarketplaceCurrentEdition(state), + pendingPlugins: getMarketplacePendingPlugins(state), + standaloneMode: getAppState(state).standalone, + updateCenterActive: + (getGlobalSettingValue(state, 'sonar.updatecenter.activate') || {}).value === 'true' + }; +}; -const mapDispatchToProps = { setEditionStatus, fetchPendingPlugins }; +const mapDispatchToProps = { fetchPendingPlugins }; export default connect<StateToProps, DispatchToProps, OwnProps>( mapStateToProps, 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 d169e5b31a4..0b762d28f7e 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/EditionBoxes.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/EditionBoxes.tsx @@ -17,144 +17,59 @@ * 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 { FormattedMessage } from 'react-intl'; import EditionBox from './components/EditionBox'; -import LicenseEditionForm from './components/LicenseEditionForm'; -import UninstallEditionForm from './components/UninstallEditionForm'; -import { sortEditions } from './utils'; -import { Edition, EditionStatus } from '../../api/marketplace'; -import { translate } from '../../helpers/l10n'; +import { EDITIONS } from './utils'; +import { getFormData } from '../../api/marketplace'; export interface Props { - canInstall: boolean; - canUninstall: boolean; - editions?: Edition[]; - editionStatus?: EditionStatus; - loading: boolean; - updateCenterActive: boolean; - updateEditionStatus: (editionStatus: EditionStatus) => void; + currentEdition?: string; } interface State { - installEdition?: Edition; - openUninstallForm: boolean; + serverId?: string; + ncloc?: number; } export default class EditionBoxes extends React.PureComponent<Props, State> { - state: State = { openUninstallForm: false }; - - handleOpenLicenseForm = (edition: Edition) => this.setState({ installEdition: edition }); - handleCloseLicenseForm = () => this.setState({ installEdition: undefined }); - - 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); + mounted = false; + state: State = {}; - 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; + componentDidMount() { + this.mounted = true; + this.fetchFormData(); } - render() { - const { canInstall, canUninstall, editions, loading } = this.props; - - if (loading) { - return <i className="big-spacer-bottom spinner" />; - } - - if (!editions) { - return ( - <div className="spacer-bottom marketplace-editions"> - <span className="alert alert-info"> - <FormattedMessage - defaultMessage={translate('marketplace.editions_unavailable')} - id="marketplace.editions_unavailable" - values={{ - url: ( - <a href="https://redirect.sonarsource.com/editions/editions.html" target="_blank"> - SonarSource.com - </a> - ) - }} - /> - </span> - </div> - ); - } + componentWillUnmount() { + this.mounted = false; + } - const sortedEditions = sortEditions(editions); - const status = this.props.editionStatus || { installationStatus: 'NONE' }; - const inProgressStatus = [ - 'AUTOMATIC_IN_PROGRESS', - 'AUTOMATIC_READY', - 'UNINSTALL_IN_PROGRESS' - ].includes(status.installationStatus); - const installedIdx = sortedEditions.findIndex( - edition => edition.key === status.currentEditionKey + fetchFormData = () => { + getFormData().then( + formData => { + if (this.mounted) { + this.setState({ ...formData }); + } + }, + () => {} ); - const nextIdx = sortedEditions.findIndex(edition => edition.key === status.nextEditionKey); - const currentIdx = inProgressStatus ? nextIdx : installedIdx; + }; + + render() { + const { currentEdition } = this.props; + const { serverId, ncloc } = this.state; return ( <div className="spacer-bottom marketplace-editions"> - <EditionBox - actionLabel={translate('marketplace.downgrade')} - disableAction={inProgressStatus} - displayAction={canUninstall && currentIdx > 0} - edition={sortedEditions[0]} - editionStatus={status} - key={sortedEditions[0].key} - onAction={this.handleOpenUninstallForm} - /> - {sortedEditions - .slice(1) - .map((edition, idx) => ( - <EditionBox - actionLabel={ - currentIdx > idx + 1 - ? translate('marketplace.downgrade') - : translate('marketplace.upgrade') - } - disableAction={inProgressStatus} - displayAction={canInstall && currentIdx !== idx + 1} - edition={edition} - editionStatus={status} - key={edition.key} - onAction={this.handleOpenLicenseForm} - /> - ))} - - {this.renderForms(sortedEditions, installedIdx)} + {EDITIONS.map(edition => ( + <EditionBox + currentEdition={currentEdition || 'community'} + edition={edition} + key={edition.key} + ncloc={ncloc} + serverId={serverId} + /> + ))} </div> ); } 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 6e153cd084d..dd49bb10079 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 @@ -20,110 +20,22 @@ import * as React from 'react'; import { shallow } from 'enzyme'; import EditionBoxes from '../EditionBoxes'; -import { EditionStatus } from '../../../api/marketplace'; -const DEFAULT_STATUS: EditionStatus = { - currentEditionKey: 'developer', - nextEditionKey: '', - installationStatus: 'NONE' -}; - -const DEFAULT_EDITIONS = [ - { - key: 'developer', - name: 'Developer Edition', - textDescription: 'foo', - downloadUrl: 'download_url', - homeUrl: 'more_url', - licenseRequestUrl: 'license_url' - }, - { - key: 'comunity', - name: 'Comunity Edition', - textDescription: 'bar', - downloadUrl: 'download_url', - homeUrl: 'more_url', - licenseRequestUrl: 'license_url' - } -]; +jest.mock('../utils', () => ({ + EDITIONS: [ + { key: 'comunity', homeUrl: 'more_url' }, + { key: 'developer', downloadUrl: 'download_url', homeUrl: 'more_url' } + ] +})); it('should display the edition boxes correctly', () => { - const wrapper = getWrapper({ editions: DEFAULT_EDITIONS, loading: true }); - expect(wrapper).toMatchSnapshot(); - wrapper.setProps({ loading: false }); - expect(wrapper).toMatchSnapshot(); -}); - -it('should display an error message', () => { - const wrapper = getWrapper(); - expect(wrapper).toMatchSnapshot(); -}); - -it('should display community without the downgrade button', () => { - const communityBox = getWrapper({ - editions: DEFAULT_EDITIONS, - editionStatus: { - currentEditionKey: '', - installationStatus: 'NONE' - }, - loading: false - }) - .find('EditionBox') - .first(); - expect(communityBox.prop('displayAction')).toBeFalsy(); -}); - -it('should not display action buttons', () => { - const wrapper = getWrapper({ - editions: DEFAULT_EDITIONS, - editionStatus: { - currentEditionKey: '', - installationStatus: 'NONE' - }, - loading: false, - canInstall: false, - canUninstall: false - }); - wrapper.find('EditionBox').forEach(box => expect(box.prop('displayAction')).toBeFalsy()); -}); - -it('should display disabled action buttons', () => { - const wrapper = getWrapper({ - editions: DEFAULT_EDITIONS, - editionStatus: { installationStatus: 'AUTOMATIC_IN_PROGRESS', nextEditionKey: 'developer' }, - loading: false - }); - - wrapper.find('EditionBox').forEach(box => expect(box.prop('disableAction')).toBeTruthy()); - expect(wrapper.find('EditionBox').map(box => box.prop('displayAction'))).toEqual([true, false]); - - wrapper.setProps({ - editionStatus: { currentEditionKey: 'developer', installationStatus: 'UNINSTALL_IN_PROGRESS' } - }); - wrapper.find('EditionBox').forEach(box => expect(box.prop('disableAction')).toBeTruthy()); - expect(wrapper.find('EditionBox').map(box => box.prop('displayAction'))).toEqual([false, true]); - - wrapper.setProps({ editionStatus: { installationStatus: 'AUTOMATIC_READY' } }); - wrapper.find('EditionBox').forEach(box => expect(box.prop('disableAction')).toBeTruthy()); + expect(getWrapper()).toMatchSnapshot(); }); -it('should open the license form', () => { - const wrapper = getWrapper({ editions: DEFAULT_EDITIONS }); - (wrapper.instance() as EditionBoxes).handleOpenLicenseForm(DEFAULT_EDITIONS[0]); - wrapper.update(); - expect(wrapper.find('LicenseEditionForm').exists()).toBeTruthy(); +it('should display the developer edition as installed', () => { + expect(getWrapper({ currentEdition: 'developer' })).toMatchSnapshot(); }); function getWrapper(props = {}) { - return shallow( - <EditionBoxes - canInstall={true} - canUninstall={true} - loading={false} - editionStatus={DEFAULT_STATUS} - updateCenterActive={true} - updateEditionStatus={jest.fn()} - {...props} - /> - ); + return shallow(<EditionBoxes currentEdition={undefined} {...props} />); } diff --git a/server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/EditionBoxes-test.tsx.snap b/server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/EditionBoxes-test.tsx.snap index 85ba8946957..687a2cde364 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/EditionBoxes-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/EditionBoxes-test.tsx.snap @@ -1,87 +1,57 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`should display an error message 1`] = ` +exports[`should display the developer edition as installed 1`] = ` <div className="spacer-bottom marketplace-editions" > - <span - className="alert alert-info" - > - <FormattedMessage - defaultMessage="marketplace.editions_unavailable" - id="marketplace.editions_unavailable" - values={ - Object { - "url": <a - href="https://redirect.sonarsource.com/editions/editions.html" - target="_blank" - > - SonarSource.com - </a>, - } + <EditionBox + currentEdition="developer" + edition={ + Object { + "homeUrl": "more_url", + "key": "comunity", } - /> - </span> + } + key="comunity" + /> + <EditionBox + currentEdition="developer" + edition={ + Object { + "downloadUrl": "download_url", + "homeUrl": "more_url", + "key": "developer", + } + } + key="developer" + /> </div> `; exports[`should display the edition boxes correctly 1`] = ` -<i - className="big-spacer-bottom spinner" -/> -`; - -exports[`should display the edition boxes correctly 2`] = ` <div className="spacer-bottom marketplace-editions" > <EditionBox - actionLabel="marketplace.downgrade" - disableAction={false} - displayAction={true} + currentEdition="community" edition={ Object { - "downloadUrl": "download_url", "homeUrl": "more_url", "key": "comunity", - "licenseRequestUrl": "license_url", - "name": "Comunity Edition", - "textDescription": "bar", - } - } - editionStatus={ - Object { - "currentEditionKey": "developer", - "installationStatus": "NONE", - "nextEditionKey": "", } } key="comunity" - onAction={[Function]} /> <EditionBox - actionLabel="marketplace.upgrade" - disableAction={false} - displayAction={false} + currentEdition="community" edition={ Object { "downloadUrl": "download_url", "homeUrl": "more_url", "key": "developer", - "licenseRequestUrl": "license_url", - "name": "Developer Edition", - "textDescription": "foo", - } - } - editionStatus={ - Object { - "currentEditionKey": "developer", - "installationStatus": "NONE", - "nextEditionKey": "", } } key="developer" - onAction={[Function]} /> </div> `; diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/EditionBox.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/EditionBox.tsx index 0ad689bc93d..968aa566020 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/components/EditionBox.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/components/EditionBox.tsx @@ -18,45 +18,37 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import EditionBoxBadge from './EditionBoxBadge'; -import { Edition, EditionStatus } from '../../../api/marketplace'; -import { Button } from '../../../components/ui/buttons'; +import CheckIcon from '../../../components/icons-components/CheckIcon'; import { translate } from '../../../helpers/l10n'; +import DocInclude from '../../../components/docs/DocInclude'; +import { Edition, getEditionUrl } from '../utils'; interface Props { - actionLabel: string; - disableAction: boolean; - displayAction: boolean; + currentEdition: string; edition: Edition; - editionStatus?: EditionStatus; - onAction: (edition: Edition) => void; + ncloc?: number; + serverId?: string; } -export default class EditionBox extends React.PureComponent<Props> { - handleAction = () => { - this.props.onAction(this.props.edition); - }; - - render() { - const { disableAction, displayAction, edition, editionStatus } = this.props; - return ( - <div className="boxed-group boxed-group-inner marketplace-edition"> - {editionStatus && <EditionBoxBadge editionKey={edition.key} status={editionStatus} />} - <div> - <h3 className="spacer-bottom">{edition.name}</h3> - <p>{edition.textDescription}</p> - </div> - <div className="marketplace-edition-action spacer-top"> - <a href={edition.homeUrl} target="_blank"> - {translate('marketplace.learn_more')} - </a> - {displayAction && ( - <Button disabled={disableAction} onClick={this.handleAction}> - {this.props.actionLabel} - </Button> - )} - </div> +export default function EditionBox({ currentEdition, edition, ncloc, serverId }: Props) { + const isInstalled = currentEdition === edition.key; + const url = getEditionUrl(edition, { ncloc, serverId, sourceEdition: currentEdition }); + return ( + <div className="boxed-group boxed-group-inner marketplace-edition"> + {isInstalled && ( + <span className="marketplace-edition-badge badge badge-normal-size display-flex-center"> + <CheckIcon className="little-spacer-right" size={14} /> + {translate('marketplace.installed')} + </span> + )} + <div> + <DocInclude path={'/tooltips/editions/' + edition.key} /> + </div> + <div className="marketplace-edition-action spacer-top"> + <a href={url} target="_blank"> + {translate('marketplace.learn_more')} + </a> </div> - ); - } + </div> + ); } diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/EditionBoxBadge.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/EditionBoxBadge.tsx deleted file mode 100644 index 341bd2e221b..00000000000 --- a/server/sonar-web/src/main/js/apps/marketplace/components/EditionBoxBadge.tsx +++ /dev/null @@ -1,62 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 isInstalled = - status.currentEditionKey === editionKey || - (!status.currentEditionKey && editionKey === 'community'); - const isProgressing = - status.nextEditionKey === editionKey || (!status.nextEditionKey && editionKey === 'community'); - const inProgressStatus = [ - 'AUTOMATIC_READY', - 'AUTOMATIC_IN_PROGRESS', - 'UNINSTALL_IN_PROGRESS' - ].includes(status.installationStatus); - - if (inProgressStatus) { - if (isProgressing) { - return ( - <span className="marketplace-edition-badge badge badge-normal-size"> - {status.installationStatus === 'AUTOMATIC_IN_PROGRESS' - ? translate('marketplace.installing') - : translate('marketplace.pending')} - </span> - ); - } - } else if (isInstalled) { - return ( - <span className="marketplace-edition-badge badge badge-normal-size display-flex-center"> - <CheckIcon size={14} className="little-spacer-right" /> - {translate('marketplace.installed')} - </span> - ); - } - - return null; -} diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/LicenseEditionForm.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/LicenseEditionForm.tsx deleted file mode 100644 index a4c48287869..00000000000 --- a/server/sonar-web/src/main/js/apps/marketplace/components/LicenseEditionForm.tsx +++ /dev/null @@ -1,114 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 LicenseEditionSet from './LicenseEditionSet'; -import { Edition, EditionStatus, applyLicense } from '../../../api/marketplace'; -import Modal from '../../../components/controls/Modal'; -import { Button, ResetButtonLink } from '../../../components/ui/buttons'; -import { translate, translateWithParameters } from '../../../helpers/l10n'; - -export interface Props { - edition: Edition; - editions: Edition[]; - isDowngrade: boolean; - onClose: () => void; - updateEditionStatus: (editionStatus: EditionStatus) => void; -} - -interface State { - license: string; - status?: string; - submitting: boolean; -} - -export default class LicenseEditionForm extends React.PureComponent<Props, State> { - mounted = false; - state: State = { license: '', submitting: false }; - - componentDidMount() { - this.mounted = true; - } - - componentWillUnmount() { - this.mounted = false; - } - - handleLicenseChange = (license: string, status?: string) => { - if (this.mounted) { - this.setState({ license, status }); - } - }; - - handleConfirmClick = () => { - const { license, status } = this.state; - if (license && status) { - this.setState({ submitting: true }); - applyLicense({ license }).then( - editionStatus => { - this.props.updateEditionStatus(editionStatus); - this.props.onClose(); - }, - () => { - if (this.mounted) { - this.setState({ submitting: false }); - } - } - ); - } - }; - - render() { - const { edition, isDowngrade } = this.props; - const { license, submitting, status } = this.state; - - const header = isDowngrade - ? translateWithParameters('marketplace.downgrade_to_x', edition.name) - : translateWithParameters('marketplace.upgrade_to_x', edition.name); - return ( - <Modal contentLabel={header} onRequestClose={this.props.onClose}> - <header className="modal-head"> - <h2>{header}</h2> - </header> - - <LicenseEditionSet - className="modal-body" - edition={edition} - editions={this.props.editions} - updateLicense={this.handleLicenseChange} - /> - - <footer className="modal-foot"> - {submitting && <i className="spinner spacer-right" />} - {status && ( - <Button - className="js-confirm" - disabled={!license || submitting} - onClick={this.handleConfirmClick}> - {status === 'AUTOMATIC_INSTALL' - ? translate('marketplace.install') - : translate('save')} - </Button> - )} - <ResetButtonLink onClick={this.props.onClose}>{translate('cancel')}</ResetButtonLink> - </footer> - </Modal> - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/LicenseEditionSet.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/LicenseEditionSet.tsx deleted file mode 100644 index b8099a65079..00000000000 --- a/server/sonar-web/src/main/js/apps/marketplace/components/LicenseEditionSet.tsx +++ /dev/null @@ -1,269 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 { stringify } from 'querystring'; -import * as React from 'react'; -import * as classNames from 'classnames'; -import { FormattedMessage } from 'react-intl'; -import { debounce } from 'lodash'; -import Checkbox from '../../../components/controls/Checkbox'; -import DeferredSpinner from '../../../components/common/DeferredSpinner'; -import { omitNil } from '../../../helpers/request'; -import { Edition, getFormData, getLicensePreview } from '../../../api/marketplace'; -import { translate, translateWithParameters } from '../../../helpers/l10n'; - -export interface Props { - className?: string; - edition?: Edition; - editions: Edition[]; - updateLicense: (license?: string, status?: string) => void; -} - -interface State { - acceptTerms: boolean; - formData?: { - serverId?: string; - ncloc?: number; - }; - license: string; - licenseEdition?: Edition; - loading: boolean; - previewStatus?: string; - wrongEdition: boolean; -} - -export default class LicenseEditionSet extends React.PureComponent<Props, State> { - mounted = false; - - constructor(props: Props) { - super(props); - this.state = { acceptTerms: false, license: '', loading: false, wrongEdition: false }; - this.fetchLicensePreview = debounce(this.fetchLicensePreview, 100); - } - - componentDidMount() { - this.mounted = true; - this.fetchFormData(); - } - - componentWillUnmount() { - this.mounted = false; - } - - fetchLicensePreview = (license: string) => { - this.setState({ loading: true }); - getLicensePreview({ license }).then( - ({ previewStatus, nextEditionKey }) => { - if (this.mounted) { - const { edition } = this.props; - const licenseEdition = this.props.editions.find( - edition => edition.key === nextEditionKey - ); - const wrongEdition = Boolean( - !licenseEdition || (edition && edition.key !== nextEditionKey) - ); - this.setLicense({ license, loading: false, licenseEdition, previewStatus, wrongEdition }); - } - }, - () => { - if (this.mounted) { - this.resetLicense({ license, loading: false }); - } - } - ); - }; - - fetchFormData = () => { - getFormData().then( - formData => { - if (this.mounted) { - this.setState({ formData }); - } - }, - () => {} - ); - }; - - getLicenseFormUrl = (edition: Edition) => { - let url = edition.licenseRequestUrl; - if (this.state.formData) { - const query = stringify(omitNil(this.state.formData)); - if (query) { - url += '?' + query; - } - } - return url; - }; - - handleLicenseChange = (event: React.SyntheticEvent<HTMLTextAreaElement>) => { - const license = event.currentTarget.value; - if (license) { - this.fetchLicensePreview(license); - this.setState({ license }); - } else { - this.resetLicense({}); - } - }; - - handleTermsCheck = (checked: boolean) => { - this.setLicense({ acceptTerms: checked }); - }; - - resetLicense<K extends keyof State>(state: Pick<State, K>) { - this.setLicense( - Object.assign( - { - license: '', - licenseEdition: undefined, - previewStatus: undefined, - wrongEdition: false - }, - state - ) - ); - } - - setLicense<K extends keyof State>(state: Pick<State, K>) { - this.setState(state, this.updateParentLicense); - } - - updateParentLicense = () => { - const { acceptTerms, license, previewStatus, wrongEdition } = this.state; - this.props.updateLicense( - previewStatus !== 'NO_INSTALL' && !acceptTerms ? undefined : license, - wrongEdition ? undefined : previewStatus - ); - }; - - renderAlert() { - const { licenseEdition, previewStatus, wrongEdition } = this.state; - if (!previewStatus || wrongEdition) { - const { edition } = this.props; - - return ( - <div className="spacer-top"> - {wrongEdition && ( - <p className="alert alert-danger"> - {edition - ? translateWithParameters('marketplace.wrong_license_type_x', edition.name) - : translate('marketplace.wrong_license_type')} - </p> - )} - {edition && ( - <a href={this.getLicenseFormUrl(edition)} target="_blank"> - {translate('marketplace.i_need_a_license')} - </a> - )} - </div> - ); - } - - return ( - <div className="spacer-top"> - <p - className={classNames('alert', { - 'alert-warning': previewStatus === 'AUTOMATIC_INSTALL', - 'alert-success': previewStatus === 'NO_INSTALL', - 'alert-danger': previewStatus === 'MANUAL_INSTALL' - })}> - {translateWithParameters( - 'marketplace.license_preview_status.' + previewStatus, - licenseEdition ? licenseEdition.name : translate('marketplace.commercial_edition') - )} - {licenseEdition && - licenseEdition.key === 'datacenter' && - previewStatus !== 'NO_INSTALL' && ( - <span className="little-spacer-left"> - <FormattedMessage - defaultMessage={translate('marketplace.how_to_setup_cluster_url')} - id="marketplace.how_to_setup_cluster_url" - values={{ - url: ( - <a - href="https://redirect.sonarsource.com/doc/data-center-edition.html" - rel="noopener noreferrer" - target="_blank"> - {licenseEdition.name} - </a> - ) - }} - /> - </span> - )} - </p> - {previewStatus !== 'NO_INSTALL' && ( - <span className="js-edition-tos"> - <Checkbox - checked={this.state.acceptTerms} - id="edition-terms" - onCheck={this.handleTermsCheck}> - <label className="little-spacer-left" htmlFor="edition-terms"> - {translate('marketplace.i_accept_the')} - </label> - </Checkbox> - <a - className="nowrap little-spacer-left" - href="http://dist.sonarsource.com/SonarSource_Terms_And_Conditions.pdf" - rel="noopener noreferrer" - target="_blank"> - {translate('marketplace.terms_and_conditions')} - </a> - </span> - )} - </div> - ); - } - - render() { - const { className, edition } = this.props; - const { license, loading } = this.state; - - return ( - <div className={className}> - {edition && ( - <label className="display-inline-block spacer-bottom" htmlFor="set-license"> - {translateWithParameters('marketplace.enter_license_for_x', edition.name)} - <em className="mandatory">*</em> - </label> - )} - <textarea - autoFocus={true} - className="display-block input-super-large" - id="set-license" - onChange={this.handleLicenseChange} - required={true} - rows={8} - style={{ resize: 'none' }} - value={license} - /> - - <DeferredSpinner - customSpinner={ - <p className="spacer-top"> - <i className="spinner spacer-right text-bottom" /> - {translate('marketplace.checking_license')} - </p> - } - loading={loading}> - {this.renderAlert()} - </DeferredSpinner> - </div> - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/UninstallEditionForm.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/UninstallEditionForm.tsx deleted file mode 100644 index 1b9be29c7df..00000000000 --- a/server/sonar-web/src/main/js/apps/marketplace/components/UninstallEditionForm.tsx +++ /dev/null @@ -1,94 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 { Edition, EditionStatus, uninstallEdition } from '../../../api/marketplace'; -import Modal from '../../../components/controls/Modal'; -import { Button, ResetButtonLink } from '../../../components/ui/buttons'; -import { translate, translateWithParameters } from '../../../helpers/l10n'; - -export interface Props { - edition?: Edition; - editionStatus: EditionStatus; - onClose: () => void; - updateEditionStatus: (editionStatus: EditionStatus) => void; -} - -interface State { - loading: boolean; -} - -export default class UninstallEditionForm extends React.PureComponent<Props, State> { - mounted = false; - state: State = { loading: false }; - - componentDidMount() { - this.mounted = true; - } - - componentWillUnmount() { - this.mounted = false; - } - - handleConfirmClick = () => { - this.setState({ loading: true }); - uninstallEdition() - .then(() => { - this.props.updateEditionStatus({ - ...this.props.editionStatus, - currentEditionKey: undefined, - installationStatus: 'UNINSTALL_IN_PROGRESS' - }); - this.props.onClose(); - }) - .catch(() => { - if (this.mounted) { - this.setState({ loading: false }); - } - }); - }; - - render() { - const { edition } = this.props; - const { loading } = this.state; - const currentEdition = edition ? edition.name : translate('marketplace.commercial_edition'); - const header = translateWithParameters('marketplace.downgrade_to_community_edition'); - return ( - <Modal contentLabel={header} onRequestClose={this.props.onClose}> - <header className="modal-head"> - <h2>{header}</h2> - </header> - - <div className="modal-body"> - <p>{translateWithParameters('marketplace.uninstall_x_confirmation', currentEdition)}</p> - </div> - - <footer className="modal-foot"> - {loading && <i className="spinner spacer-right" />} - <Button disabled={loading} onClick={this.handleConfirmClick}> - {translate('marketplace.downgrade')} - </Button> - <ResetButtonLink className="js-modal-close" onClick={this.props.onClose}> - {translate('cancel')} - </ResetButtonLink> - </footer> - </Modal> - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/EditionBox-test.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/EditionBox-test.tsx index c217058bb4f..ec0303053a4 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/EditionBox-test.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/EditionBox-test.tsx @@ -19,46 +19,22 @@ */ import * as React from 'react'; import { shallow } from 'enzyme'; -import { Edition, EditionStatus } from '../../../../api/marketplace'; import EditionBox from '../EditionBox'; -const DEFAULT_STATUS: EditionStatus = { - currentEditionKey: '', - nextEditionKey: '', - installationStatus: 'NONE' -}; - -const DEFAULT_EDITION: Edition = { +const DEFAULT_EDITION = { key: 'foo', - name: 'Foo', - textDescription: 'Foo desc', downloadUrl: 'download_url', - homeUrl: 'more_url', - licenseRequestUrl: 'license_url' + homeUrl: 'more_url' }; it('should display the edition', () => { - expect(getWrapper()).toMatchSnapshot(); -}); - -it('should disable action button', () => { - expect(getWrapper({ disableAction: true })).toMatchSnapshot(); + expect(getWrapper({ ncloc: 1000, serverId: 'serverId' })).toMatchSnapshot(); }); -it('should correctly hide action buttons', () => { - expect(getWrapper({ displayAction: false })).toMatchSnapshot(); +it('should show insalled badge', () => { + expect(getWrapper({ currentEdition: 'foo' })).toMatchSnapshot(); }); function getWrapper(props = {}) { - return shallow( - <EditionBox - actionLabel="action" - disableAction={false} - displayAction={true} - edition={DEFAULT_EDITION} - editionStatus={DEFAULT_STATUS} - onAction={jest.fn()} - {...props} - /> - ); + return shallow(<EditionBox currentEdition="community" edition={DEFAULT_EDITION} {...props} />); } diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/EditionBoxBadge-test.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/EditionBoxBadge-test.tsx deleted file mode 100644 index dfe79a80762..00000000000 --- a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/EditionBoxBadge-test.tsx +++ /dev/null @@ -1,81 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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({ - status: { - currentEditionKey: 'foo', - nextEditionKey: '', - installationStatus: 'NONE' - } - }) - ).toMatchSnapshot(); -}); - -it('should display installing badge', () => { - expect( - getWrapper({ - status: { - currentEditionKey: 'foo', - nextEditionKey: 'foo', - installationStatus: 'AUTOMATIC_IN_PROGRESS' - } - }) - ).toMatchSnapshot(); -}); - -it('should display pending badge', () => { - expect( - getWrapper({ - status: { - currentEditionKey: '', - nextEditionKey: 'foo', - installationStatus: 'AUTOMATIC_READY' - } - }) - ).toMatchSnapshot(); -}); - -it('should not display a badge', () => { - expect( - getWrapper({ - status: { - currentEditionKey: '', - nextEditionKey: 'bar', - installationStatus: 'AUTOMATIC_READY' - } - }).type() - ).toBeNull(); -}); - -function getWrapper(props = {}) { - return shallow(<EditionBoxBadge editionKey="foo" status={DEFAULT_STATUS} {...props} />); -} diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/LicenseEditionForm-test.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/LicenseEditionForm-test.tsx deleted file mode 100644 index 9814570cbf8..00000000000 --- a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/LicenseEditionForm-test.tsx +++ /dev/null @@ -1,98 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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. - */ -/* eslint-disable import/order */ -import * as React from 'react'; -import { shallow } from 'enzyme'; -import { click } from '../../../../helpers/testUtils'; -import LicenseEditionForm from '../LicenseEditionForm'; - -jest.mock('../../../../api/marketplace', () => ({ - applyLicense: jest.fn(() => - Promise.resolve({ nextEditionKey: 'foo', installationStatus: 'AUTOMATIC_IN_PROGRESS' }) - ) -})); - -const applyLicense = require('../../../../api/marketplace').applyLicense as jest.Mock<any>; - -const DEFAULT_EDITION = { - key: 'foo', - name: 'Foo', - textDescription: 'Foo desc', - downloadUrl: 'download_url', - homeUrl: 'more_url', - licenseRequestUrl: 'license_url' -}; - -beforeEach(() => { - applyLicense.mockClear(); -}); - -it('should display correctly', () => { - expect(getWrapper()).toMatchSnapshot(); -}); - -it('should correctly change the button based on the status and license', () => { - const wrapper = getWrapper(); - let button; - (wrapper.instance() as LicenseEditionForm).mounted = true; - - wrapper.setState({ license: 'mylicense', status: 'NO_INSTALL' }); - button = wrapper.find('Button'); - expect(button).toMatchSnapshot(); - - wrapper.setState({ license: undefined, status: 'MANUAL_INSTALL' }); - button = wrapper.find('Button'); - expect(button).toMatchSnapshot(); - - wrapper.setState({ status: 'AUTOMATIC_INSTALL' }); - button = wrapper.find('Button'); - expect(button).toMatchSnapshot(); - - wrapper.setState({ license: 'mylicense' }); - expect(wrapper.find('Button').prop('disabled')).toBeFalsy(); -}); - -it('should update the edition status after install', async () => { - const updateEditionStatus = jest.fn(); - const wrapper = getWrapper({ updateEditionStatus }); - const form = wrapper.instance() as LicenseEditionForm; - form.handleLicenseChange('mylicense', 'AUTOMATIC_INSTALL'); - wrapper.update(); - click(wrapper.find('Button')); - expect(applyLicense).toHaveBeenCalledWith({ license: 'mylicense' }); - await new Promise(setImmediate); - expect(updateEditionStatus).toHaveBeenCalledWith({ - nextEditionKey: 'foo', - installationStatus: 'AUTOMATIC_IN_PROGRESS' - }); -}); - -function getWrapper(props = {}) { - return shallow( - <LicenseEditionForm - edition={DEFAULT_EDITION} - editions={[DEFAULT_EDITION]} - isDowngrade={false} - onClose={jest.fn()} - updateEditionStatus={jest.fn()} - {...props} - /> - ); -} diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/LicenseEditionSet-test.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/LicenseEditionSet-test.tsx deleted file mode 100644 index 691f9c3613f..00000000000 --- a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/LicenseEditionSet-test.tsx +++ /dev/null @@ -1,126 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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. - */ -/* eslint-disable import/order */ -import * as React from 'react'; -import { shallow } from 'enzyme'; -import { change, waitAndUpdate } from '../../../../helpers/testUtils'; -import LicenseEditionSet from '../LicenseEditionSet'; - -jest.mock('../../../../api/marketplace', () => ({ - getLicensePreview: jest.fn(() => - Promise.resolve({ nextEditionKey: 'foo', previewStatus: 'NO_INSTALL' }) - ), - getFormData: jest.fn(() => Promise.resolve({ serverId: 'foo', ncloc: 1000 })) -})); - -jest.mock('lodash', () => { - const lodash = require.requireActual('lodash'); - lodash.debounce = (fn: Function) => (...args: any[]) => fn(...args); - return lodash; -}); - -const getLicensePreview = require('../../../../api/marketplace').getLicensePreview as jest.Mock< - any ->; - -const DEFAULT_EDITION = { - key: 'foo', - name: 'Foo', - textDescription: 'Foo desc', - downloadUrl: 'download_url', - homeUrl: 'more_url', - licenseRequestUrl: 'license_url' -}; - -beforeEach(() => { - getLicensePreview.mockClear(); -}); - -it('should display correctly', () => { - expect(getWrapper()).toMatchSnapshot(); -}); - -it('should display the get license link with parameters', async () => { - const wrapper = getWrapper(); - await waitAndUpdate(wrapper); - expect(wrapper.find('a')).toMatchSnapshot(); -}); - -it('should correctly display status message after checking license', async () => { - let wrapper = await testLicenseStatus('NO_INSTALL'); - expect(wrapper.find('p.alert')).toMatchSnapshot(); - wrapper = await testLicenseStatus('AUTOMATIC_INSTALL'); - expect(wrapper.find('p.alert')).toMatchSnapshot(); - wrapper = await testLicenseStatus('MANUAL_INSTALL'); - expect(wrapper.find('p.alert')).toMatchSnapshot(); - wrapper = await testLicenseStatus('AUTOMATIC_INSTALL', jest.fn(), 'bar'); - expect(wrapper.find('p.alert')).toMatchSnapshot(); - wrapper = await testLicenseStatus('AUTOMATIC_INSTALL', jest.fn(), 'bar', { edition: undefined }); - expect(wrapper.find('p.alert')).toMatchSnapshot(); -}); - -it('should display terms of license checkbox', async () => { - let updateLicense = jest.fn(); - let wrapper = await testLicenseStatus('NO_INSTALL', updateLicense); - expect(wrapper.find('.js-edition-tos').exists()).toBeFalsy(); - expect(updateLicense).toHaveBeenCalledWith('mylicense', 'NO_INSTALL'); - - updateLicense = jest.fn(); - wrapper = await testLicenseStatus('AUTOMATIC_INSTALL', updateLicense); - const tosCheckbox = wrapper.find('.js-edition-tos'); - expect(tosCheckbox.find('a').exists()).toBeTruthy(); - expect(updateLicense).toHaveBeenLastCalledWith(undefined, 'AUTOMATIC_INSTALL'); - (tosCheckbox.find('Checkbox').prop('onCheck') as Function)(true); - expect(updateLicense).toHaveBeenLastCalledWith('mylicense', 'AUTOMATIC_INSTALL'); - - updateLicense = jest.fn(); - wrapper = await testLicenseStatus('MANUAL_INSTALL', updateLicense); - expect(wrapper.find('.js-edition-tos').exists()).toBeTruthy(); - expect(updateLicense).toHaveBeenLastCalledWith(undefined, 'MANUAL_INSTALL'); -}); - -function getWrapper(props = {}) { - return shallow( - <LicenseEditionSet - edition={DEFAULT_EDITION} - editions={[DEFAULT_EDITION]} - updateLicense={jest.fn()} - {...props} - /> - ); -} - -async function testLicenseStatus( - status: string, - updateLicense = jest.fn(), - nextEditionKey = 'foo', - props = {} -) { - getLicensePreview.mockImplementation(() => - Promise.resolve({ nextEditionKey, previewStatus: status }) - ); - const wrapper = getWrapper({ updateLicense, ...props }); - change(wrapper.find('textarea'), 'mylicense'); - expect(getLicensePreview).toHaveBeenCalled(); - await new Promise(setImmediate); - expect(updateLicense).toHaveBeenCalled(); - wrapper.update(); - return wrapper; -} diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/UninstallEditionForm-test.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/UninstallEditionForm-test.tsx deleted file mode 100644 index f8dbede4cb2..00000000000 --- a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/UninstallEditionForm-test.tsx +++ /dev/null @@ -1,72 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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. - */ -/* eslint-disable import/order */ -import * as React from 'react'; -import { shallow } from 'enzyme'; -import { click } from '../../../../helpers/testUtils'; -import UninstallEditionForm from '../UninstallEditionForm'; - -jest.mock('../../../../api/marketplace', () => ({ - uninstallEdition: jest.fn(() => Promise.resolve()) -})); - -const uninstallEdition = require('../../../../api/marketplace').uninstallEdition as jest.Mock<any>; - -const DEFAULT_EDITION = { - key: 'foo', - name: 'Foo', - textDescription: 'Foo desc', - downloadUrl: 'download_url', - homeUrl: 'more_url', - licenseRequestUrl: 'license_url' -}; - -beforeEach(() => { - uninstallEdition.mockClear(); -}); - -it('should display correctly', () => { - expect(getWrapper()).toMatchSnapshot(); -}); - -it('should update the edition status after uninstall', async () => { - const updateEditionStatus = jest.fn(); - const wrapper = getWrapper({ updateEditionStatus }); - (wrapper.instance() as UninstallEditionForm).mounted = true; - click(wrapper.find('Button')); - expect(uninstallEdition).toHaveBeenCalled(); - await new Promise(setImmediate); - expect(updateEditionStatus).toHaveBeenCalledWith({ - currentEditionKey: undefined, - installationStatus: 'UNINSTALL_IN_PROGRESS' - }); -}); - -function getWrapper(props = {}) { - return shallow( - <UninstallEditionForm - edition={DEFAULT_EDITION} - editionStatus={{ currentEditionKey: 'foo', installationStatus: 'NONE' }} - onClose={jest.fn()} - updateEditionStatus={jest.fn()} - {...props} - /> - ); -} diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/EditionBox-test.tsx.snap b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/EditionBox-test.tsx.snap index b9925cec2c1..39c71155bc6 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/EditionBox-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/EditionBox-test.tsx.snap @@ -1,34 +1,19 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`should correctly hide action buttons 1`] = ` +exports[`should display the edition 1`] = ` <div className="boxed-group boxed-group-inner marketplace-edition" > - <EditionBoxBadge - editionKey="foo" - status={ - Object { - "currentEditionKey": "", - "installationStatus": "NONE", - "nextEditionKey": "", - } - } - /> <div> - <h3 - className="spacer-bottom" - > - Foo - </h3> - <p> - Foo desc - </p> + <DocInclude + path="/tooltips/editions/foo" + /> </div> <div className="marketplace-edition-action spacer-top" > <a - href="more_url" + href="more_url?ncloc=1000&serverId=serverId&sourceEdition=community" target="_blank" > marketplace.learn_more @@ -37,88 +22,33 @@ exports[`should correctly hide action buttons 1`] = ` </div> `; -exports[`should disable action button 1`] = ` +exports[`should show insalled badge 1`] = ` <div className="boxed-group boxed-group-inner marketplace-edition" > - <EditionBoxBadge - editionKey="foo" - status={ - Object { - "currentEditionKey": "", - "installationStatus": "NONE", - "nextEditionKey": "", - } - } - /> - <div> - <h3 - className="spacer-bottom" - > - Foo - </h3> - <p> - Foo desc - </p> - </div> - <div - className="marketplace-edition-action spacer-top" + <span + className="marketplace-edition-badge badge badge-normal-size display-flex-center" > - <a - href="more_url" - target="_blank" - > - marketplace.learn_more - </a> - <Button - disabled={true} - onClick={[Function]} - > - action - </Button> - </div> -</div> -`; - -exports[`should display the edition 1`] = ` -<div - className="boxed-group boxed-group-inner marketplace-edition" -> - <EditionBoxBadge - editionKey="foo" - status={ - Object { - "currentEditionKey": "", - "installationStatus": "NONE", - "nextEditionKey": "", - } - } - /> + <CheckIcon + className="little-spacer-right" + size={14} + /> + marketplace.installed + </span> <div> - <h3 - className="spacer-bottom" - > - Foo - </h3> - <p> - Foo desc - </p> + <DocInclude + path="/tooltips/editions/foo" + /> </div> <div className="marketplace-edition-action spacer-top" > <a - href="more_url" + href="more_url?sourceEdition=foo" target="_blank" > marketplace.learn_more </a> - <Button - disabled={false} - onClick={[Function]} - > - action - </Button> </div> </div> `; diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/EditionBoxBadge-test.tsx.snap b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/EditionBoxBadge-test.tsx.snap deleted file mode 100644 index 733b0062157..00000000000 --- a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/EditionBoxBadge-test.tsx.snap +++ /dev/null @@ -1,29 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should display installed badge 1`] = ` -<span - className="marketplace-edition-badge badge badge-normal-size display-flex-center" -> - <CheckIcon - className="little-spacer-right" - size={14} - /> - marketplace.installed -</span> -`; - -exports[`should display installing badge 1`] = ` -<span - className="marketplace-edition-badge badge badge-normal-size" -> - marketplace.installing -</span> -`; - -exports[`should display pending badge 1`] = ` -<span - className="marketplace-edition-badge badge badge-normal-size" -> - marketplace.pending -</span> -`; diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/LicenseEditionForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/LicenseEditionForm-test.tsx.snap deleted file mode 100644 index c0dcce3c40a..00000000000 --- a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/LicenseEditionForm-test.tsx.snap +++ /dev/null @@ -1,81 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should correctly change the button based on the status and license 1`] = ` -<Button - className="js-confirm" - disabled={false} - onClick={[Function]} -> - save -</Button> -`; - -exports[`should correctly change the button based on the status and license 2`] = ` -<Button - className="js-confirm" - disabled={true} - onClick={[Function]} -> - save -</Button> -`; - -exports[`should correctly change the button based on the status and license 3`] = ` -<Button - className="js-confirm" - disabled={true} - onClick={[Function]} -> - marketplace.install -</Button> -`; - -exports[`should display correctly 1`] = ` -<Modal - contentLabel="marketplace.upgrade_to_x.Foo" - onRequestClose={[MockFunction]} -> - <header - className="modal-head" - > - <h2> - marketplace.upgrade_to_x.Foo - </h2> - </header> - <LicenseEditionSet - className="modal-body" - edition={ - Object { - "downloadUrl": "download_url", - "homeUrl": "more_url", - "key": "foo", - "licenseRequestUrl": "license_url", - "name": "Foo", - "textDescription": "Foo desc", - } - } - editions={ - Array [ - Object { - "downloadUrl": "download_url", - "homeUrl": "more_url", - "key": "foo", - "licenseRequestUrl": "license_url", - "name": "Foo", - "textDescription": "Foo desc", - }, - ] - } - updateLicense={[Function]} - /> - <footer - className="modal-foot" - > - <ResetButtonLink - onClick={[MockFunction]} - > - cancel - </ResetButtonLink> - </footer> -</Modal> -`; diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/LicenseEditionSet-test.tsx.snap b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/LicenseEditionSet-test.tsx.snap deleted file mode 100644 index 5ff0d2f7d37..00000000000 --- a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/LicenseEditionSet-test.tsx.snap +++ /dev/null @@ -1,105 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should correctly display status message after checking license 1`] = ` -<p - className="alert alert-success" -> - marketplace.license_preview_status.NO_INSTALL.Foo -</p> -`; - -exports[`should correctly display status message after checking license 2`] = ` -<p - className="alert alert-warning" -> - marketplace.license_preview_status.AUTOMATIC_INSTALL.Foo -</p> -`; - -exports[`should correctly display status message after checking license 3`] = ` -<p - className="alert alert-danger" -> - marketplace.license_preview_status.MANUAL_INSTALL.Foo -</p> -`; - -exports[`should correctly display status message after checking license 4`] = ` -<p - className="alert alert-danger" -> - marketplace.wrong_license_type_x.Foo -</p> -`; - -exports[`should correctly display status message after checking license 5`] = ` -<p - className="alert alert-danger" -> - marketplace.wrong_license_type -</p> -`; - -exports[`should display correctly 1`] = ` -<div> - <label - className="display-inline-block spacer-bottom" - htmlFor="set-license" - > - marketplace.enter_license_for_x.Foo - <em - className="mandatory" - > - * - </em> - </label> - <textarea - autoFocus={true} - className="display-block input-super-large" - id="set-license" - onChange={[Function]} - required={true} - rows={8} - style={ - Object { - "resize": "none", - } - } - value="" - /> - <DeferredSpinner - customSpinner={ - <p - className="spacer-top" - > - <i - className="spinner spacer-right text-bottom" - /> - marketplace.checking_license - </p> - } - loading={false} - timeout={100} - > - <div - className="spacer-top" - > - <a - href="license_url" - target="_blank" - > - marketplace.i_need_a_license - </a> - </div> - </DeferredSpinner> -</div> -`; - -exports[`should display the get license link with parameters 1`] = ` -<a - href="license_url?serverId=foo&ncloc=1000" - target="_blank" -> - marketplace.i_need_a_license -</a> -`; diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/UninstallEditionForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/UninstallEditionForm-test.tsx.snap deleted file mode 100644 index 491d59eb8f5..00000000000 --- a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/UninstallEditionForm-test.tsx.snap +++ /dev/null @@ -1,39 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should display correctly 1`] = ` -<Modal - contentLabel="marketplace.downgrade_to_community_edition." - onRequestClose={[MockFunction]} -> - <header - className="modal-head" - > - <h2> - marketplace.downgrade_to_community_edition. - </h2> - </header> - <div - className="modal-body" - > - <p> - marketplace.uninstall_x_confirmation.Foo - </p> - </div> - <footer - className="modal-foot" - > - <Button - disabled={false} - onClick={[Function]} - > - marketplace.downgrade - </Button> - <ResetButtonLink - className="js-modal-close" - onClick={[MockFunction]} - > - cancel - </ResetButtonLink> - </footer> -</Modal> -`; diff --git a/server/sonar-web/src/main/js/apps/marketplace/style.css b/server/sonar-web/src/main/js/apps/marketplace/style.css index 9e51642bbc5..0e2ef2a2831 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/style.css +++ b/server/sonar-web/src/main/js/apps/marketplace/style.css @@ -35,6 +35,11 @@ margin-right: 8px; } +.marketplace-edition .markdown h3 { + font-size: 14px; + margin-top: 0; +} + .marketplace-edition-badge { position: absolute; right: -1px; 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 690b6873121..9aebc85c3ad 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/utils.ts +++ b/server/sonar-web/src/main/js/apps/marketplace/utils.ts @@ -17,26 +17,58 @@ * 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 { stringify } from 'querystring'; +import { memoize } from 'lodash'; import { Plugin, PluginAvailable, PluginInstalled, PluginPending } from '../../api/plugins'; import { cleanQuery, parseAsString, RawQuery, serializeString } from '../../helpers/query'; -import { Edition } from '../../api/marketplace'; +import { omitNil } from '../../helpers/request'; + +export interface Edition { + downloadUrl?: string; + homeUrl: string; + key: string; +} export interface Query { filter: string; search?: string; } -export function isPluginAvailable(plugin: Plugin): plugin is PluginAvailable { - return (plugin as any).release !== undefined; -} +export const EDITIONS: Edition[] = [ + { + key: 'community', + homeUrl: 'https://redirect.sonarsource.com/editions/community.html' + }, + { + key: 'developer', + homeUrl: 'https://redirect.sonarsource.com/editions/developer.html', + downloadUrl: + 'https://sonarsource.bintray.com/CommercialDistribution/editions/developer-edition-7.0.0.717.zip' + }, + { + key: 'enterprise', + homeUrl: 'https://redirect.sonarsource.com/editions/enterprise.html', + downloadUrl: + 'https://sonarsource.bintray.com/CommercialDistribution/editions/enterprise-edition-7.0.0.717.zip' + }, + { + key: 'datacenter', + homeUrl: 'https://redirect.sonarsource.com/editions/datacenter.html', + downloadUrl: + 'https://sonarsource.bintray.com/CommercialDistribution/editions/datacenter-edition-7.0.0.717.zip' + } +]; -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 function getEditionUrl( + edition: Edition, + data: { serverId?: string; ncloc?: number; sourceEdition: string } +) { + let url = edition.homeUrl; + const query = stringify(omitNil(data)); + if (query) { + url += '?' + query; + } + return url; } export function filterPlugins(plugins: Plugin[], search: string): Plugin[] { @@ -50,9 +82,16 @@ export function filterPlugins(plugins: Plugin[], search: string): Plugin[] { }); } -const EDITIONS_ORDER = ['community', 'developer', 'enterprise', 'datacenter']; -export function sortEditions(editions: Edition[]): Edition[] { - return sortBy(editions, edition => EDITIONS_ORDER.indexOf(edition.key)); +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'; diff --git a/server/sonar-web/src/main/js/store/marketplace/actions.ts b/server/sonar-web/src/main/js/store/marketplace/actions.ts index 894c74f2398..7e9b6156d9c 100644 --- a/server/sonar-web/src/main/js/store/marketplace/actions.ts +++ b/server/sonar-web/src/main/js/store/marketplace/actions.ts @@ -18,62 +18,34 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { Dispatch } from 'react-redux'; -import { getEditionsForVersion, getEditionsForLastVersion } from './utils'; -import { Edition, EditionStatus, getEditionStatus, getEditionsList } from '../../api/marketplace'; +import { getEditionStatus } from '../../api/marketplace'; import { getPendingPlugins, PluginPendingResult } from '../../api/plugins'; -interface LoadEditionsAction { - type: 'LOAD_EDITIONS'; - loading: boolean; -} - interface SetPendingPluginsAction { type: 'SET_PENDING_PLUGINS'; pending: PluginPendingResult; } -interface SetEditionsAction { - type: 'SET_EDITIONS'; - editions: Edition[]; - readOnly: boolean; -} - -interface SetEditionStatusAction { - type: 'SET_EDITION_STATUS'; - status: EditionStatus; +interface SetCurrentEditionAction { + type: 'SET_CURRENT_EDITION'; + currentEdition?: string; } -export type Action = - | LoadEditionsAction - | SetEditionsAction - | SetEditionStatusAction - | SetPendingPluginsAction; - -export function loadEditions(loading = true): LoadEditionsAction { - return { type: 'LOAD_EDITIONS', loading }; -} +export type Action = SetCurrentEditionAction | SetPendingPluginsAction; export function setPendingPlugins(pending: PluginPendingResult): SetPendingPluginsAction { return { type: 'SET_PENDING_PLUGINS', pending }; } -export function setEditions(editions: Edition[], readOnly?: boolean): SetEditionsAction { - return { type: 'SET_EDITIONS', editions, readOnly: !!readOnly }; -} +export const setCurrentEdition = (currentEdition?: string) => (dispatch: Dispatch<Action>) => { + dispatch({ type: 'SET_CURRENT_EDITION', currentEdition }); +}; -let editionTimer: number | undefined; -export const setEditionStatus = (status: EditionStatus) => (dispatch: Dispatch<Action>) => { - dispatch({ type: 'SET_EDITION_STATUS', status }); - if (editionTimer) { - window.clearTimeout(editionTimer); - editionTimer = undefined; - } - if (status.installationStatus === 'AUTOMATIC_IN_PROGRESS') { - editionTimer = window.setTimeout(() => { - getEditionStatus().then(status => setEditionStatus(status)(dispatch), () => {}); - editionTimer = undefined; - }, 2000); - } +export const fetchCurrentEdition = () => (dispatch: Dispatch<Action>) => { + getEditionStatus().then( + editionStatus => dispatch(setCurrentEdition(editionStatus.currentEditionKey)), + () => {} + ); }; export const fetchPendingPlugins = () => (dispatch: Dispatch<Action>) => { @@ -84,18 +56,3 @@ export const fetchPendingPlugins = () => (dispatch: Dispatch<Action>) => { () => {} ); }; - -export const fetchEditions = (url: string, version: string) => (dispatch: Dispatch<Action>) => { - dispatch(loadEditions(true)); - getEditionsList(url).then( - editionsPerVersion => { - const editions = getEditionsForVersion(editionsPerVersion, version); - if (editions) { - dispatch(setEditions(editions)); - } else { - dispatch(setEditions(getEditionsForLastVersion(editionsPerVersion), true)); - } - }, - () => dispatch(loadEditions(false)) - ); -}; diff --git a/server/sonar-web/src/main/js/store/marketplace/reducer.ts b/server/sonar-web/src/main/js/store/marketplace/reducer.ts index 11c5e28d7f5..55ad93d69db 100644 --- a/server/sonar-web/src/main/js/store/marketplace/reducer.ts +++ b/server/sonar-web/src/main/js/store/marketplace/reducer.ts @@ -18,48 +18,30 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { Action } from './actions'; -import { Edition, EditionStatus } from '../../api/marketplace'; import { PluginPendingResult } from '../../api/plugins'; interface State { - editions?: Edition[]; - loading: boolean; - status?: EditionStatus; - readOnly: boolean; + currentEdition?: string; pending: PluginPendingResult; } -const defaultState: State = { - loading: true, - readOnly: false, - pending: { installing: [], removing: [], updating: [] } -}; +const defaultState: State = { pending: { installing: [], removing: [], updating: [] } }; export default function(state: State = defaultState, action: Action): State { - if (action.type === 'SET_EDITIONS') { - return { ...state, editions: action.editions, readOnly: action.readOnly, loading: false }; - } - if (action.type === 'LOAD_EDITIONS') { - return { ...state, loading: action.loading }; - } if (action.type === 'SET_PENDING_PLUGINS') { return { ...state, pending: action.pending }; } - if (action.type === 'SET_EDITION_STATUS') { - const hasChanged = Object.keys(action.status).some( - (key: keyof EditionStatus) => !state.status || state.status[key] !== action.status[key] - ); - // Prevent from rerendering the whole admin if the status didn't change - if (hasChanged) { - return { ...state, status: action.status }; - } + if (action.type === 'SET_CURRENT_EDITION') { + return { + ...state, + currentEdition: action.currentEdition + }; } return state; } -export const getEditions = (state: State) => state.editions; -export const getEditionStatus = (state: State) => state.status; +export const getCurrentEdition = (state: State) => state.currentEdition; export const getPendingPlugins = (state: State) => state.pending; diff --git a/server/sonar-web/src/main/js/store/marketplace/utils.ts b/server/sonar-web/src/main/js/store/marketplace/utils.ts deleted file mode 100644 index 7e22118a78d..00000000000 --- a/server/sonar-web/src/main/js/store/marketplace/utils.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 { sortBy } from 'lodash'; -import { Edition, EditionsPerVersion } from '../../api/marketplace'; - -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; -} diff --git a/server/sonar-web/src/main/js/store/rootReducer.js b/server/sonar-web/src/main/js/store/rootReducer.js index 0ca7aab07c4..7581381ce28 100644 --- a/server/sonar-web/src/main/js/store/rootReducer.js +++ b/server/sonar-web/src/main/js/store/rootReducer.js @@ -73,10 +73,8 @@ export const isFavorite = (state, componentKey) => export const getMarketplaceState = state => state.marketplace; -export const getMarketplaceEditions = state => fromMarketplace.getEditions(state.marketplace); - -export const getMarketplaceEditionStatus = state => - fromMarketplace.getEditionStatus(state.marketplace); +export const getMarketplaceCurrentEdition = state => + fromMarketplace.getCurrentEdition(state.marketplace); export const getMarketplacePendingPlugins = state => fromMarketplace.getPendingPlugins(state.marketplace); |