From bff95ad641af2546712ea61d38a259f0847343c5 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Gr=C3=A9goire=20Aubert?= Date: Tue, 17 Oct 2017 16:17:11 +0200 Subject: [PATCH] SONAR-9936 Update edition notification to be scoped to page and not whole administration --- .../main/js/app/components/AdminContainer.tsx | 15 +++----- .../components/nav/settings/SettingsNav.tsx | 14 ++------ .../settings/__tests__/SettingsNav-test.tsx | 19 +---------- .../__snapshots__/SettingsNav-test.tsx.snap | 15 -------- .../src/main/js/apps/marketplace/App.tsx | 25 +++++++++++--- .../main/js/apps/marketplace/AppContainer.tsx | 1 - .../main/js/apps/marketplace/EditionBoxes.tsx | 7 +++- .../js/apps/marketplace/PendingActions.tsx | 5 ++- .../__tests__/EditionBoxes-test.tsx | 1 + .../components/EditionsStatusNotif.tsx} | 34 +++++++++++++------ .../components/LicenseEditionForm.tsx | 7 ++-- .../__tests__/EditionsStatusNotif-test.tsx} | 8 ++--- .../__tests__/LicenseEditionForm-test.tsx | 23 ++++++++----- .../EditionsStatusNotif-test.tsx.snap} | 24 ++++++++----- .../src/main/js/store/appState/duck.ts | 21 +----------- .../src/main/less/components/alerts.less | 5 +++ .../resources/org/sonar/l10n/core.properties | 8 ++--- 17 files changed, 107 insertions(+), 125 deletions(-) rename server/sonar-web/src/main/js/{app/components/nav/settings/SettingsEditionsNotif.tsx => apps/marketplace/components/EditionsStatusNotif.tsx} (65%) rename server/sonar-web/src/main/js/{app/components/nav/settings/__tests__/SettingsEditionsNotif-test.tsx => apps/marketplace/components/__tests__/EditionsStatusNotif-test.tsx} (79%) rename server/sonar-web/src/main/js/{app/components/nav/settings/__tests__/__snapshots__/SettingsEditionsNotif-test.tsx.snap => apps/marketplace/components/__tests__/__snapshots__/EditionsStatusNotif-test.tsx.snap} (67%) 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 fec085b91f2..ea9c6f1301b 100644 --- a/server/sonar-web/src/main/js/app/components/AdminContainer.tsx +++ b/server/sonar-web/src/main/js/app/components/AdminContainer.tsx @@ -24,20 +24,17 @@ import { connect } from 'react-redux'; import SettingsNav from './nav/settings/SettingsNav'; import { getAppState } from '../../store/rootReducer'; import { getSettingsNavigation } from '../../api/nav'; -import { EditionStatus, getEditionStatus } from '../../api/marketplace'; -import { setAdminPages, setEditionStatus } from '../../store/appState/duck'; +import { setAdminPages } from '../../store/appState/duck'; import { translate } from '../../helpers/l10n'; import { Extension } from '../types'; interface Props { appState: { adminPages: Extension[]; - editionStatus?: EditionStatus; organizationsEnabled: boolean; }; location: {}; setAdminPages: (adminPages: Extension[]) => void; - setEditionStatus: (editionStatus: EditionStatus) => void; } class AdminContainer extends React.PureComponent { @@ -56,17 +53,16 @@ class AdminContainer extends React.PureComponent { } loadData() { - Promise.all([getSettingsNavigation(), getEditionStatus()]).then( - ([r, editionStatus]) => { + getSettingsNavigation().then( + r => { this.props.setAdminPages(r.extensions); - this.props.setEditionStatus(editionStatus); }, () => {} ); } render() { - const { adminPages, editionStatus, organizationsEnabled } = this.props.appState; + const { adminPages, organizationsEnabled } = this.props.appState; // Check that the adminPages are loaded if (!adminPages) { @@ -80,7 +76,6 @@ class AdminContainer extends React.PureComponent { @@ -94,6 +89,6 @@ const mapStateToProps = (state: any) => ({ appState: getAppState(state) }); -const mapDispatchToProps = { setAdminPages, setEditionStatus }; +const mapDispatchToProps = { setAdminPages }; export default connect(mapStateToProps, mapDispatchToProps)(AdminContainer as any); 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 b677146a392..7f7632d6630 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 @@ -21,14 +21,11 @@ import * as React from 'react'; import * as classNames from 'classnames'; import { IndexLink, Link } from 'react-router'; import ContextNavBar from '../../../../components/nav/ContextNavBar'; -import SettingsEditionsNotif from './SettingsEditionsNotif'; import NavBarTabs from '../../../../components/nav/NavBarTabs'; -import { EditionStatus } from '../../../../api/marketplace'; import { Extension } from '../../../types'; import { translate } from '../../../../helpers/l10n'; interface Props { - editionStatus?: EditionStatus; extensions: Extension[]; customOrganizations: boolean; location: {}; @@ -75,7 +72,7 @@ export default class SettingsNav extends React.PureComponent { }; render() { - const { customOrganizations, editionStatus, extensions } = this.props; + const { customOrganizations, extensions } = this.props; const isSecurity = this.isSecurityActive(); const isProjects = this.isProjectsActive(); const isSystem = this.isSystemActive(); @@ -94,15 +91,8 @@ export default class SettingsNav extends React.PureComponent { const hasSupportExtension = extensionsWithoutSupport.length < extensions.length; - let notifComponent; - if (editionStatus && editionStatus.installationStatus !== 'NONE') { - notifComponent = ; - } return ( - +

{translate('layout.settings')}

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 f819af90c49..9650ec9f4a9 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 @@ -24,24 +24,7 @@ import SettingsNav from '../SettingsNav'; it('should work with extensions', () => { const extensions = [{ key: 'foo', name: 'Foo' }]; const wrapper = shallow( - + ); expect(wrapper).toMatchSnapshot(); }); - -it('should display an edition notification', () => { - const wrapper = shallow( - - ); - expect({ ...wrapper.find('ContextNavBar').props(), children: [] }).toMatchSnapshot(); -}); 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 9dd9e567752..62c113f24bb 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,20 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`should display an edition notification 1`] = ` -Object { - "children": Array [], - "height": 95, - "id": "context-navigation", - "notif": , -} -`; - exports[`should work with extensions 1`] = ` { componentDidMount() { this.mounted = true; this.fetchPendingPlugins(); + this.fetchEditionStatus(); this.fetchQueryPlugins(); } @@ -148,6 +150,19 @@ export default class App extends React.PureComponent { () => {} ); + fetchEditionStatus = () => + getEditionStatus().then( + editionStatus => { + if (this.mounted) { + this.updateEditionStatus(editionStatus); + } + }, + () => {} + ); + + updateEditionStatus = (editionStatus: EditionStatus) => + this.setState({ editionStatus: editionStatus }); + updateQuery = (newQuery: Partial) => { const query = serializeQuery({ ...parseQuery(this.props.location.query), @@ -160,18 +175,20 @@ export default class App extends React.PureComponent { }; render() { - const { plugins, pending } = this.state; + const { editionStatus, plugins, pending } = this.state; const query = parseQuery(this.props.location.query); const filteredPlugins = query.search ? filterPlugins(plugins, query.search) : plugins; return (
+ {editionStatus && }
({ - editionStatus: getAppState(state).editionStatus, editionsUrl: (getGlobalSettingValue(state, 'sonar.editions.jsonUrl') || {}).value, sonarqubeVersion: getAppState(state).version, updateCenterActive: (getGlobalSettingValue(state, 'sonar.updatecenter.activate') || {}).value 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 58ffe846c63..e1f5f373f1f 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/EditionBoxes.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/EditionBoxes.tsx @@ -30,6 +30,7 @@ export interface Props { editionsUrl: string; sonarqubeVersion: string; updateCenterActive: boolean; + updateEditionStatus: (editionStatus: EditionStatus) => void; } interface State { @@ -108,7 +109,11 @@ export default class EditionBoxes extends React.PureComponent { )} {installEdition && ( - + )}
); diff --git a/server/sonar-web/src/main/js/apps/marketplace/PendingActions.tsx b/server/sonar-web/src/main/js/apps/marketplace/PendingActions.tsx index 921afeef3da..bc96da9bbf0 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/PendingActions.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/PendingActions.tsx @@ -19,9 +19,9 @@ */ import * as React from 'react'; import { FormattedMessage } from 'react-intl'; -import { translate } from '../../helpers/l10n'; -import { cancelPendingPlugins, PluginPending } from '../../api/plugins'; import RestartForm from '../../components/common/RestartForm'; +import { cancelPendingPlugins, PluginPending } from '../../api/plugins'; +import { translate } from '../../helpers/l10n'; interface Props { pending: { @@ -40,7 +40,6 @@ export default class PendingActions extends React.PureComponent { state: State = { openRestart: false }; handleOpenRestart = () => this.setState({ openRestart: true }); - hanleCloseRestart = () => this.setState({ openRestart: false }); handleRevert = () => { 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 c3525b0e304..e64c714577f 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 @@ -80,6 +80,7 @@ function getWrapper(props = {}) { editionsUrl="" sonarqubeVersion="6.7.5" updateCenterActive={true} + updateEditionStatus={jest.fn()} {...props} /> ); diff --git a/server/sonar-web/src/main/js/app/components/nav/settings/SettingsEditionsNotif.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/EditionsStatusNotif.tsx similarity index 65% rename from server/sonar-web/src/main/js/app/components/nav/settings/SettingsEditionsNotif.tsx rename to server/sonar-web/src/main/js/apps/marketplace/components/EditionsStatusNotif.tsx index 5df886e38b2..1ef4aacc308 100644 --- a/server/sonar-web/src/main/js/app/components/nav/settings/SettingsEditionsNotif.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/components/EditionsStatusNotif.tsx @@ -18,41 +18,53 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import NavBarNotif from '../../../../components/nav/NavBarNotif'; -import { EditionStatus } from '../../../../api/marketplace'; -import { translate } from '../../../../helpers/l10n'; +import RestartForm from '../../../components/common/RestartForm'; +import { EditionStatus } from '../../../api/marketplace'; +import { translate } from '../../../helpers/l10n'; interface Props { editionStatus: EditionStatus; } -export default class SettingsEditionsNotif extends React.PureComponent { +interface State { + openRestart: boolean; +} + +export default class EditionsStatusNotif extends React.PureComponent { + state: State = { openRestart: false }; + + handleOpenRestart = () => this.setState({ openRestart: true }); + hanleCloseRestart = () => this.setState({ openRestart: false }); + render() { const { editionStatus } = this.props; - if (editionStatus.installationStatus === 'AUTOMATIC_IN_PROGRESS') { return ( - +
{translate('marketplace.status.AUTOMATIC_IN_PROGRESS')} - +
); } else if (editionStatus.installationStatus === 'AUTOMATIC_READY') { return ( - +
{translate('marketplace.status.AUTOMATIC_READY')} - + + {this.state.openRestart && } +
); } else if ( ['MANUAL_IN_PROGRESS', 'AUTOMATIC_FAILURE'].includes(editionStatus.installationStatus) ) { return ( - +
{translate('marketplace.status', editionStatus.installationStatus)} {translate('marketplace.how_to_install')} - +
); } 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 index 1f00178f113..fb513eba0bc 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/components/LicenseEditionForm.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/components/LicenseEditionForm.tsx @@ -20,14 +20,13 @@ import * as React from 'react'; import Modal from 'react-modal'; import LicenseEditionSet from './LicenseEditionSet'; -import getStore from '../../../app/utils/getStore'; -import { setEditionStatus } from '../../../store/appState/duck'; -import { Edition, applyLicense } from '../../../api/marketplace'; +import { Edition, EditionStatus, applyLicense } from '../../../api/marketplace'; import { translate, translateWithParameters } from '../../../helpers/l10n'; export interface Props { edition: Edition; onClose: () => void; + updateEditionStatus: (editionStatus: EditionStatus) => void; } interface State { @@ -66,7 +65,7 @@ export default class LicenseEditionForm extends React.PureComponent { - getStore().dispatch(setEditionStatus(editionStatus)); + this.props.updateEditionStatus(editionStatus); this.props.onClose(); }, () => { diff --git a/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/SettingsEditionsNotif-test.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/EditionsStatusNotif-test.tsx similarity index 79% rename from server/sonar-web/src/main/js/app/components/nav/settings/__tests__/SettingsEditionsNotif-test.tsx rename to server/sonar-web/src/main/js/apps/marketplace/components/__tests__/EditionsStatusNotif-test.tsx index 55612c06b34..48a65f4b20f 100644 --- a/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/SettingsEditionsNotif-test.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/EditionsStatusNotif-test.tsx @@ -19,25 +19,25 @@ */ import * as React from 'react'; import { shallow } from 'enzyme'; -import SettingsEditionsNotif from '../SettingsEditionsNotif'; +import EditionsStatusNotif from '../EditionsStatusNotif'; it('should display an in progress notif', () => { const wrapper = shallow( - + ); expect(wrapper).toMatchSnapshot(); }); it('should display an error notification', () => { const wrapper = shallow( - + ); expect(wrapper).toMatchSnapshot(); }); it('should display a ready notification', () => { const wrapper = shallow( - + ); expect(wrapper).toMatchSnapshot(); }); 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 index 7dff71932d5..a08b002a718 100644 --- 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 @@ -22,10 +22,6 @@ import { shallow } from 'enzyme'; import { click } from '../../../../helpers/testUtils'; import LicenseEditionForm from '../LicenseEditionForm'; -jest.mock('../../../../app/utils/getStore', () => { - const dispatch = jest.fn(); - return { default: () => ({ dispatch }) }; -}); jest.mock('../../../../api/marketplace', () => ({ applyLicense: jest.fn(() => Promise.resolve({ nextEditionKey: 'foo', installationStatus: 'AUTOMATIC_IN_PROGRESS' }) @@ -33,7 +29,6 @@ jest.mock('../../../../api/marketplace', () => ({ })); const applyLicense = require('../../../../api/marketplace').applyLicense as jest.Mock; -const getStore = require('../../../../app/utils/getStore').default as jest.Mock; const DEFAULT_EDITION = { key: 'foo', @@ -46,7 +41,6 @@ const DEFAULT_EDITION = { beforeEach(() => { applyLicense.mockClear(); - getStore().dispatch.mockClear(); }); it('should display correctly', () => { @@ -65,16 +59,27 @@ it('should correctly change the button based on the status', () => { }); it('should update the edition status after install', async () => { - const wrapper = getWrapper(); + const updateEditionStatus = jest.fn(); + const wrapper = getWrapper({ updateEditionStatus }); const form = wrapper.instance() as LicenseEditionForm; form.mounted = true; form.handleLicenseChange('mylicense', 'AUTOMATIC_INSTALL'); click(wrapper.find('button')); expect(applyLicense).toHaveBeenCalledWith({ license: 'mylicense' }); await new Promise(setImmediate); - expect(getStore().dispatch).toHaveBeenCalled(); + expect(updateEditionStatus).toHaveBeenCalledWith({ + nextEditionKey: 'foo', + installationStatus: 'AUTOMATIC_IN_PROGRESS' + }); }); function getWrapper(props = {}) { - return shallow(); + return shallow( + + ); } 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/apps/marketplace/components/__tests__/__snapshots__/EditionsStatusNotif-test.tsx.snap similarity index 67% rename from server/sonar-web/src/main/js/app/components/nav/settings/__tests__/__snapshots__/SettingsEditionsNotif-test.tsx.snap rename to server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/EditionsStatusNotif-test.tsx.snap index 030ff105e6a..807a7c6714a 100644 --- a/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/__snapshots__/SettingsEditionsNotif-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/EditionsStatusNotif-test.tsx.snap @@ -1,18 +1,24 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`should display a ready notification 1`] = ` - marketplace.status.AUTOMATIC_READY - + + `; exports[`should display an error notification 1`] = ` - marketplace.status.AUTOMATIC_FAILURE marketplace.how_to_install - + `; exports[`should display an in progress notif 1`] = ` - marketplace.status.AUTOMATIC_IN_PROGRESS - + `; diff --git a/server/sonar-web/src/main/js/store/appState/duck.ts b/server/sonar-web/src/main/js/store/appState/duck.ts index d783242272f..abb05d0b309 100644 --- a/server/sonar-web/src/main/js/store/appState/duck.ts +++ b/server/sonar-web/src/main/js/store/appState/duck.ts @@ -19,13 +19,11 @@ */ import { Extension } from '../../app/types'; -import { EditionStatus } from '../../api/marketplace'; interface AppState { adminPages?: Extension[]; authenticationError: boolean; authorizationError: boolean; - editionStatus?: EditionStatus; organizationsEnabled: boolean; qualifiers?: string[]; } @@ -40,20 +38,11 @@ interface SetAdminPagesAction { adminPages: Extension[]; } -interface SetEditionStatusAction { - type: 'SET_EDITION_STATUS'; - editionStatus: EditionStatus; -} - interface RequireAuthorizationAction { type: 'REQUIRE_AUTHORIZATION'; } -export type Action = - | SetAppStateAction - | SetAdminPagesAction - | SetEditionStatusAction - | RequireAuthorizationAction; +export type Action = SetAppStateAction | SetAdminPagesAction | RequireAuthorizationAction; export function setAppState(appState: AppState): SetAppStateAction { return { @@ -70,10 +59,6 @@ export function requireAuthorization(): RequireAuthorizationAction { return { type: 'REQUIRE_AUTHORIZATION' }; } -export function setEditionStatus(editionStatus: EditionStatus): SetEditionStatusAction { - return { type: 'SET_EDITION_STATUS', editionStatus }; -} - const defaultValue: AppState = { authenticationError: false, authorizationError: false, @@ -89,10 +74,6 @@ export default function(state: AppState = defaultValue, action: Action): AppStat return { ...state, adminPages: action.adminPages }; } - if (action.type === 'SET_EDITION_STATUS') { - return { ...state, editionStatus: action.editionStatus }; - } - if (action.type === 'REQUIRE_AUTHORIZATION') { return { ...state, authorizationError: true }; } diff --git a/server/sonar-web/src/main/less/components/alerts.less b/server/sonar-web/src/main/less/components/alerts.less index 65694046261..ee471b96bbb 100644 --- a/server/sonar-web/src/main/less/components/alerts.less +++ b/server/sonar-web/src/main/less/components/alerts.less @@ -38,6 +38,11 @@ vertical-align: middle; } +.alert-page { + margin-bottom: 16px; + padding: 8px; +} + .modal-alert { margin: -10px -10px 16px; padding: 10px; diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index 373b3504a62..9d717313a9b 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -2094,11 +2094,11 @@ marketplace.update_to_x=Update to {0} marketplace.uninstall=Uninstall marketplace.i_accept_the=I accept the marketplace.terms_and_conditions=Terms and Conditions -marketplace.editions_unavailable=Explore our Editions: advanced feature packs brought to you by SonarSource on {url} +marketplace.editions_unavailable=Explore our Commercial Editions: advanced feature packs brought to you by SonarSource on {url} marketplace.status.AUTOMATIC_IN_PROGRESS=Updating your installation... Please wait... -marketplace.status.AUTOMATIC_READY=New installation complete. Please restart Server to benefit from it. -marketplace.status.MANUAL_IN_PROGRESS=Can't install Developer Edition because of internet access issue. Please manually install the package in your SonarQube's plugins folder. -marketplace.status.AUTOMATIC_FAILURE=Can't install Developer Edition. Please manually install the package in your SonarQube's plugins folder. +marketplace.status.AUTOMATIC_READY=Commercial Edition successfully installed. Please restart Server to activate your new features. +marketplace.status.MANUAL_IN_PROGRESS=Can't install Commercial Edition because of internet access issue. Please manually install the package in your SonarQube's plugins folder. +marketplace.status.AUTOMATIC_FAILURE=Can't install Commercial Edition. Please manually install the package in your SonarQube's plugins folder. marketplace.how_to_install=How to install it? marketplace.enter_license_for_x=Enter your license key for {0} marketplace.i_need_a_license=I need a license key -- 2.39.5