From 94ff5648e0af09146d3db4c9a2fdba1852911022 Mon Sep 17 00:00:00 2001 From: Jeremy Davis Date: Tue, 10 Aug 2021 17:50:25 +0200 Subject: [PATCH] SONAR-15258 Add App Report Settings --- .../js/app/components/nav/component/Menu.tsx | 18 +++ .../src/main/js/app/utils/startReactApp.tsx | 2 + .../ApplicationSettingsApp.tsx | 136 ++++++++++++++++++ .../ReportFrequencyForm.tsx | 96 +++++++++++++ .../__tests__/ApplicationSettingsApp-test.tsx | 70 +++++++++ .../__tests__/ReportFrequencyForm-test.tsx | 70 +++++++++ .../ApplicationSettingsApp-test.tsx.snap | 59 ++++++++ .../ReportFrequencyForm-test.tsx.snap | 108 ++++++++++++++ .../js/apps/application-settings/routes.ts | 28 ++++ .../src/main/js/helpers/mocks/settings.ts | 15 +- .../sonar-web/src/main/js/types/settings.ts | 3 +- .../resources/org/sonar/l10n/core.properties | 3 + 12 files changed, 606 insertions(+), 2 deletions(-) create mode 100644 server/sonar-web/src/main/js/apps/application-settings/ApplicationSettingsApp.tsx create mode 100644 server/sonar-web/src/main/js/apps/application-settings/ReportFrequencyForm.tsx create mode 100644 server/sonar-web/src/main/js/apps/application-settings/__tests__/ApplicationSettingsApp-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/application-settings/__tests__/ReportFrequencyForm-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/application-settings/__tests__/__snapshots__/ApplicationSettingsApp-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/application-settings/__tests__/__snapshots__/ReportFrequencyForm-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/application-settings/routes.ts diff --git a/server/sonar-web/src/main/js/app/components/nav/component/Menu.tsx b/server/sonar-web/src/main/js/app/components/nav/component/Menu.tsx index c56114349b5..eee1d068e57 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/Menu.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/Menu.tsx @@ -291,6 +291,7 @@ export class Menu extends React.PureComponent { this.renderBranchesLink(query, isProject), this.renderBaselineLink(query, isApplication, isPortfolio), this.renderConsoleAppLink(query, isApplication), + this.renderReportSettingsLink(query, isApplication), ...this.renderAdminExtensions(query, isApplication), this.renderProfilesLink(query), this.renderQualityGateLink(query), @@ -385,6 +386,23 @@ export class Menu extends React.PureComponent { ); }; + renderReportSettingsLink = (query: Query, isApplication: boolean) => { + const extensions = this.getConfiguration().extensions || []; + const hasGovernance = extensions.find(e => e.key === 'governance/console'); + + if (!isApplication || !hasGovernance) { + return null; + } + + return ( +
  • + + {translate('application_settings.report')} + +
  • + ); + }; + renderProfilesLink = (query: Query) => { if (!this.getConfiguration().showQualityProfiles) { return null; diff --git a/server/sonar-web/src/main/js/app/utils/startReactApp.tsx b/server/sonar-web/src/main/js/app/utils/startReactApp.tsx index f3d5ed8709b..9f1187c0720 100644 --- a/server/sonar-web/src/main/js/app/utils/startReactApp.tsx +++ b/server/sonar-web/src/main/js/app/utils/startReactApp.tsx @@ -32,6 +32,7 @@ import getHistory from 'sonar-ui-common/helpers/getHistory'; import aboutRoutes from '../../apps/about/routes'; import accountRoutes from '../../apps/account/routes'; import applicationConsoleRoutes from '../../apps/application-console/routes'; +import applicationSettingsRoutes from '../../apps/application-settings/routes'; import auditLogsRoutes from '../../apps/audit-logs/routes'; import backgroundTasksRoutes from '../../apps/background-tasks/routes'; import codeRoutes from '../../apps/code/routes'; @@ -203,6 +204,7 @@ function renderComponentRoutes() { + { + mounted = false; + + state: State = { + loading: true + }; + + componentDidMount() { + this.mounted = true; + this.fetchInitialData(); + } + + async componentDidUpdate(prevProps: Props) { + if (prevProps.component.key !== this.props.component.key) { + this.setState({ loading: true }); + await this.fetchSetting(); + this.setState({ loading: false }); + } + } + + componentWillUnmount() { + this.mounted = false; + } + + fetchInitialData = async () => { + this.setState({ loading: true }); + await Promise.all([this.fetchDefinition(), this.fetchSetting()]); + this.setState({ loading: false }); + }; + + fetchDefinition = async () => { + const { component } = this.props; + try { + const definitions = await getDefinitions(component.key); + const definition = definitions.find(d => d.key === SettingsKey.ProjectReportFrequency); + + if (this.mounted) { + this.setState({ definition }); + } + } catch (_) { + /* do nothing */ + } + }; + + fetchSetting = async () => { + const { component } = this.props; + try { + const setting = ( + await getValues({ component: component.key, keys: SettingsKey.ProjectReportFrequency }) + ).pop(); + + if (this.mounted) { + this.setState({ + frequency: setting ? setting.value : undefined + }); + } + } catch (_) { + /* do nothing */ + } + }; + + handleSubmit = async (frequency: string) => { + const { component } = this.props; + + try { + await setSimpleSettingValue({ + component: component.key, + key: SettingsKey.ProjectReportFrequency, + value: frequency + }); + + this.setState({ frequency }); + } catch (_) { + /* Do nothing */ + } + }; + + render() { + const { definition, frequency, loading } = this.state; + + return ( +
    +

    {translate('application_settings.page')}

    +
    + +
    + {definition && frequency && ( + + )} +
    +
    +
    +
    + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/application-settings/ReportFrequencyForm.tsx b/server/sonar-web/src/main/js/apps/application-settings/ReportFrequencyForm.tsx new file mode 100644 index 00000000000..2d8790133a9 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/application-settings/ReportFrequencyForm.tsx @@ -0,0 +1,96 @@ +/* + * 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, ResetButtonLink } from 'sonar-ui-common/components/controls/buttons'; +import Select from 'sonar-ui-common/components/controls/Select'; +import { translate } from 'sonar-ui-common/helpers/l10n'; +import { sanitizeStringRestricted } from '../../helpers/sanitize'; +import { SettingCategoryDefinition } from '../../types/settings'; + +export interface ReportFrequencyFormProps { + definition: SettingCategoryDefinition; + frequency: string; + onSave: (value: string) => Promise; +} + +export default function ReportFrequencyForm(props: ReportFrequencyFormProps) { + const { definition, frequency } = props; + const { defaultValue } = definition; + + const [currentSelection, setCurrentSelection] = React.useState(frequency); + + const options = props.definition.options.map(option => ({ + label: option, + value: option + })); + + const handleReset = () => { + if (defaultValue) { + setCurrentSelection(defaultValue); + props.onSave(defaultValue); + } + }; + + return ( +
    +

    {translate('application_settings.report.frequency')}

    + {definition.description && ( +
    + )} + + +
    + +
    +
    +`; + +exports[`should render correctly: default 1`] = ` +
    +

    + application_settings.report.frequency +

    +
    + +
    +
    +`; diff --git a/server/sonar-web/src/main/js/apps/application-settings/routes.ts b/server/sonar-web/src/main/js/apps/application-settings/routes.ts new file mode 100644 index 00000000000..18822c1779c --- /dev/null +++ b/server/sonar-web/src/main/js/apps/application-settings/routes.ts @@ -0,0 +1,28 @@ +/* + * 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 { lazyLoadComponent } from 'sonar-ui-common/components/lazyLoadComponent'; + +const routes = [ + { + indexRoute: { component: lazyLoadComponent(() => import('./ApplicationSettingsApp')) } + } +]; + +export default routes; diff --git a/server/sonar-web/src/main/js/helpers/mocks/settings.ts b/server/sonar-web/src/main/js/helpers/mocks/settings.ts index 59fe8ecb25c..14065d55238 100644 --- a/server/sonar-web/src/main/js/helpers/mocks/settings.ts +++ b/server/sonar-web/src/main/js/helpers/mocks/settings.ts @@ -17,7 +17,20 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { Setting, SettingWithCategory } from '../../types/settings'; +import { Setting, SettingCategoryDefinition, SettingWithCategory } from '../../types/settings'; + +export function mockDefinition( + overrides: Partial = {} +): SettingCategoryDefinition { + return { + key: 'foo', + category: 'foo category', + fields: [], + options: [], + subCategory: 'foo subCat', + ...overrides + }; +} export function mockSetting(overrides: Partial = {}): Setting { return { diff --git a/server/sonar-web/src/main/js/types/settings.ts b/server/sonar-web/src/main/js/types/settings.ts index 6432a176068..b272b5b9943 100644 --- a/server/sonar-web/src/main/js/types/settings.ts +++ b/server/sonar-web/src/main/js/types/settings.ts @@ -21,7 +21,8 @@ export const enum SettingsKey { DaysBeforeDeletingInactiveBranchesAndPRs = 'sonar.dbcleaner.daysBeforeDeletingInactiveBranchesAndPRs', DefaultProjectVisibility = 'projects.default.visibility', ServerBaseUrl = 'sonar.core.serverBaseURL', - PluginRiskConsent = 'sonar.plugins.risk.consent' + PluginRiskConsent = 'sonar.plugins.risk.consent', + ProjectReportFrequency = 'sonar.governance.report.project.branch.frequency' } export type Setting = SettingValue & { definition: SettingDefinition }; diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index 8e79a50764e..152d00ca6f4 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -562,6 +562,9 @@ application_console.delete_application=Delete Application application_console.recompute=Recompute application_console.refresh_started=Your application will be recomputed soon application_console.do_you_want_to_delete=Are you sure that you want to delete "{0}"? +application_settings.page=Application Settings +application_settings.report=Application Report Settings +application_settings.report.frequency=Application Reports Frequency coding_rules.page=Rules global_permissions.page=Global Permissions global_permissions.page.description=Grant and revoke permissions to make changes at the global level. These permissions include editing Quality Profiles, executing analysis, and performing global system administration. -- 2.39.5