} 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';
super(props);
this.isSystemAdmin =
- isLoggedIn(this.props.currentUser) && hasGlobalPermission(this.props.currentUser, 'admin');
+ isLoggedIn(this.props.currentUser) &&
+ hasGlobalPermission(this.props.currentUser, Permissions.Admin);
}
componentDidMount() {
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 {
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';
interface State {
loadingPlugins: boolean;
plugins: Plugin[];
+ riskConsent?: RiskConsent;
}
export class App extends React.PureComponent<Props, State> {
componentDidMount() {
this.mounted = true;
this.fetchQueryPlugins();
+ this.fetchRiskConsent();
}
componentDidUpdate(prevProps: Props) {
);
};
+ 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 });
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);
<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}
<PluginsList
pending={pendingPlugins}
plugins={filteredPlugins}
- readOnly={!standaloneMode}
+ readOnly={!standaloneMode || riskConsent !== RiskConsent.Accepted}
refreshPending={this.props.fetchPendingPlugins}
/>
<Footer total={filteredPlugins.length} />
};
};
-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);
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', () => {
};
});
+jest.mock('../../../api/settings', () => ({
+ getValues: jest.fn().mockResolvedValue([]),
+ setSimpleSettingValue: jest.fn().mockResolvedValue(true)
+}));
+
beforeEach(jest.clearAllMocks);
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();
function shallowRender(props: Partial<App['props']> = {}) {
return shallow<App>(
<App
+ currentEdition={EditionKey.developer}
fetchPendingPlugins={jest.fn()}
location={mockLocation()}
pendingPlugins={{
--- /dev/null
+/*
+ * 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');
+ });
+});
+++ /dev/null
-// 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>
-`;
--- /dev/null
+/*
+ * 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>
+ );
+}
--- /dev/null
+/*
+ * 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} />);
+}
--- /dev/null
+// 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>
+`;
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'>;
const canCreateApplication =
appState.qualifiers.includes(ComponentQualifier.Application) &&
- hasGlobalPermission(currentUser, 'applicationcreator');
+ hasGlobalPermission(currentUser, Permissions.ApplicationCreation);
if (!canCreateApplication) {
return null;
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;
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">
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 {
boundAlms: Array<string>;
}
-const PROJECT_CREATION_PERMISSION = 'provisioning';
-
const almSettingsValidators = {
[AlmKeys.Azure]: (settings: AlmSettingsInstance) => !!settings.url,
[AlmKeys.BitbucketServer]: (_: AlmSettingsInstance) => true,
fetchAlmBindings = async () => {
const { currentUser } = this.props;
- const canCreateProject = hasGlobalPermission(currentUser, PROJECT_CREATION_PERMISSION);
+ const canCreateProject = hasGlobalPermission(currentUser, Permissions.ProjectCreation);
// getAlmSettings requires branchesEnabled
if (!canCreateProject) {
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;
*/
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();
// 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>
`;
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';
<Header
defaultProjectVisibility={defaultProjectVisibility}
- hasProvisionPermission={hasGlobalPermission(currentUser, 'provisioning')}
+ hasProvisionPermission={hasGlobalPermission(currentUser, Permissions.ProjectCreation)}
onChangeDefaultProjectVisibility={this.handleDefaultProjectVisibilityChange}
onProjectCreate={this.openCreateProjectForm}
/>
--- /dev/null
+/*
+ * 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'
+}
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;
}
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 };
marketplace.page.you_are_running.enterprise=You are currently running an Enterprise Edition.
marketplace.page.you_are_running.datacenter=You are currently running a Data Center Edition.
marketplace.page.plugins=Plugins
-marketplace.page.plugins.description=Plugins available in the MarketPlace are not provided or supported by SonarSource. Please reach out directly to their maintainers for support.
+marketplace.page.plugins.description=Plugins available in the Marketplace are not provided or supported by SonarSource. Please reach out directly to their maintainers for support.
+marketplace.page.plugins.description2=Installing a plugin is a manual operation. Please refer to the {link}.
+marketplace.page.plugins.description2.link=documentation
marketplace.plugin_list.no_plugins.all=No installed plugins or updates available
marketplace.plugin_list.no_plugins.installed=No installed plugins
marketplace.plugin_list.no_plugins.updates=No plugin updates available
marketplace.how_to_setup_cluster_url=Further configuration is required to set up a cluster. See {url} documentation.
marketplace.search=Search by features, tags, or categories...
+marketplace.risk_consent.title=Installation of plugins
+marketplace.risk_consent.description=Plugins are not provided by SonarSource and are therefore installed at your own risk. SonarSource disclaims all liability for installing and using such plugins.
+marketplace.risk_consent.installation=You can install plugins directly from the list below after you acknowledge the risk.
+marketplace.risk_consent.action=I understand the risk.
+
+plugin_risk_consent.title=Installation of plugins
+plugin_risk_consent.description=A third-party plugin has been detected.
+plugin_risk_consent.description2=Plugins are not provided by SonarSource and are therefore installed at your own risk. SonarSource disclaims all liability for installing and using such plugins.
+plugin_risk_consent.description3=If you wish to uninstall the plugin(s) instead, you may refer to the {link}.
+plugin_risk_consent.description3.link=documentation
+plugin_risk_consent.action=I understand the risk
#------------------------------------------------------------------------------
#
indexation.page_unavailable.title={componentQualifier} {componentName} is temporarily unavailable
indexation.page_unavailable.description=This page will be available after the data is reloaded. This might take a while depending on the amount of projects and issues in your SonarQube instance.
indexation.page_unavailable.description.additional_information=You can keep analyzing your projects during this process.
+
+
#------------------------------------------------------------------------------
#
# HOMEPAGE