summaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorPascal Mugnier <pascal.mugnier@sonarsource.com>2018-04-10 13:47:42 +0200
committerSonarTech <sonartech@sonarsource.com>2018-04-10 20:20:54 +0200
commit50766c5a770d31733a4f5f0f29a3cbf663473d77 (patch)
tree6aa335d1afd44ba45e43e6ccc254f192ad69d4d0 /server
parentf239ffffd3818fec2aa2d74b12875355324df73e (diff)
downloadsonarqube-50766c5a770d31733a4f5f0f29a3cbf663473d77.tar.gz
sonarqube-50766c5a770d31733a4f5f0f29a3cbf663473d77.zip
SONAR-10133 Full-width banner to prompt restart after plugin change (#102)
Diffstat (limited to 'server')
-rw-r--r--server/sonar-web/src/main/js/api/plugins.ts12
-rw-r--r--server/sonar-web/src/main/js/app/components/AdminContainer.tsx19
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/settings/PendingPluginsActionNotif.tsx90
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/settings/SettingsEditionsNotif.tsx2
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.tsx34
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/settings/__tests__/PendingPluginsActionNotif-test.tsx (renamed from server/sonar-web/src/main/js/apps/marketplace/__tests__/PendingActions-test.tsx)11
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/settings/__tests__/SettingsNav-test.tsx8
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/settings/__tests__/__snapshots__/PendingPluginsActionNotif-test.tsx.snap56
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/settings/__tests__/__snapshots__/SettingsEditionsNotif-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/settings/__tests__/__snapshots__/SettingsNav-test.tsx.snap1
-rw-r--r--server/sonar-web/src/main/js/app/styles/components/alerts.css6
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/App.tsx43
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/AppContainer.tsx11
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/PendingActions.tsx108
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/PendingActions-test.tsx.snap61
-rw-r--r--server/sonar-web/src/main/js/components/nav/NavBarNotif.tsx5
-rw-r--r--server/sonar-web/src/main/js/store/marketplace/actions.ts25
-rw-r--r--server/sonar-web/src/main/js/store/marketplace/reducer.ts12
-rw-r--r--server/sonar-web/src/main/js/store/rootReducer.js3
19 files changed, 278 insertions, 231 deletions
diff --git a/server/sonar-web/src/main/js/api/plugins.ts b/server/sonar-web/src/main/js/api/plugins.ts
index 8d98b04de20..faf104eba0b 100644
--- a/server/sonar-web/src/main/js/api/plugins.ts
+++ b/server/sonar-web/src/main/js/api/plugins.ts
@@ -49,6 +49,12 @@ export interface Update {
previousUpdates?: Update[];
}
+export interface PluginPendingResult {
+ installing: PluginPending[];
+ updating: PluginPending[];
+ removing: PluginPending[];
+}
+
export interface PluginAvailable extends Plugin {
release: Release;
update: Update;
@@ -74,11 +80,7 @@ export function getAvailablePlugins(): Promise<{
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);
}
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 3f797a3419f..173efed4d1d 100644
--- a/server/sonar-web/src/main/js/app/components/AdminContainer.tsx
+++ b/server/sonar-web/src/main/js/app/components/AdminContainer.tsx
@@ -25,14 +25,20 @@ import SettingsNav from './nav/settings/SettingsNav';
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: {
@@ -43,7 +49,9 @@ interface Props {
editionsUrl: string;
editionStatus?: EditionStatus;
fetchEditions: (url: string, version: string) => void;
+ fetchPendingPlugins: () => void;
location: {};
+ pendingPlugins: PluginPendingResult;
setAdminPages: (adminPages: Extension[]) => void;
setEditionStatus: (editionStatus: EditionStatus) => void;
}
@@ -88,8 +96,10 @@ class AdminContainer extends React.PureComponent<Props> {
<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>
@@ -100,9 +110,10 @@ class AdminContainer extends React.PureComponent<Props> {
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);
diff --git a/server/sonar-web/src/main/js/app/components/nav/settings/PendingPluginsActionNotif.tsx b/server/sonar-web/src/main/js/app/components/nav/settings/PendingPluginsActionNotif.tsx
new file mode 100644
index 00000000000..e1e3906fb0a
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/components/nav/settings/PendingPluginsActionNotif.tsx
@@ -0,0 +1,90 @@
+/*
+ * 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>
+ );
+ }
+}
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
index 3c8850a95ef..c4f1af5e32b 100644
--- 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
@@ -52,7 +52,7 @@ export default class SettingsEditionsNotif extends React.PureComponent<Props, St
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(
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 3064c54212e..8379d1822bb 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,6 +21,7 @@ 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 NavBarTabs from '../../../../components/nav/NavBarTabs';
@@ -28,12 +29,15 @@ 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';
interface Props {
editionStatus?: EditionStatus;
extensions: Extension[];
+ fetchPendingPlugins: () => void;
location: {};
organizationsEnabled: boolean;
+ pendingPlugins: PluginPendingResult;
}
export default class SettingsNav extends React.PureComponent<Props> {
@@ -216,22 +220,42 @@ 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>
diff --git a/server/sonar-web/src/main/js/apps/marketplace/__tests__/PendingActions-test.tsx b/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/PendingPluginsActionNotif-test.tsx
index ff023270fac..b531164eeac 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/__tests__/PendingActions-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/PendingPluginsActionNotif-test.tsx
@@ -20,14 +20,15 @@
/* eslint-disable import/order */
import * as React from 'react';
import { shallow } from 'enzyme';
-import { click } from '../../../helpers/testUtils';
-import PendingActions from '../PendingActions';
+import { click } from '../../../../../helpers/testUtils';
+import PendingPluginsActionNotif from '../PendingPluginsActionNotif';
-jest.mock('../../../api/plugins', () => ({
+jest.mock('../../../../../api/plugins', () => ({
cancelPendingPlugins: jest.fn(() => Promise.resolve())
}));
-const cancelPendingPlugins = require('../../../api/plugins').cancelPendingPlugins as jest.Mock<any>;
+const cancelPendingPlugins = require('../../../../../api/plugins')
+ .cancelPendingPlugins as jest.Mock<any>;
beforeEach(() => {
cancelPendingPlugins.mockClear();
@@ -59,7 +60,7 @@ it('should cancel all pending and refresh them', async () => {
function getWrapper(props = {}) {
return shallow(
- <PendingActions
+ <PendingPluginsActionNotif
pending={{
installing: [
{
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 e589b879494..ae3f5e900af 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,7 +24,13 @@ import SettingsNav from '../SettingsNav';
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();
diff --git a/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/__snapshots__/PendingPluginsActionNotif-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/__snapshots__/PendingPluginsActionNotif-test.tsx.snap
new file mode 100644
index 00000000000..f7c1f279fa4
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/__snapshots__/PendingPluginsActionNotif-test.tsx.snap
@@ -0,0 +1,56 @@
+// 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>
+`;
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
index f843a2d29ba..051f00fce94 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/app/components/nav/settings/__tests__/__snapshots__/SettingsEditionsNotif-test.tsx.snap
@@ -44,7 +44,7 @@ exports[`should display an in progress notif 1`] = `
className="alert alert-info"
>
<i
- className="spinner spacer-right text-bottom"
+ className="spinner spacer-right"
/>
<span>
marketplace.edition_status.AUTOMATIC_IN_PROGRESS
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 439e362124e..d7495bbbfc3 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
@@ -4,6 +4,7 @@ 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 b405e80aa34..aa8e515e341 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,6 +17,12 @@
* 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/apps/marketplace/App.tsx b/server/sonar-web/src/main/js/apps/marketplace/App.tsx
index 2dcf58ab2e0..9446bfe471c 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/App.tsx
+++ b/server/sonar-web/src/main/js/apps/marketplace/App.tsx
@@ -24,17 +24,15 @@ import Helmet from 'react-helmet';
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';
@@ -46,20 +44,17 @@ export interface Props {
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[];
}
@@ -74,18 +69,13 @@ export default class App extends React.PureComponent<Props, State> {
super(props);
this.state = {
loadingPlugins: true,
- pending: {
- installing: [],
- updating: [],
- removing: []
- },
plugins: []
};
}
componentDidMount() {
this.mounted = true;
- this.fetchPendingPlugins();
+ this.props.fetchPendingPlugins();
this.fetchQueryPlugins();
}
@@ -127,16 +117,6 @@ export default class App extends React.PureComponent<Props, State> {
);
};
- 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 });
@@ -149,19 +129,14 @@ export default class App extends React.PureComponent<Props, State> {
};
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}
@@ -180,10 +155,10 @@ export default class App extends React.PureComponent<Props, State> {
{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} />}
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 4f7676767ba..73611432c0b 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/AppContainer.tsx
+++ b/server/sonar-web/src/main/js/apps/marketplace/AppContainer.tsx
@@ -24,11 +24,13 @@ import {
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 };
@@ -39,12 +41,14 @@ interface StateToProps {
editionsReadOnly: boolean;
editionStatus?: EditionStatus;
loadingEditions: boolean;
+ pendingPlugins: PluginPendingResult;
standaloneMode: boolean;
updateCenterActive: boolean;
}
interface DispatchToProps {
setEditionStatus: (editionStatus: EditionStatus) => void;
+ fetchPendingPlugins: () => void;
}
const mapStateToProps = (state: any) => ({
@@ -52,12 +56,13 @@ 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,
diff --git a/server/sonar-web/src/main/js/apps/marketplace/PendingActions.tsx b/server/sonar-web/src/main/js/apps/marketplace/PendingActions.tsx
deleted file mode 100644
index d392d5efcc3..00000000000
--- a/server/sonar-web/src/main/js/apps/marketplace/PendingActions.tsx
+++ /dev/null
@@ -1,108 +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 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>
- );
- }
-}
diff --git a/server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/PendingActions-test.tsx.snap b/server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/PendingActions-test.tsx.snap
deleted file mode 100644
index c8688596746..00000000000
--- a/server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/PendingActions-test.tsx.snap
+++ /dev/null
@@ -1,61 +0,0 @@
-// 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>
-`;
diff --git a/server/sonar-web/src/main/js/components/nav/NavBarNotif.tsx b/server/sonar-web/src/main/js/components/nav/NavBarNotif.tsx
index 63f5695de00..835b51831e1 100644
--- a/server/sonar-web/src/main/js/components/nav/NavBarNotif.tsx
+++ b/server/sonar-web/src/main/js/components/nav/NavBarNotif.tsx
@@ -34,7 +34,10 @@ export default function NavBarNotif(props: Props) {
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>
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 dfc5f8da834..894c74f2398 100644
--- a/server/sonar-web/src/main/js/store/marketplace/actions.ts
+++ b/server/sonar-web/src/main/js/store/marketplace/actions.ts
@@ -20,12 +20,18 @@
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[];
@@ -37,12 +43,20 @@ interface SetEditionStatusAction {
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 };
}
@@ -62,6 +76,15 @@ export const setEditionStatus = (status: EditionStatus) => (dispatch: Dispatch<A
}
};
+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(
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 2afdef1e678..11c5e28d7f5 100644
--- a/server/sonar-web/src/main/js/store/marketplace/reducer.ts
+++ b/server/sonar-web/src/main/js/store/marketplace/reducer.ts
@@ -19,17 +19,20 @@
*/
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 {
@@ -39,6 +42,12 @@ 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]
@@ -53,3 +62,4 @@ export default function(state: State = defaultState, action: Action): State {
export const getEditions = (state: State) => state.editions;
export const getEditionStatus = (state: State) => state.status;
+export const getPendingPlugins = (state: State) => state.pending;
diff --git a/server/sonar-web/src/main/js/store/rootReducer.js b/server/sonar-web/src/main/js/store/rootReducer.js
index 1a8ca7d54e6..51ea1ad9971 100644
--- a/server/sonar-web/src/main/js/store/rootReducer.js
+++ b/server/sonar-web/src/main/js/store/rootReducer.js
@@ -82,6 +82,9 @@ export const getMarketplaceEditions = state => fromMarketplace.getEditions(state
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);