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<Props> {
}
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) {
<Helmet defaultTitle={defaultTitle} titleTemplate={'%s - ' + defaultTitle} />
<SettingsNav
customOrganizations={organizationsEnabled}
- editionStatus={editionStatus}
extensions={adminPages}
location={this.props.location}
/>
appState: getAppState(state)
});
-const mapDispatchToProps = { setAdminPages, setEditionStatus };
+const mapDispatchToProps = { setAdminPages };
export default connect(mapStateToProps, mapDispatchToProps)(AdminContainer as any);
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import * as React from 'react';
-import NavBarNotif from '../../../../components/nav/NavBarNotif';
-import { EditionStatus } from '../../../../api/marketplace';
-import { translate } from '../../../../helpers/l10n';
-
-interface Props {
- editionStatus: EditionStatus;
-}
-
-export default class SettingsEditionsNotif extends React.PureComponent<Props> {
- render() {
- const { editionStatus } = this.props;
-
- if (editionStatus.installationStatus === 'AUTOMATIC_IN_PROGRESS') {
- return (
- <NavBarNotif className="alert alert-info">
- <i className="spinner spacer-right text-bottom" />
- <span>{translate('marketplace.status.AUTOMATIC_IN_PROGRESS')}</span>
- </NavBarNotif>
- );
- } else if (editionStatus.installationStatus === 'AUTOMATIC_READY') {
- return (
- <NavBarNotif className="alert alert-success">
- <span>{translate('marketplace.status.AUTOMATIC_READY')}</span>
- </NavBarNotif>
- );
- } else if (
- ['MANUAL_IN_PROGRESS', 'AUTOMATIC_FAILURE'].includes(editionStatus.installationStatus)
- ) {
- return (
- <NavBarNotif className="alert alert-danger">
- {translate('marketplace.status', editionStatus.installationStatus)}
- <a className="little-spacer-left" href="https://www.sonarsource.com" target="_blank">
- {translate('marketplace.how_to_install')}
- </a>
- </NavBarNotif>
- );
- }
- return null;
- }
-}
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: {};
};
render() {
- const { customOrganizations, editionStatus, extensions } = this.props;
+ const { customOrganizations, extensions } = this.props;
const isSecurity = this.isSecurityActive();
const isProjects = this.isProjectsActive();
const isSystem = this.isSystemActive();
const hasSupportExtension = extensionsWithoutSupport.length < extensions.length;
- let notifComponent;
- if (editionStatus && editionStatus.installationStatus !== 'NONE') {
- notifComponent = <SettingsEditionsNotif editionStatus={editionStatus} />;
- }
return (
- <ContextNavBar
- id="context-navigation"
- height={notifComponent ? 95 : 65}
- notif={notifComponent}>
+ <ContextNavBar id="context-navigation" height={65}>
<h1 className="navbar-context-header">
<strong>{translate('layout.settings')}</strong>
</h1>
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import * as React from 'react';
-import { shallow } from 'enzyme';
-import SettingsEditionsNotif from '../SettingsEditionsNotif';
-
-it('should display an in progress notif', () => {
- const wrapper = shallow(
- <SettingsEditionsNotif editionStatus={{ installationStatus: 'AUTOMATIC_IN_PROGRESS' }} />
- );
- expect(wrapper).toMatchSnapshot();
-});
-
-it('should display an error notification', () => {
- const wrapper = shallow(
- <SettingsEditionsNotif editionStatus={{ installationStatus: 'AUTOMATIC_FAILURE' }} />
- );
- expect(wrapper).toMatchSnapshot();
-});
-
-it('should display a ready notification', () => {
- const wrapper = shallow(
- <SettingsEditionsNotif editionStatus={{ installationStatus: 'AUTOMATIC_READY' }} />
- );
- expect(wrapper).toMatchSnapshot();
-});
it('should work with extensions', () => {
const extensions = [{ key: 'foo', name: 'Foo' }];
const wrapper = shallow(
- <SettingsNav
- customOrganizations={false}
- editionStatus={{ installationStatus: 'NONE' }}
- extensions={extensions}
- location={{}}
- />
+ <SettingsNav customOrganizations={false} extensions={extensions} location={{}} />
);
expect(wrapper).toMatchSnapshot();
});
-
-it('should display an edition notification', () => {
- const wrapper = shallow(
- <SettingsNav
- customOrganizations={false}
- editionStatus={{ installationStatus: 'AUTOMATIC_IN_PROGRESS' }}
- extensions={[]}
- location={{}}
- />
- );
- expect({ ...wrapper.find('ContextNavBar').props(), children: [] }).toMatchSnapshot();
-});
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should display a ready notification 1`] = `
-<NavBarNotif
- className="alert alert-success"
->
- <span>
- marketplace.status.AUTOMATIC_READY
- </span>
-</NavBarNotif>
-`;
-
-exports[`should display an error notification 1`] = `
-<NavBarNotif
- className="alert alert-danger"
->
- marketplace.status.AUTOMATIC_FAILURE
- <a
- className="little-spacer-left"
- href="https://www.sonarsource.com"
- target="_blank"
- >
- marketplace.how_to_install
- </a>
-</NavBarNotif>
-`;
-
-exports[`should display an in progress notif 1`] = `
-<NavBarNotif
- className="alert alert-info"
->
- <i
- className="spinner spacer-right text-bottom"
- />
- <span>
- marketplace.status.AUTOMATIC_IN_PROGRESS
- </span>
-</NavBarNotif>
-`;
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`should display an edition notification 1`] = `
-Object {
- "children": Array [],
- "height": 95,
- "id": "context-navigation",
- "notif": <SettingsEditionsNotif
- editionStatus={
- Object {
- "installationStatus": "AUTOMATIC_IN_PROGRESS",
- }
- }
-/>,
-}
-`;
-
exports[`should work with extensions 1`] = `
<ContextNavBar
height={65}
import { sortBy, uniqBy } from 'lodash';
import Helmet from 'react-helmet';
import Header from './Header';
+import EditionsStatusNotif from './components/EditionsStatusNotif';
import EditionBoxes from './EditionBoxes';
import Footer from './Footer';
import PendingActions from './PendingActions';
Plugin,
PluginPending
} from '../../api/plugins';
-import { EditionStatus } from '../../api/marketplace';
+import { EditionStatus, getEditionStatus } from '../../api/marketplace';
import { RawQuery } from '../../helpers/query';
import { translate } from '../../helpers/l10n';
import { filterPlugins, parseQuery, Query, serializeQuery } from './utils';
export interface Props {
- editionStatus?: EditionStatus;
editionsUrl: string;
location: { pathname: string; query: RawQuery };
sonarqubeVersion: string;
}
interface State {
+ editionStatus?: EditionStatus;
loading: boolean;
pending: {
installing: PluginPending[];
componentDidMount() {
this.mounted = true;
this.fetchPendingPlugins();
+ this.fetchEditionStatus();
this.fetchQueryPlugins();
}
() => {}
);
+ fetchEditionStatus = () =>
+ getEditionStatus().then(
+ editionStatus => {
+ if (this.mounted) {
+ this.updateEditionStatus(editionStatus);
+ }
+ },
+ () => {}
+ );
+
+ updateEditionStatus = (editionStatus: EditionStatus) =>
+ this.setState({ editionStatus: editionStatus });
+
updateQuery = (newQuery: Partial<Query>) => {
const query = serializeQuery({
...parseQuery(this.props.location.query),
};
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 (
<div className="page page-limited" id="marketplace-page">
<Helmet title={translate('marketplace.page')} />
+ {editionStatus && <EditionsStatusNotif editionStatus={editionStatus} />}
<Header />
<EditionBoxes
- editionStatus={this.props.editionStatus}
+ editionStatus={editionStatus}
editionsUrl={this.props.editionsUrl}
sonarqubeVersion={this.props.sonarqubeVersion}
updateCenterActive={this.props.updateCenterActive}
+ updateEditionStatus={this.updateEditionStatus}
/>
<PendingActions refreshPending={this.fetchPendingPlugins} pending={pending} />
<Search
import './style.css';
const mapStateToProps = (state: any) => ({
- editionStatus: getAppState(state).editionStatus,
editionsUrl: (getGlobalSettingValue(state, 'sonar.editions.jsonUrl') || {}).value,
sonarqubeVersion: getAppState(state).version,
updateCenterActive: (getGlobalSettingValue(state, 'sonar.updatecenter.activate') || {}).value
editionsUrl: string;
sonarqubeVersion: string;
updateCenterActive: boolean;
+ updateEditionStatus: (editionStatus: EditionStatus) => void;
}
interface State {
)}
{installEdition && (
- <LicenseEditionForm edition={installEdition} onClose={this.handleCloseLicenseForm} />
+ <LicenseEditionForm
+ edition={installEdition}
+ onClose={this.handleCloseLicenseForm}
+ updateEditionStatus={this.props.updateEditionStatus}
+ />
)}
</div>
);
*/
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: {
state: State = { openRestart: false };
handleOpenRestart = () => this.setState({ openRestart: true });
-
hanleCloseRestart = () => this.setState({ openRestart: false });
handleRevert = () => {
editionsUrl=""
sonarqubeVersion="6.7.5"
updateCenterActive={true}
+ updateEditionStatus={jest.fn()}
{...props}
/>
);
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import RestartForm from '../../../components/common/RestartForm';
+import { EditionStatus } from '../../../api/marketplace';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+ editionStatus: EditionStatus;
+}
+
+interface State {
+ openRestart: boolean;
+}
+
+export default class EditionsStatusNotif extends React.PureComponent<Props, State> {
+ state: State = { openRestart: false };
+
+ handleOpenRestart = () => this.setState({ openRestart: true });
+ hanleCloseRestart = () => this.setState({ openRestart: false });
+
+ render() {
+ const { editionStatus } = this.props;
+ if (editionStatus.installationStatus === 'AUTOMATIC_IN_PROGRESS') {
+ return (
+ <div className="alert alert-page alert-info">
+ <i className="spinner spacer-right text-bottom" />
+ <span>{translate('marketplace.status.AUTOMATIC_IN_PROGRESS')}</span>
+ </div>
+ );
+ } else if (editionStatus.installationStatus === 'AUTOMATIC_READY') {
+ return (
+ <div className="alert alert-page alert-success">
+ <span>{translate('marketplace.status.AUTOMATIC_READY')}</span>
+ <button className="js-restart spacer-left" onClick={this.handleOpenRestart}>
+ {translate('marketplace.restart')}
+ </button>
+ {this.state.openRestart && <RestartForm onClose={this.hanleCloseRestart} />}
+ </div>
+ );
+ } else if (
+ ['MANUAL_IN_PROGRESS', 'AUTOMATIC_FAILURE'].includes(editionStatus.installationStatus)
+ ) {
+ return (
+ <div className="alert alert-page alert-danger">
+ {translate('marketplace.status', editionStatus.installationStatus)}
+ <a className="little-spacer-left" href="https://www.sonarsource.com" target="_blank">
+ {translate('marketplace.how_to_install')}
+ </a>
+ </div>
+ );
+ }
+ return null;
+ }
+}
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 {
this.setState({ loading: true });
applyLicense({ license }).then(
editionStatus => {
- getStore().dispatch(setEditionStatus(editionStatus));
+ this.props.updateEditionStatus(editionStatus);
this.props.onClose();
},
() => {
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import EditionsStatusNotif from '../EditionsStatusNotif';
+
+it('should display an in progress notif', () => {
+ const wrapper = shallow(
+ <EditionsStatusNotif editionStatus={{ installationStatus: 'AUTOMATIC_IN_PROGRESS' }} />
+ );
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('should display an error notification', () => {
+ const wrapper = shallow(
+ <EditionsStatusNotif editionStatus={{ installationStatus: 'AUTOMATIC_FAILURE' }} />
+ );
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('should display a ready notification', () => {
+ const wrapper = shallow(
+ <EditionsStatusNotif editionStatus={{ installationStatus: 'AUTOMATIC_READY' }} />
+ );
+ expect(wrapper).toMatchSnapshot();
+});
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' })
}));
const applyLicense = require('../../../../api/marketplace').applyLicense as jest.Mock<any>;
-const getStore = require('../../../../app/utils/getStore').default as jest.Mock<any>;
const DEFAULT_EDITION = {
key: 'foo',
beforeEach(() => {
applyLicense.mockClear();
- getStore().dispatch.mockClear();
});
it('should display correctly', () => {
});
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(<LicenseEditionForm edition={DEFAULT_EDITION} onClose={jest.fn()} {...props} />);
+ return shallow(
+ <LicenseEditionForm
+ edition={DEFAULT_EDITION}
+ onClose={jest.fn()}
+ updateEditionStatus={jest.fn()}
+ {...props}
+ />
+ );
}
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should display a ready notification 1`] = `
+<div
+ className="alert alert-page alert-success"
+>
+ <span>
+ marketplace.status.AUTOMATIC_READY
+ </span>
+ <button
+ className="js-restart spacer-left"
+ onClick={[Function]}
+ >
+ marketplace.restart
+ </button>
+</div>
+`;
+
+exports[`should display an error notification 1`] = `
+<div
+ className="alert alert-page alert-danger"
+>
+ marketplace.status.AUTOMATIC_FAILURE
+ <a
+ className="little-spacer-left"
+ href="https://www.sonarsource.com"
+ target="_blank"
+ >
+ marketplace.how_to_install
+ </a>
+</div>
+`;
+
+exports[`should display an in progress notif 1`] = `
+<div
+ className="alert alert-page alert-info"
+>
+ <i
+ className="spinner spacer-right text-bottom"
+ />
+ <span>
+ marketplace.status.AUTOMATIC_IN_PROGRESS
+ </span>
+</div>
+`;
*/
import { Extension } from '../../app/types';
-import { EditionStatus } from '../../api/marketplace';
interface AppState {
adminPages?: Extension[];
authenticationError: boolean;
authorizationError: boolean;
- editionStatus?: EditionStatus;
organizationsEnabled: boolean;
qualifiers?: string[];
}
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 {
return { type: 'REQUIRE_AUTHORIZATION' };
}
-export function setEditionStatus(editionStatus: EditionStatus): SetEditionStatusAction {
- return { type: 'SET_EDITION_STATUS', editionStatus };
-}
-
const defaultValue: AppState = {
authenticationError: false,
authorizationError: false,
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 };
}
vertical-align: middle;
}
+.alert-page {
+ margin-bottom: 16px;
+ padding: 8px;
+}
+
.modal-alert {
margin: -10px -10px 16px;
padding: 10px;
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