aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main
diff options
context:
space:
mode:
authorJeremy Davis <jeremy.davis@sonarsource.com>2021-03-24 16:22:53 +0100
committersonartech <sonartech@sonarsource.com>2021-04-15 20:03:44 +0000
commit83774f0672fe554b374128808b46c1a0abaca286 (patch)
tree3e6d2559418a817006162bb54fcebf70ff1d1412 /server/sonar-web/src/main
parent8b518c6bccb19f5416991dd05b0924ddd0838526 (diff)
downloadsonarqube-83774f0672fe554b374128808b46c1a0abaca286.tar.gz
sonarqube-83774f0672fe554b374128808b46c1a0abaca286.zip
SONAR-14606 Plugins require risk acknowledgment
Diffstat (limited to 'server/sonar-web/src/main')
-rw-r--r--server/sonar-web/src/main/js/app/components/indexation/IndexationNotification.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/App.tsx60
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/AppContainer.tsx16
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/__tests__/App-test.tsx28
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/__tests__/AppContainer-test.tsx59
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/App-test.tsx.snap116
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/PluginRiskConsentBox.tsx55
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/__tests__/PluginRiskConsentBox-test.tsx39
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/PluginRiskConsentBox-test.tsx.snap100
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ApplicationCreation.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/EmptyInstance.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectCreationMenu.tsx7
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/EmptyInstance-test.tsx8
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/EmptyInstance-test.tsx.snap49
-rw-r--r--server/sonar-web/src/main/js/apps/projectsManagement/App.tsx3
-rw-r--r--server/sonar-web/src/main/js/types/permissions.ts25
-rw-r--r--server/sonar-web/src/main/js/types/plugins.ts6
-rw-r--r--server/sonar-web/src/main/js/types/settings.ts3
18 files changed, 427 insertions, 157 deletions
diff --git a/server/sonar-web/src/main/js/app/components/indexation/IndexationNotification.tsx b/server/sonar-web/src/main/js/app/components/indexation/IndexationNotification.tsx
index 39a5b9b2d82..6111cb3bd0d 100644
--- a/server/sonar-web/src/main/js/app/components/indexation/IndexationNotification.tsx
+++ b/server/sonar-web/src/main/js/app/components/indexation/IndexationNotification.tsx
@@ -24,6 +24,7 @@ import withIndexationContext, {
} from '../../../components/hoc/withIndexationContext';
import { hasGlobalPermission, isLoggedIn } from '../../../helpers/users';
import { IndexationNotificationType } from '../../../types/indexation';
+import { Permissions } from '../../../types/permissions';
import './IndexationNotification.css';
import IndexationNotificationHelper from './IndexationNotificationHelper';
import IndexationNotificationRenderer from './IndexationNotificationRenderer';
@@ -44,7 +45,8 @@ export class IndexationNotification extends React.PureComponent<Props, State> {
super(props);
this.isSystemAdmin =
- isLoggedIn(this.props.currentUser) && hasGlobalPermission(this.props.currentUser, 'admin');
+ isLoggedIn(this.props.currentUser) &&
+ hasGlobalPermission(this.props.currentUser, Permissions.Admin);
}
componentDidMount() {
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 9c0f15d14cc..6c837133eb2 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/App.tsx
+++ b/server/sonar-web/src/main/js/apps/marketplace/App.tsx
@@ -20,6 +20,8 @@
import { sortBy, uniqBy } from 'lodash';
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
+import { FormattedMessage } from 'react-intl';
+import { Link } from 'react-router';
import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner';
import { translate } from 'sonar-ui-common/helpers/l10n';
import {
@@ -28,10 +30,13 @@ import {
getInstalledPluginsWithUpdates,
getPluginUpdates
} from '../../api/plugins';
+import { getValues, setSimpleSettingValue } from '../../api/settings';
import Suggestions from '../../app/components/embed-docs-modal/Suggestions';
import { Location, Router, withRouter } from '../../components/hoc/withRouter';
import { EditionKey } from '../../types/editions';
-import { PendingPluginResult, Plugin } from '../../types/plugins';
+import { PendingPluginResult, Plugin, RiskConsent } from '../../types/plugins';
+import { SettingsKey } from '../../types/settings';
+import PluginRiskConsentBox from './components/PluginRiskConsentBox';
import EditionBoxes from './EditionBoxes';
import Footer from './Footer';
import Header from './Header';
@@ -53,6 +58,7 @@ interface Props {
interface State {
loadingPlugins: boolean;
plugins: Plugin[];
+ riskConsent?: RiskConsent;
}
export class App extends React.PureComponent<Props, State> {
@@ -62,6 +68,7 @@ export class App extends React.PureComponent<Props, State> {
componentDidMount() {
this.mounted = true;
this.fetchQueryPlugins();
+ this.fetchRiskConsent();
}
componentDidUpdate(prevProps: Props) {
@@ -102,6 +109,27 @@ export class App extends React.PureComponent<Props, State> {
);
};
+ fetchRiskConsent = async () => {
+ const result = await getValues({ keys: SettingsKey.PluginRiskConsent });
+
+ if (!result || result.length < 1) {
+ return;
+ }
+
+ const [consent] = result;
+
+ this.setState({ riskConsent: consent.value as RiskConsent | undefined });
+ };
+
+ acknowledgeRisk = async () => {
+ await setSimpleSettingValue({
+ key: SettingsKey.PluginRiskConsent,
+ value: RiskConsent.Accepted
+ });
+
+ await this.fetchRiskConsent();
+ };
+
updateQuery = (newQuery: Partial<Query>) => {
const query = serializeQuery({ ...parseQuery(this.props.location.query), ...newQuery });
this.props.router.push({ pathname: this.props.location.pathname, query });
@@ -115,7 +143,7 @@ export class App extends React.PureComponent<Props, State> {
render() {
const { currentEdition, standaloneMode, pendingPlugins } = this.props;
- const { loadingPlugins, plugins } = this.state;
+ const { loadingPlugins, plugins, riskConsent } = this.state;
const query = parseQuery(this.props.location.query);
const filteredPlugins = filterPlugins(plugins, query.search);
@@ -128,9 +156,33 @@ export class App extends React.PureComponent<Props, State> {
<header className="page-header">
<h1 className="page-title">{translate('marketplace.page.plugins')}</h1>
<div className="page-description">
- {translate('marketplace.page.plugins.description')}
+ <p>{translate('marketplace.page.plugins.description')}</p>
+ {currentEdition !== EditionKey.community && (
+ <p className="spacer-top">
+ <FormattedMessage
+ id="marketplace.page.plugins.description2"
+ defaultMessage={translate('marketplace.page.plugins.description2')}
+ values={{
+ link: (
+ <Link
+ to="/documentation/instance-administration/marketplace/"
+ target="_blank">
+ {translate('marketplace.page.plugins.description2.link')}
+ </Link>
+ )
+ }}
+ />
+ </p>
+ )}
</div>
</header>
+
+ <PluginRiskConsentBox
+ acknowledgeRisk={this.acknowledgeRisk}
+ currentEdition={currentEdition}
+ riskConsent={riskConsent}
+ />
+
<Search
query={query}
updateCenterActive={this.props.updateCenterActive}
@@ -144,7 +196,7 @@ export class App extends React.PureComponent<Props, State> {
<PluginsList
pending={pendingPlugins}
plugins={filteredPlugins}
- readOnly={!standaloneMode}
+ readOnly={!standaloneMode || riskConsent !== RiskConsent.Accepted}
refreshPending={this.props.fetchPendingPlugins}
/>
<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 9a65f60dadb..e49721d363f 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/AppContainer.tsx
+++ b/server/sonar-web/src/main/js/apps/marketplace/AppContainer.tsx
@@ -43,12 +43,14 @@ const mapStateToProps = (state: Store) => {
};
};
-const WithAdminContext = (props: StateToProps & OwnProps) => (
- <AdminContext.Consumer>
- {({ fetchPendingPlugins, pendingPlugins }) => (
- <App fetchPendingPlugins={fetchPendingPlugins} pendingPlugins={pendingPlugins} {...props} />
- )}
- </AdminContext.Consumer>
-);
+function WithAdminContext(props: StateToProps & OwnProps) {
+ return (
+ <AdminContext.Consumer>
+ {({ fetchPendingPlugins, pendingPlugins }) => (
+ <App fetchPendingPlugins={fetchPendingPlugins} pendingPlugins={pendingPlugins} {...props} />
+ )}
+ </AdminContext.Consumer>
+ );
+}
export default connect(mapStateToProps)(WithAdminContext);
diff --git a/server/sonar-web/src/main/js/apps/marketplace/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/marketplace/__tests__/App-test.tsx
index be40182dbb6..e3e9e93bfd0 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/__tests__/App-test.tsx
+++ b/server/sonar-web/src/main/js/apps/marketplace/__tests__/App-test.tsx
@@ -26,7 +26,10 @@ import {
getInstalledPluginsWithUpdates,
getPluginUpdates
} from '../../../api/plugins';
+import { getValues, setSimpleSettingValue } from '../../../api/settings';
import { mockLocation, mockRouter } from '../../../helpers/testMocks';
+import { RiskConsent } from '../../../types/plugins';
+import { SettingsKey } from '../../../types/settings';
import { App } from '../App';
jest.mock('../../../api/plugins', () => {
@@ -40,6 +43,11 @@ jest.mock('../../../api/plugins', () => {
};
});
+jest.mock('../../../api/settings', () => ({
+ getValues: jest.fn().mockResolvedValue([]),
+ setSimpleSettingValue: jest.fn().mockResolvedValue(true)
+}));
+
beforeEach(jest.clearAllMocks);
it('should render correctly', async () => {
@@ -50,6 +58,25 @@ it('should render correctly', async () => {
expect(wrapper).toMatchSnapshot('loaded');
});
+it('should handle accepting the risk', async () => {
+ (getValues as jest.Mock)
+ .mockResolvedValueOnce([{ value: RiskConsent.NotAccepted }])
+ .mockResolvedValueOnce([{ value: RiskConsent.Accepted }]);
+
+ const wrapper = shallowRender();
+
+ await waitAndUpdate(wrapper);
+ expect(getValues).toBeCalledWith({ keys: SettingsKey.PluginRiskConsent });
+
+ wrapper.instance().acknowledgeRisk();
+
+ await new Promise(setImmediate);
+
+ expect(setSimpleSettingValue).toBeCalled();
+ expect(getValues).toBeCalledWith({ keys: SettingsKey.PluginRiskConsent });
+ expect(wrapper.state().riskConsent).toBe(RiskConsent.Accepted);
+});
+
it('should fetch plugin info', async () => {
const wrapper = shallowRender();
@@ -69,6 +96,7 @@ it('should fetch plugin info', async () => {
function shallowRender(props: Partial<App['props']> = {}) {
return shallow<App>(
<App
+ currentEdition={EditionKey.developer}
fetchPendingPlugins={jest.fn()}
location={mockLocation()}
pendingPlugins={{
diff --git a/server/sonar-web/src/main/js/apps/marketplace/__tests__/AppContainer-test.tsx b/server/sonar-web/src/main/js/apps/marketplace/__tests__/AppContainer-test.tsx
new file mode 100644
index 00000000000..595ba21f4dd
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/marketplace/__tests__/AppContainer-test.tsx
@@ -0,0 +1,59 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { connect } from 'react-redux';
+import { mockStore } from '../../../helpers/testMocks';
+import { getAppState, getGlobalSettingValue } from '../../../store/rootReducer';
+import { EditionKey } from '../../../types/editions';
+import '../AppContainer';
+
+jest.mock('react-redux', () => ({
+ connect: jest.fn(() => (a: any) => a)
+}));
+
+jest.mock('../../../store/rootReducer', () => {
+ return {
+ getAppState: jest.fn(),
+ getGlobalSettingValue: jest.fn()
+ };
+});
+
+describe('redux', () => {
+ it('should correctly map state and dispatch props', () => {
+ const store = mockStore();
+ const edition = EditionKey.developer;
+ const standalone = true;
+ const updateCenterActive = true;
+ (getAppState as jest.Mock).mockReturnValue({ edition, standalone });
+ (getGlobalSettingValue as jest.Mock).mockReturnValueOnce({
+ value: `${updateCenterActive}`
+ });
+
+ const [mapStateToProps] = (connect as jest.Mock).mock.calls[0];
+
+ const props = mapStateToProps(store);
+ expect(props).toEqual({
+ currentEdition: edition,
+ standaloneMode: standalone,
+ updateCenterActive
+ });
+
+ expect(getGlobalSettingValue).toHaveBeenCalledWith(store, 'sonar.updatecenter.activate');
+ });
+});
diff --git a/server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/App-test.tsx.snap b/server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/App-test.tsx.snap
deleted file mode 100644
index 09a6bc145c1..00000000000
--- a/server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/App-test.tsx.snap
+++ /dev/null
@@ -1,116 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly: loaded 1`] = `
-<div
- className="page page-limited"
- id="marketplace-page"
->
- <Suggestions
- suggestions="marketplace"
- />
- <Helmet
- defer={true}
- encodeSpecialCharacters={true}
- title="marketplace.page"
- />
- <Header />
- <EditionBoxes />
- <header
- className="page-header"
- >
- <h1
- className="page-title"
- >
- marketplace.page.plugins
- </h1>
- <div
- className="page-description"
- >
- marketplace.page.plugins.description
- </div>
- </header>
- <Search
- query={
- Object {
- "filter": "all",
- "search": "",
- }
- }
- updateCenterActive={false}
- updateQuery={[Function]}
- />
- <DeferredSpinner
- loading={false}
- >
- <PluginsList
- pending={
- Object {
- "installing": Array [],
- "removing": Array [],
- "updating": Array [],
- }
- }
- plugins={
- Array [
- Object {
- "key": "sonar-foo",
- "name": "Sonar Foo",
- },
- ]
- }
- readOnly={true}
- refreshPending={[MockFunction]}
- />
- <Footer
- total={1}
- />
- </DeferredSpinner>
-</div>
-`;
-
-exports[`should render correctly: loading 1`] = `
-<div
- className="page page-limited"
- id="marketplace-page"
->
- <Suggestions
- suggestions="marketplace"
- />
- <Helmet
- defer={true}
- encodeSpecialCharacters={true}
- title="marketplace.page"
- />
- <Header />
- <EditionBoxes />
- <header
- className="page-header"
- >
- <h1
- className="page-title"
- >
- marketplace.page.plugins
- </h1>
- <div
- className="page-description"
- >
- marketplace.page.plugins.description
- </div>
- </header>
- <Search
- query={
- Object {
- "filter": "all",
- "search": "",
- }
- }
- updateCenterActive={false}
- updateQuery={[Function]}
- />
- <DeferredSpinner
- loading={true}
- >
- marketplace.plugin_list.no_plugins.all
- </DeferredSpinner>
-</div>
-`;
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/PluginRiskConsentBox.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/PluginRiskConsentBox.tsx
new file mode 100644
index 00000000000..ed8cff315fd
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/marketplace/components/PluginRiskConsentBox.tsx
@@ -0,0 +1,55 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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 { Button } from 'sonar-ui-common/components/controls/buttons';
+import { translate } from 'sonar-ui-common/helpers/l10n';
+import { EditionKey } from '../../../types/editions';
+import { RiskConsent } from '../../../types/plugins';
+
+export interface PluginRiskConsentBoxProps {
+ acknowledgeRisk: () => void;
+ currentEdition?: EditionKey;
+ riskConsent?: RiskConsent;
+}
+
+export default function PluginRiskConsentBox(props: PluginRiskConsentBoxProps) {
+ const { currentEdition, riskConsent } = props;
+
+ if (riskConsent === RiskConsent.Accepted) {
+ return null;
+ }
+
+ return (
+ <div className="boxed-group it__plugin_risk_consent_box">
+ <h2>{translate('marketplace.risk_consent.title')}</h2>
+ <div className="boxed-group-inner">
+ <p>{translate('marketplace.risk_consent.description')}</p>
+ {currentEdition === EditionKey.community && (
+ <p className="spacer-top">{translate('marketplace.risk_consent.installation')}</p>
+ )}
+ <Button
+ className="display-block big-spacer-top button-primary"
+ onClick={props.acknowledgeRisk}>
+ {translate('marketplace.risk_consent.action')}
+ </Button>
+ </div>
+ </div>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/PluginRiskConsentBox-test.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/PluginRiskConsentBox-test.tsx
new file mode 100644
index 00000000000..66f28bddcdb
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/PluginRiskConsentBox-test.tsx
@@ -0,0 +1,39 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import { EditionKey } from '../../../../types/editions';
+import { RiskConsent } from '../../../../types/plugins';
+import PluginRiskConsentBox, { PluginRiskConsentBoxProps } from '../PluginRiskConsentBox';
+
+it.each([[undefined], [RiskConsent.Accepted], [RiskConsent.NotAccepted], [RiskConsent.Required]])(
+ 'should render correctly for risk consent %s',
+ (riskConsent?: RiskConsent) => {
+ expect(shallowRender({ riskConsent })).toMatchSnapshot();
+ }
+);
+
+it('should render correctly for community edition', () => {
+ expect(shallowRender({ currentEdition: EditionKey.community })).toMatchSnapshot();
+});
+
+function shallowRender(props: Partial<PluginRiskConsentBoxProps> = {}) {
+ return shallow(<PluginRiskConsentBox acknowledgeRisk={jest.fn()} {...props} />);
+}
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/PluginRiskConsentBox-test.tsx.snap b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/PluginRiskConsentBox-test.tsx.snap
new file mode 100644
index 00000000000..39b0f9ea863
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/PluginRiskConsentBox-test.tsx.snap
@@ -0,0 +1,100 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly for community edition 1`] = `
+<div
+ className="boxed-group it__plugin_risk_consent_box"
+>
+ <h2>
+ marketplace.risk_consent.title
+ </h2>
+ <div
+ className="boxed-group-inner"
+ >
+ <p>
+ marketplace.risk_consent.description
+ </p>
+ <p
+ className="spacer-top"
+ >
+ marketplace.risk_consent.installation
+ </p>
+ <Button
+ className="display-block big-spacer-top button-primary"
+ onClick={[MockFunction]}
+ >
+ marketplace.risk_consent.action
+ </Button>
+ </div>
+</div>
+`;
+
+exports[`should render correctly for risk consent ACCEPTED 1`] = `""`;
+
+exports[`should render correctly for risk consent NOT_ACCEPTED 1`] = `
+<div
+ className="boxed-group it__plugin_risk_consent_box"
+>
+ <h2>
+ marketplace.risk_consent.title
+ </h2>
+ <div
+ className="boxed-group-inner"
+ >
+ <p>
+ marketplace.risk_consent.description
+ </p>
+ <Button
+ className="display-block big-spacer-top button-primary"
+ onClick={[MockFunction]}
+ >
+ marketplace.risk_consent.action
+ </Button>
+ </div>
+</div>
+`;
+
+exports[`should render correctly for risk consent REQUIRED 1`] = `
+<div
+ className="boxed-group it__plugin_risk_consent_box"
+>
+ <h2>
+ marketplace.risk_consent.title
+ </h2>
+ <div
+ className="boxed-group-inner"
+ >
+ <p>
+ marketplace.risk_consent.description
+ </p>
+ <Button
+ className="display-block big-spacer-top button-primary"
+ onClick={[MockFunction]}
+ >
+ marketplace.risk_consent.action
+ </Button>
+ </div>
+</div>
+`;
+
+exports[`should render correctly for risk consent undefined 1`] = `
+<div
+ className="boxed-group it__plugin_risk_consent_box"
+>
+ <h2>
+ marketplace.risk_consent.title
+ </h2>
+ <div
+ className="boxed-group-inner"
+ >
+ <p>
+ marketplace.risk_consent.description
+ </p>
+ <Button
+ className="display-block big-spacer-top button-primary"
+ onClick={[MockFunction]}
+ >
+ marketplace.risk_consent.action
+ </Button>
+ </div>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/projects/components/ApplicationCreation.tsx b/server/sonar-web/src/main/js/apps/projects/components/ApplicationCreation.tsx
index 12f4257256b..56742d4e086 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/ApplicationCreation.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/ApplicationCreation.tsx
@@ -28,6 +28,7 @@ import { Router, withRouter } from '../../../components/hoc/withRouter';
import { getComponentAdminUrl, getComponentOverviewUrl } from '../../../helpers/urls';
import { hasGlobalPermission } from '../../../helpers/users';
import { ComponentQualifier } from '../../../types/component';
+import { Permissions } from '../../../types/permissions';
export interface ApplicationCreationProps {
appState: Pick<T.AppState, 'qualifiers'>;
@@ -43,7 +44,7 @@ export function ApplicationCreation(props: ApplicationCreationProps) {
const canCreateApplication =
appState.qualifiers.includes(ComponentQualifier.Application) &&
- hasGlobalPermission(currentUser, 'applicationcreator');
+ hasGlobalPermission(currentUser, Permissions.ApplicationCreation);
if (!canCreateApplication) {
return null;
diff --git a/server/sonar-web/src/main/js/apps/projects/components/EmptyInstance.tsx b/server/sonar-web/src/main/js/apps/projects/components/EmptyInstance.tsx
index b9c251336bc..34678061215 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/EmptyInstance.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/EmptyInstance.tsx
@@ -23,6 +23,7 @@ import { Button } from 'sonar-ui-common/components/controls/buttons';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { withRouter } from '../../../components/hoc/withRouter';
import { hasGlobalPermission, isLoggedIn } from '../../../helpers/users';
+import { Permissions } from '../../../types/permissions';
export interface EmptyInstanceProps {
currentUser: T.CurrentUser;
@@ -32,7 +33,7 @@ export interface EmptyInstanceProps {
export function EmptyInstance(props: EmptyInstanceProps) {
const { currentUser, router } = props;
const showNewProjectButton =
- isLoggedIn(currentUser) && hasGlobalPermission(currentUser, 'provisioning');
+ isLoggedIn(currentUser) && hasGlobalPermission(currentUser, Permissions.ProjectCreation);
return (
<div className="projects-empty-list">
diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCreationMenu.tsx b/server/sonar-web/src/main/js/apps/projects/components/ProjectCreationMenu.tsx
index fb282ba3f49..d2c52c76eba 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/ProjectCreationMenu.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectCreationMenu.tsx
@@ -27,6 +27,7 @@ import { withCurrentUser } from '../../../components/hoc/withCurrentUser';
import { IMPORT_COMPATIBLE_ALMS } from '../../../helpers/constants';
import { hasGlobalPermission } from '../../../helpers/users';
import { AlmKeys, AlmSettingsInstance } from '../../../types/alm-settings';
+import { Permissions } from '../../../types/permissions';
import ProjectCreationMenuItem from './ProjectCreationMenuItem';
interface Props {
@@ -38,8 +39,6 @@ interface State {
boundAlms: Array<string>;
}
-const PROJECT_CREATION_PERMISSION = 'provisioning';
-
const almSettingsValidators = {
[AlmKeys.Azure]: (settings: AlmSettingsInstance) => !!settings.url,
[AlmKeys.BitbucketServer]: (_: AlmSettingsInstance) => true,
@@ -68,7 +67,7 @@ export class ProjectCreationMenu extends React.PureComponent<Props, State> {
fetchAlmBindings = async () => {
const { currentUser } = this.props;
- const canCreateProject = hasGlobalPermission(currentUser, PROJECT_CREATION_PERMISSION);
+ const canCreateProject = hasGlobalPermission(currentUser, Permissions.ProjectCreation);
// getAlmSettings requires branchesEnabled
if (!canCreateProject) {
@@ -94,7 +93,7 @@ export class ProjectCreationMenu extends React.PureComponent<Props, State> {
const { className, currentUser } = this.props;
const { boundAlms } = this.state;
- const canCreateProject = hasGlobalPermission(currentUser, PROJECT_CREATION_PERMISSION);
+ const canCreateProject = hasGlobalPermission(currentUser, Permissions.ProjectCreation);
if (!canCreateProject) {
return null;
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/EmptyInstance-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/EmptyInstance-test.tsx
index ccd0eb4c0c6..3121467f77d 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/EmptyInstance-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/EmptyInstance-test.tsx
@@ -19,14 +19,18 @@
*/
import { shallow } from 'enzyme';
import * as React from 'react';
-import EmptyInstance from '../EmptyInstance';
+import { mockRouter } from '../../../../helpers/testMocks';
+import { EmptyInstance } from '../EmptyInstance';
it('renders correctly for SQ', () => {
- expect(shallow(<EmptyInstance currentUser={{ isLoggedIn: false }} />)).toMatchSnapshot();
+ expect(
+ shallow(<EmptyInstance currentUser={{ isLoggedIn: false }} router={mockRouter()} />)
+ ).toMatchSnapshot();
expect(
shallow(
<EmptyInstance
currentUser={{ isLoggedIn: true, permissions: { global: ['provisioning'] } }}
+ router={mockRouter()}
/>
)
).toMatchSnapshot();
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/EmptyInstance-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/EmptyInstance-test.tsx.snap
index 3c17a844515..247739f3cec 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/EmptyInstance-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/EmptyInstance-test.tsx.snap
@@ -1,26 +1,37 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders correctly for SQ 1`] = `
-<EmptyInstance
- currentUser={
- Object {
- "isLoggedIn": false,
- }
- }
-/>
+<div
+ className="projects-empty-list"
+>
+ <h3>
+ projects.no_projects.empty_instance
+ </h3>
+</div>
`;
exports[`renders correctly for SQ 2`] = `
-<EmptyInstance
- currentUser={
- Object {
- "isLoggedIn": true,
- "permissions": Object {
- "global": Array [
- "provisioning",
- ],
- },
- }
- }
-/>
+<div
+ className="projects-empty-list"
+>
+ <h3>
+ projects.no_projects.empty_instance.new_project
+ </h3>
+ <div>
+ <p
+ className="big-spacer-top"
+ >
+ projects.no_projects.empty_instance.how_to_add_projects
+ </p>
+ <p
+ className="big-spacer-top"
+ >
+ <Button
+ onClick={[Function]}
+ >
+ my_account.create_new.TRK
+ </Button>
+ </p>
+ </div>
+</div>
`;
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/App.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/App.tsx
index 7c0abc8f42e..c10ecb3d411 100644
--- a/server/sonar-web/src/main/js/apps/projectsManagement/App.tsx
+++ b/server/sonar-web/src/main/js/apps/projectsManagement/App.tsx
@@ -30,6 +30,7 @@ import { getValues } from '../../api/settings';
import Suggestions from '../../app/components/embed-docs-modal/Suggestions';
import { hasGlobalPermission } from '../../helpers/users';
import { getAppState, getCurrentUser, Store } from '../../store/rootReducer';
+import { Permissions } from '../../types/permissions';
import { SettingsKey } from '../../types/settings';
import CreateProjectForm from './CreateProjectForm';
import Header from './Header';
@@ -205,7 +206,7 @@ export class App extends React.PureComponent<Props, State> {
<Header
defaultProjectVisibility={defaultProjectVisibility}
- hasProvisionPermission={hasGlobalPermission(currentUser, 'provisioning')}
+ hasProvisionPermission={hasGlobalPermission(currentUser, Permissions.ProjectCreation)}
onChangeDefaultProjectVisibility={this.handleDefaultProjectVisibilityChange}
onProjectCreate={this.openCreateProjectForm}
/>
diff --git a/server/sonar-web/src/main/js/types/permissions.ts b/server/sonar-web/src/main/js/types/permissions.ts
new file mode 100644
index 00000000000..2da4b7d21c3
--- /dev/null
+++ b/server/sonar-web/src/main/js/types/permissions.ts
@@ -0,0 +1,25 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.
+ */
+
+export enum Permissions {
+ Admin = 'admin',
+ ProjectCreation = 'provisioning',
+ ApplicationCreation = 'applicationcreator'
+}
diff --git a/server/sonar-web/src/main/js/types/plugins.ts b/server/sonar-web/src/main/js/types/plugins.ts
index f602016a712..cd4100e56e6 100644
--- a/server/sonar-web/src/main/js/types/plugins.ts
+++ b/server/sonar-web/src/main/js/types/plugins.ts
@@ -76,6 +76,12 @@ export enum PluginType {
External = 'EXTERNAL'
}
+export enum RiskConsent {
+ Accepted = 'ACCEPTED',
+ NotAccepted = 'NOT_ACCEPTED',
+ Required = 'REQUIRED'
+}
+
export function isAvailablePlugin(plugin: Plugin): plugin is AvailablePlugin {
return (plugin as any).release !== undefined;
}
diff --git a/server/sonar-web/src/main/js/types/settings.ts b/server/sonar-web/src/main/js/types/settings.ts
index c7ca6d250ab..0cf1661724b 100644
--- a/server/sonar-web/src/main/js/types/settings.ts
+++ b/server/sonar-web/src/main/js/types/settings.ts
@@ -20,7 +20,8 @@
export const enum SettingsKey {
DaysBeforeDeletingInactiveBranchesAndPRs = 'sonar.dbcleaner.daysBeforeDeletingInactiveBranchesAndPRs',
DefaultProjectVisibility = 'projects.default.visibility',
- ServerBaseUrl = 'sonar.core.serverBaseURL'
+ ServerBaseUrl = 'sonar.core.serverBaseURL',
+ PluginRiskConsent = 'sonar.plugins.risk.consent'
}
export type Setting = SettingValue & { definition: SettingDefinition };