previousUpdates?: Update[];
}
+export interface PluginPendingResult {
+ installing: PluginPending[];
+ updating: PluginPending[];
+ removing: PluginPending[];
+}
+
export interface PluginAvailable extends Plugin {
release: Release;
update: Update;
return getJSON('/api/plugins/available').catch(throwGlobalError);
}
-export function getPendingPlugins(): Promise<{
- installing: PluginPending[];
- updating: PluginPending[];
- removing: PluginPending[];
-}> {
+export function getPendingPlugins(): Promise<PluginPendingResult> {
return getJSON('/api/plugins/pending').catch(throwGlobalError);
}
import {
getAppState,
getGlobalSettingValue,
- getMarketplaceEditionStatus
+ getMarketplaceEditionStatus,
+ getMarketplacePendingPlugins
} from '../../store/rootReducer';
import { getSettingsNavigation } from '../../api/nav';
import { EditionStatus, getEditionStatus } from '../../api/marketplace';
import { setAdminPages } from '../../store/appState/duck';
-import { fetchEditions, setEditionStatus } from '../../store/marketplace/actions';
+import {
+ fetchEditions,
+ setEditionStatus,
+ fetchPendingPlugins
+} from '../../store/marketplace/actions';
import { translate } from '../../helpers/l10n';
import { Extension } from '../types';
+import { PluginPendingResult } from '../../api/plugins';
interface Props {
appState: {
editionsUrl: string;
editionStatus?: EditionStatus;
fetchEditions: (url: string, version: string) => void;
+ fetchPendingPlugins: () => void;
location: {};
+ pendingPlugins: PluginPendingResult;
setAdminPages: (adminPages: Extension[]) => void;
setEditionStatus: (editionStatus: EditionStatus) => void;
}
<SettingsNav
editionStatus={this.props.editionStatus}
extensions={adminPages}
+ fetchPendingPlugins={this.props.fetchPendingPlugins}
location={this.props.location}
organizationsEnabled={organizationsEnabled}
+ pendingPlugins={this.props.pendingPlugins}
/>
{this.props.children}
</div>
const mapStateToProps = (state: any) => ({
appState: getAppState(state),
editionStatus: getMarketplaceEditionStatus(state),
- editionsUrl: (getGlobalSettingValue(state, 'sonar.editions.jsonUrl') || {}).value
+ editionsUrl: (getGlobalSettingValue(state, 'sonar.editions.jsonUrl') || {}).value,
+ pendingPlugins: getMarketplacePendingPlugins(state)
});
-const mapDispatchToProps = { setAdminPages, setEditionStatus, fetchEditions };
+const mapDispatchToProps = { setAdminPages, setEditionStatus, fetchEditions, fetchPendingPlugins };
export default connect(mapStateToProps, mapDispatchToProps)(AdminContainer as any);
--- /dev/null
+/*
+ * 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 RestartForm from '../../../../components/common/RestartForm';
+import { cancelPendingPlugins, PluginPendingResult } from '../../../../api/plugins';
+import { Button } from '../../../../components/ui/buttons';
+import { translate } from '../../../../helpers/l10n';
+import NavBarNotif from '../../../../components/nav/NavBarNotif';
+
+interface Props {
+ pending: PluginPendingResult;
+ refreshPending: () => void;
+}
+
+interface State {
+ openRestart: boolean;
+}
+
+export default class PendingPluginsActionNotif extends React.PureComponent<Props, State> {
+ state: State = { openRestart: false };
+
+ handleOpenRestart = () => {
+ this.setState({ openRestart: true });
+ };
+
+ hanleCloseRestart = () => {
+ this.setState({ openRestart: false });
+ };
+
+ handleRevert = () => {
+ cancelPendingPlugins().then(this.props.refreshPending, () => {});
+ };
+
+ render() {
+ const { installing, updating, removing } = this.props.pending;
+ const hasPendingActions = installing.length || updating.length || removing.length;
+ if (!hasPendingActions) {
+ return null;
+ }
+
+ return (
+ <NavBarNotif className="alert alert-info">
+ <span className="little-spacer-right">
+ {translate('marketplace.sonarqube_needs_to_be_restarted_to')}
+ </span>
+ {[
+ { length: installing.length, msg: 'marketplace.install_x_plugins' },
+ { length: updating.length, msg: 'marketplace.update_x_plugins' },
+ { length: removing.length, msg: 'marketplace.uninstall_x_plugins' }
+ ]
+ .filter(({ length }) => length > 0)
+ .map(({ length, msg }, idx) => (
+ <span key={msg}>
+ {idx > 0 && '; '}
+ <FormattedMessage
+ defaultMessage={translate(msg)}
+ id={msg}
+ values={{ nb: <strong>{length}</strong> }}
+ />
+ </span>
+ ))}
+ <Button className="spacer-left js-restart" onClick={this.handleOpenRestart}>
+ {translate('marketplace.restart')}
+ </Button>
+ <Button className="spacer-left js-cancel-all button-red" onClick={this.handleRevert}>
+ {translate('marketplace.revert')}
+ </Button>
+ {this.state.openRestart && <RestartForm onClose={this.hanleCloseRestart} />}
+ </NavBarNotif>
+ );
+ }
+}
const { editionStatus } = this.props;
return (
<NavBarNotif className="alert alert-info">
- <i className="spinner spacer-right text-bottom" />
+ <i className="spinner spacer-right" />
<span>
{edition
? translateWithParameters(
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 NavBarTabs from '../../../../components/nav/NavBarTabs';
import { Extension } from '../../../types';
import { translate } from '../../../../helpers/l10n';
import Dropdown from '../../../../components/controls/Dropdown';
+import { PluginPendingResult } from '../../../../api/plugins';
interface Props {
editionStatus?: EditionStatus;
extensions: Extension[];
+ fetchPendingPlugins: () => void;
location: {};
organizationsEnabled: boolean;
+ pendingPlugins: PluginPendingResult;
}
export default class SettingsNav extends React.PureComponent<Props> {
}
render() {
- const { editionStatus, extensions } = this.props;
+ const { editionStatus, extensions, pendingPlugins } = this.props;
const hasSupportExtension = extensions.find(extension => extension.key === 'license/support');
- let notifComponent;
+ const notifComponents = [];
if (
editionStatus &&
(editionStatus.installError || editionStatus.installationStatus !== 'NONE')
) {
- notifComponent = <SettingsEditionsNotifContainer editionStatus={editionStatus} />;
+ notifComponents.push(<SettingsEditionsNotifContainer editionStatus={editionStatus} />);
}
+ if (
+ pendingPlugins.installing.length > 0 ||
+ pendingPlugins.removing.length > 0 ||
+ pendingPlugins.updating.length > 0
+ ) {
+ notifComponents.push(
+ <PendingPluginsActionNotif
+ pending={pendingPlugins}
+ refreshPending={this.props.fetchPendingPlugins}
+ />
+ );
+ }
+
+ const notifContainer =
+ notifComponents.length > 0 ? (
+ <div className="alert-container">
+ {notifComponents.map((element, index) => <div key={index}>{element}</div>)}
+ </div>
+ ) : null;
+
return (
<ContextNavBar
- height={notifComponent ? theme.contextNavHeightRaw + 20 : theme.contextNavHeightRaw}
+ height={theme.contextNavHeightRaw + 38 * notifComponents.length}
id="context-navigation"
- notif={notifComponent}>
+ notif={notifContainer}>
<header className="navbar-context-header">
<h1>{translate('layout.settings')}</h1>
</header>
--- /dev/null
+/*
+ * 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 PendingPluginsActionNotif from '../PendingPluginsActionNotif';
+
+jest.mock('../../../../../api/plugins', () => ({
+ cancelPendingPlugins: jest.fn(() => Promise.resolve())
+}));
+
+const cancelPendingPlugins = require('../../../../../api/plugins')
+ .cancelPendingPlugins as jest.Mock<any>;
+
+beforeEach(() => {
+ cancelPendingPlugins.mockClear();
+});
+
+it('should display pending actions', () => {
+ expect(getWrapper()).toMatchSnapshot();
+});
+
+it('should not display anything', () => {
+ expect(getWrapper({ pending: { installing: [], updating: [], removing: [] } }).type()).toBeNull();
+});
+
+it('should open the restart form', () => {
+ const wrapper = getWrapper();
+ click(wrapper.find('.js-restart'));
+ expect(wrapper.find('RestartForm').exists()).toBeTruthy();
+});
+
+it('should cancel all pending and refresh them', async () => {
+ const refreshPending = jest.fn();
+ const wrapper = getWrapper({ refreshPending });
+ click(wrapper.find('.js-cancel-all'));
+ expect(cancelPendingPlugins).toHaveBeenCalled();
+ await new Promise(setImmediate);
+
+ expect(refreshPending).toHaveBeenCalled();
+});
+
+function getWrapper(props = {}) {
+ return shallow(
+ <PendingPluginsActionNotif
+ pending={{
+ installing: [
+ {
+ key: 'foo',
+ name: 'Foo',
+ description: 'foo description',
+ version: 'fooversion',
+ implementationBuild: 'foobuild'
+ },
+ {
+ key: 'bar',
+ name: 'Bar',
+ description: 'bar description',
+ version: 'barversion',
+ implementationBuild: 'barbuild'
+ }
+ ],
+ updating: [],
+ removing: [
+ {
+ key: 'baz',
+ name: 'Baz',
+ description: 'baz description',
+ version: 'bazversion',
+ implementationBuild: 'bazbuild'
+ }
+ ]
+ }}
+ refreshPending={() => {}}
+ {...props}
+ />
+ );
+}
it('should work with extensions', () => {
const extensions = [{ key: 'foo', name: 'Foo' }];
const wrapper = shallow(
- <SettingsNav extensions={extensions} location={{}} organizationsEnabled={false} />
+ <SettingsNav
+ extensions={extensions}
+ fetchPendingPlugins={() => {}}
+ location={{}}
+ organizationsEnabled={false}
+ pendingPlugins={{ installing: [], removing: [], updating: [] }}
+ />
);
expect(wrapper).toMatchSnapshot();
expect(wrapper.find('Dropdown').map(x => x.dive())).toMatchSnapshot();
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should display pending actions 1`] = `
+<NavBarNotif
+ className="alert alert-info"
+>
+ <span
+ className="little-spacer-right"
+ >
+ marketplace.sonarqube_needs_to_be_restarted_to
+ </span>
+ <span
+ key="marketplace.install_x_plugins"
+ >
+ <FormattedMessage
+ defaultMessage="marketplace.install_x_plugins"
+ id="marketplace.install_x_plugins"
+ values={
+ Object {
+ "nb": <strong>
+ 2
+ </strong>,
+ }
+ }
+ />
+ </span>
+ <span
+ key="marketplace.uninstall_x_plugins"
+ >
+ ;
+ <FormattedMessage
+ defaultMessage="marketplace.uninstall_x_plugins"
+ id="marketplace.uninstall_x_plugins"
+ values={
+ Object {
+ "nb": <strong>
+ 1
+ </strong>,
+ }
+ }
+ />
+ </span>
+ <Button
+ className="spacer-left js-restart"
+ onClick={[Function]}
+ >
+ marketplace.restart
+ </Button>
+ <Button
+ className="spacer-left js-cancel-all button-red"
+ onClick={[Function]}
+ >
+ marketplace.revert
+ </Button>
+</NavBarNotif>
+`;
className="alert alert-info"
>
<i
- className="spinner spacer-right text-bottom"
+ className="spinner spacer-right"
/>
<span>
marketplace.edition_status.AUTOMATIC_IN_PROGRESS
<ContextNavBar
height={72}
id="context-navigation"
+ notif={null}
>
<header
className="navbar-context-header"
* 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;
import Header from './Header';
import EditionBoxes from './EditionBoxes';
import Footer from './Footer';
-import PendingActions from './PendingActions';
import PluginsList from './PluginsList';
import Search from './Search';
import { filterPlugins, parseQuery, Query, serializeQuery } from './utils';
import {
getAvailablePlugins,
getInstalledPluginsWithUpdates,
- getPendingPlugins,
getPluginUpdates,
Plugin,
- PluginPending,
+ PluginPendingResult,
getInstalledPlugins
} from '../../api/plugins';
import { Edition, EditionStatus } from '../../api/marketplace';
editions?: Edition[];
editionsReadOnly: boolean;
editionStatus?: EditionStatus;
+ fetchPendingPlugins: () => void;
loadingEditions: boolean;
location: { pathname: string; query: RawQuery };
+ pendingPlugins: PluginPendingResult;
standaloneMode: boolean;
- updateCenterActive: boolean;
setEditionStatus: (editionStatus: EditionStatus) => void;
+ updateCenterActive: boolean;
}
interface State {
loadingPlugins: boolean;
- pending: {
- installing: PluginPending[];
- updating: PluginPending[];
- removing: PluginPending[];
- };
plugins: Plugin[];
}
super(props);
this.state = {
loadingPlugins: true,
- pending: {
- installing: [],
- updating: [],
- removing: []
- },
plugins: []
};
}
componentDidMount() {
this.mounted = true;
- this.fetchPendingPlugins();
+ this.props.fetchPendingPlugins();
this.fetchQueryPlugins();
}
);
};
- fetchPendingPlugins = () =>
- getPendingPlugins().then(
- pending => {
- if (this.mounted) {
- this.setState({ pending });
- }
- },
- () => {}
- );
-
updateQuery = (newQuery: Partial<Query>) => {
const query = serializeQuery({ ...parseQuery(this.props.location.query), ...newQuery });
this.context.router.push({ pathname: this.props.location.pathname, query });
};
render() {
- const { editions, editionStatus, standaloneMode } = this.props;
- const { loadingPlugins, plugins, pending } = this.state;
+ const { editions, editionStatus, 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;
return (
<div className="page page-limited" id="marketplace-page">
<Helmet title={translate('marketplace.page')} />
- <div className="page-notifs">
- {standaloneMode && (
- <PendingActions pending={pending} refreshPending={this.fetchPendingPlugins} />
- )}
- </div>
<Header />
<EditionBoxes
canInstall={standaloneMode && !this.props.editionsReadOnly}
{loadingPlugins && <i className="spinner" />}
{!loadingPlugins && (
<PluginsList
- pending={pending}
+ pending={pendingPlugins}
plugins={filteredPlugins}
readOnly={!standaloneMode}
- refreshPending={this.fetchPendingPlugins}
+ refreshPending={this.props.fetchPendingPlugins}
/>
)}
{!loadingPlugins && <Footer total={filteredPlugins.length} />}
getGlobalSettingValue,
getMarketplaceState,
getMarketplaceEditions,
- getMarketplaceEditionStatus
+ getMarketplaceEditionStatus,
+ getMarketplacePendingPlugins
} from '../../store/rootReducer';
import { Edition, EditionStatus } from '../../api/marketplace';
-import { setEditionStatus } from '../../store/marketplace/actions';
+import { setEditionStatus, fetchPendingPlugins } from '../../store/marketplace/actions';
import { RawQuery } from '../../helpers/query';
+import { PluginPendingResult } from '../../api/plugins';
interface OwnProps {
location: { pathname: string; query: RawQuery };
editionsReadOnly: boolean;
editionStatus?: EditionStatus;
loadingEditions: boolean;
+ pendingPlugins: PluginPendingResult;
standaloneMode: boolean;
updateCenterActive: boolean;
}
interface DispatchToProps {
setEditionStatus: (editionStatus: EditionStatus) => void;
+ fetchPendingPlugins: () => void;
}
const mapStateToProps = (state: any) => ({
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 mapDispatchToProps = { setEditionStatus };
+const mapDispatchToProps = { setEditionStatus, fetchPendingPlugins };
export default connect<StateToProps, DispatchToProps, OwnProps>(
mapStateToProps,
+++ /dev/null
-/*
- * 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 RestartForm from '../../components/common/RestartForm';
-import { cancelPendingPlugins, PluginPending } from '../../api/plugins';
-import { Button } from '../../components/ui/buttons';
-import { translate } from '../../helpers/l10n';
-
-interface Props {
- pending: {
- installing: PluginPending[];
- updating: PluginPending[];
- removing: PluginPending[];
- };
- refreshPending: () => void;
-}
-
-interface State {
- openRestart: boolean;
-}
-
-export default class PendingActions extends React.PureComponent<Props, State> {
- state: State = { openRestart: false };
-
- handleOpenRestart = () => {
- this.setState({ openRestart: true });
- };
-
- hanleCloseRestart = () => {
- this.setState({ openRestart: false });
- };
-
- handleRevert = () => {
- cancelPendingPlugins().then(this.props.refreshPending, () => {});
- };
-
- render() {
- const { installing, updating, removing } = this.props.pending;
- const hasPendingActions = installing.length || updating.length || removing.length;
- if (!hasPendingActions) {
- return null;
- }
-
- return (
- <div className="js-pending alert alert-warning">
- <div className="display-inline-block">
- <p>{translate('marketplace.sonarqube_needs_to_be_restarted_to')}</p>
- <ul className="list-styled spacer-top">
- {installing.length > 0 && (
- <li>
- <FormattedMessage
- defaultMessage={translate('marketplace.install_x_plugins')}
- id="marketplace.install_x_plugins"
- values={{ nb: <strong>{installing.length}</strong> }}
- />
- </li>
- )}
- {updating.length > 0 && (
- <li>
- <FormattedMessage
- defaultMessage={translate('marketplace.update_x_plugins')}
- id="marketplace.update_x_plugins"
- values={{ nb: <strong>{updating.length}</strong> }}
- />
- </li>
- )}
- {removing.length > 0 && (
- <li>
- <FormattedMessage
- defaultMessage={translate('marketplace.uninstall_x_plugins')}
- id="marketplace.uninstall_x_plugins"
- values={{ nb: <strong>{removing.length}</strong> }}
- />
- </li>
- )}
- </ul>
- </div>
- <div className="pull-right">
- <Button className="js-restart little-spacer-right" onClick={this.handleOpenRestart}>
- {translate('marketplace.restart')}
- </Button>
- <Button className="js-cancel-all button-red" onClick={this.handleRevert}>
- {translate('marketplace.revert')}
- </Button>
- </div>
- {this.state.openRestart && <RestartForm onClose={this.hanleCloseRestart} />}
- </div>
- );
- }
-}
+++ /dev/null
-/*
- * 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 PendingActions from '../PendingActions';
-
-jest.mock('../../../api/plugins', () => ({
- cancelPendingPlugins: jest.fn(() => Promise.resolve())
-}));
-
-const cancelPendingPlugins = require('../../../api/plugins').cancelPendingPlugins as jest.Mock<any>;
-
-beforeEach(() => {
- cancelPendingPlugins.mockClear();
-});
-
-it('should display pending actions', () => {
- expect(getWrapper()).toMatchSnapshot();
-});
-
-it('should not display anything', () => {
- expect(getWrapper({ pending: { installing: [], updating: [], removing: [] } }).type()).toBeNull();
-});
-
-it('should open the restart form', () => {
- const wrapper = getWrapper();
- click(wrapper.find('.js-restart'));
- expect(wrapper.find('RestartForm').exists()).toBeTruthy();
-});
-
-it('should cancel all pending and refresh them', async () => {
- const refreshPending = jest.fn();
- const wrapper = getWrapper({ refreshPending });
- click(wrapper.find('.js-cancel-all'));
- expect(cancelPendingPlugins).toHaveBeenCalled();
- await new Promise(setImmediate);
-
- expect(refreshPending).toHaveBeenCalled();
-});
-
-function getWrapper(props = {}) {
- return shallow(
- <PendingActions
- pending={{
- installing: [
- {
- key: 'foo',
- name: 'Foo',
- description: 'foo description',
- version: 'fooversion',
- implementationBuild: 'foobuild'
- },
- {
- key: 'bar',
- name: 'Bar',
- description: 'bar description',
- version: 'barversion',
- implementationBuild: 'barbuild'
- }
- ],
- updating: [],
- removing: [
- {
- key: 'baz',
- name: 'Baz',
- description: 'baz description',
- version: 'bazversion',
- implementationBuild: 'bazbuild'
- }
- ]
- }}
- refreshPending={() => {}}
- {...props}
- />
- );
-}
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should display pending actions 1`] = `
-<div
- className="js-pending alert alert-warning"
->
- <div
- className="display-inline-block"
- >
- <p>
- marketplace.sonarqube_needs_to_be_restarted_to
- </p>
- <ul
- className="list-styled spacer-top"
- >
- <li>
- <FormattedMessage
- defaultMessage="marketplace.install_x_plugins"
- id="marketplace.install_x_plugins"
- values={
- Object {
- "nb": <strong>
- 2
- </strong>,
- }
- }
- />
- </li>
- <li>
- <FormattedMessage
- defaultMessage="marketplace.uninstall_x_plugins"
- id="marketplace.uninstall_x_plugins"
- values={
- Object {
- "nb": <strong>
- 1
- </strong>,
- }
- }
- />
- </li>
- </ul>
- </div>
- <div
- className="pull-right"
- >
- <Button
- className="js-restart little-spacer-right"
- onClick={[Function]}
- >
- marketplace.restart
- </Button>
- <Button
- className="js-cancel-all button-red"
- onClick={[Function]}
- >
- marketplace.revert
- </Button>
- </div>
-</div>
-`;
return (
<div className={classNames('navbar-notif', props.className)}>
<div className="navbar-limited clearfix">
- <div className={classNames({ 'navbar-notif-cancelable': !!props.onCancel })}>
+ <div
+ className={classNames('display-flex-center', {
+ 'navbar-notif-cancelable': !!props.onCancel
+ })}>
{props.children}
{props.onCancel && <DeleteButton className="button-small" onClick={props.onCancel} />}
</div>
import { Dispatch } from 'react-redux';
import { getEditionsForVersion, getEditionsForLastVersion } from './utils';
import { Edition, EditionStatus, getEditionStatus, getEditionsList } 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[];
status: EditionStatus;
}
-export type Action = LoadEditionsAction | SetEditionsAction | SetEditionStatusAction;
+export type Action =
+ | LoadEditionsAction
+ | SetEditionsAction
+ | SetEditionStatusAction
+ | SetPendingPluginsAction;
export function loadEditions(loading = true): LoadEditionsAction {
return { type: 'LOAD_EDITIONS', loading };
}
+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 fetchPendingPlugins = () => (dispatch: Dispatch<Action>) => {
+ getPendingPlugins().then(
+ pending => {
+ dispatch(setPendingPlugins(pending));
+ },
+ () => {}
+ );
+};
+
export const fetchEditions = (url: string, version: string) => (dispatch: Dispatch<Action>) => {
dispatch(loadEditions(true));
getEditionsList(url).then(
*/
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;
+ pending: PluginPendingResult;
}
const defaultState: State = {
loading: true,
- readOnly: false
+ readOnly: false,
+ pending: { installing: [], removing: [], updating: [] }
};
export default function(state: State = defaultState, action: Action): State {
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]
export const getEditions = (state: State) => state.editions;
export const getEditionStatus = (state: State) => state.status;
+export const getPendingPlugins = (state: State) => state.pending;
export const getMarketplaceEditionStatus = state =>
fromMarketplace.getEditionStatus(state.marketplace);
+export const getMarketplacePendingPlugins = state =>
+ fromMarketplace.getPendingPlugins(state.marketplace);
+
export const getMetrics = state => fromMetrics.getMetrics(state.metrics);
export const getMetricByKey = (state, key) => fromMetrics.getMetricByKey(state.metrics, key);