From 2f2b3affe264f494ad5680966627cfe0649debba Mon Sep 17 00:00:00 2001 From: Guillaume Peoc'h Date: Tue, 20 Sep 2022 17:31:22 +0200 Subject: [PATCH] SONAR-17096 Ability to display a system announcement to all users --- .../js/app/components/GlobalContainer.tsx | 4 +- .../js/app/components/SystemAnnouncement.css | 35 ++++++++ .../js/app/components/SystemAnnouncement.tsx | 84 +++++++++++++++++ .../__tests__/SystemAnnouncement-test.tsx | 89 +++++++++++++++++++ .../sonar-web/src/main/js/types/settings.ts | 4 +- .../core/config/CorePropertyDefinitions.java | 5 +- 6 files changed, 217 insertions(+), 4 deletions(-) create mode 100644 server/sonar-web/src/main/js/app/components/SystemAnnouncement.css create mode 100644 server/sonar-web/src/main/js/app/components/SystemAnnouncement.tsx create mode 100644 server/sonar-web/src/main/js/app/components/__tests__/SystemAnnouncement-test.tsx diff --git a/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx b/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx index f9f4cecc211..f8d3c1d6012 100644 --- a/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx +++ b/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx @@ -32,6 +32,7 @@ import MetricsContextProvider from './metrics/MetricsContextProvider'; import GlobalNav from './nav/global/GlobalNav'; import PromotionNotification from './promotion-notification/PromotionNotification'; import StartupModal from './StartupModal'; +import SystemAnnouncement from './SystemAnnouncement'; import UpdateNotification from './update-notification/UpdateNotification'; export default function GlobalContainer() { @@ -51,9 +52,10 @@ export default function GlobalContainer() { - + + diff --git a/server/sonar-web/src/main/js/app/components/SystemAnnouncement.css b/server/sonar-web/src/main/js/app/components/SystemAnnouncement.css new file mode 100644 index 00000000000..6333b46e459 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/SystemAnnouncement.css @@ -0,0 +1,35 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 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. + */ + +.system-announcement-wrapper { + height: 34px; +} + +.system-announcement-banner { + position: fixed; + width: 100%; + z-index: var(--globalBannerZIndex); +} + +.system-announcement-banner .alert-content { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} diff --git a/server/sonar-web/src/main/js/app/components/SystemAnnouncement.tsx b/server/sonar-web/src/main/js/app/components/SystemAnnouncement.tsx new file mode 100644 index 00000000000..65e630c76dc --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/SystemAnnouncement.tsx @@ -0,0 +1,84 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 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 { isEmpty, keyBy } from 'lodash'; +import * as React from 'react'; +import { getValues } from '../../api/settings'; +import { Alert } from '../../components/ui/Alert'; +import { GlobalSettingKeys, SettingValue } from '../../types/settings'; +import './SystemAnnouncement.css'; + +interface State { + displayMessage: boolean; + message: string; +} + +export default class SystemAnnouncement extends React.PureComponent<{}, State> { + state: State = { displayMessage: false, message: '' }; + + componentDidMount() { + this.getSettings(); + document.addEventListener('visibilitychange', this.handleVisibilityChange); + } + + componentWillUnmount() { + document.removeEventListener('visibilitychange', this.handleVisibilityChange); + } + + getSettings = async () => { + const values: SettingValue[] = await getValues({ + keys: [GlobalSettingKeys.DisplaySystemMessage, GlobalSettingKeys.SystemMessage].join(',') + }); + const settings = keyBy(values, 'key'); + + this.setState({ + displayMessage: settings[GlobalSettingKeys.DisplaySystemMessage].value === 'true', + message: + (settings[GlobalSettingKeys.SystemMessage] && + settings[GlobalSettingKeys.SystemMessage].value) || + '' + }); + }; + + handleVisibilityChange = () => { + if (document.visibilityState === 'visible') { + this.getSettings(); + } + }; + + render() { + const { displayMessage, message } = this.state; + if (!displayMessage || isEmpty(message)) { + return null; + } + + return ( +
+ + {message} + +
+ ); + } +} diff --git a/server/sonar-web/src/main/js/app/components/__tests__/SystemAnnouncement-test.tsx b/server/sonar-web/src/main/js/app/components/__tests__/SystemAnnouncement-test.tsx new file mode 100644 index 00000000000..cc837ef72d0 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/__tests__/SystemAnnouncement-test.tsx @@ -0,0 +1,89 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 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 { fireEvent, screen } from '@testing-library/dom'; +import * as React from 'react'; +import { getValues } from '../../../api/settings'; +import { renderComponent } from '../../../helpers/testReactTestingUtils'; +import SystemAnnouncement from '../SystemAnnouncement'; + +jest.mock('../../../api/settings', () => ({ + getValues: jest.fn() +})); + +it('should display system announcement', async () => { + (getValues as jest.Mock) + .mockResolvedValueOnce([ + { + key: 'sonar.systemAnnouncement.displayMessage', + value: 'false', + inherited: true + } + ]) + .mockResolvedValueOnce([ + { + key: 'sonar.systemAnnouncement.displayMessage', + value: 'false', + inherited: true + } + ]) + .mockResolvedValueOnce([ + { + key: 'sonar.systemAnnouncement.displayMessage', + value: 'true' + } + ]) + .mockResolvedValueOnce([ + { + key: 'sonar.systemAnnouncement.displayMessage', + value: 'true' + }, + { + key: 'sonar.systemAnnouncement.message', + value: '' + } + ]) + .mockResolvedValueOnce([ + { + key: 'sonar.systemAnnouncement.displayMessage', + value: 'true' + }, + { + key: 'sonar.systemAnnouncement.message', + value: 'Foo' + } + ]); + + renderSystemAnnouncement(); + + expect(screen.queryByRole('alert')).not.toBeInTheDocument(); + fireEvent(document, new Event('visibilitychange')); + expect(screen.queryByRole('alert')).not.toBeInTheDocument(); + fireEvent(document, new Event('visibilitychange')); + expect(screen.queryByRole('alert')).not.toBeInTheDocument(); + fireEvent(document, new Event('visibilitychange')); + expect(screen.queryByRole('alert')).not.toBeInTheDocument(); + fireEvent(document, new Event('visibilitychange')); + expect(await screen.findByRole('alert')).toBeInTheDocument(); + expect(screen.getByText('Foo')).toBeInTheDocument(); +}); + +function renderSystemAnnouncement() { + return renderComponent(); +} diff --git a/server/sonar-web/src/main/js/types/settings.ts b/server/sonar-web/src/main/js/types/settings.ts index 9374cf76253..079c23e8b6c 100644 --- a/server/sonar-web/src/main/js/types/settings.ts +++ b/server/sonar-web/src/main/js/types/settings.ts @@ -36,7 +36,9 @@ export enum GlobalSettingKeys { GravatarServerUrl = 'sonar.lf.gravatarServerUrl', RatingGrid = 'sonar.technicalDebt.ratingGrid', DeveloperAggregatedInfoDisabled = 'sonar.developerAggregatedInfo.disabled', - UpdatecenterActivated = 'sonar.updatecenter.activate' + UpdatecenterActivated = 'sonar.updatecenter.activate', + DisplaySystemMessage = 'sonar.systemAnnouncement.displayMessage', + SystemMessage = 'sonar.systemAnnouncement.message' } export type SettingDefinitionAndValue = { diff --git a/sonar-core/src/main/java/org/sonar/core/config/CorePropertyDefinitions.java b/sonar-core/src/main/java/org/sonar/core/config/CorePropertyDefinitions.java index 985e3120895..397314d3c17 100644 --- a/sonar-core/src/main/java/org/sonar/core/config/CorePropertyDefinitions.java +++ b/sonar-core/src/main/java/org/sonar/core/config/CorePropertyDefinitions.java @@ -94,14 +94,15 @@ public class CorePropertyDefinitions { .build(), PropertyDefinition.builder(SYSTEM_ANNOUNCEMENT_MESSAGE) .name("System announcement message") - .description("If \"Display system announcement message\" is set to True, this message will be displayed in a banner to all authenticate SonarQube users.") + .description("If \"Display system announcement message\" is set to True, this message will be displayed in a warning banner to all SonarQube users." + +" If this field is empty, no message will be displayed, even if \"Display system announcement message\" is set to True.") .category(CoreProperties.CATEGORY_GENERAL) .subCategory(SUB_SYSTEM_ANNOUNCEMENT) .type(TEXT) .build(), PropertyDefinition.builder(SYSTEM_ANNOUNCEMENT_DISPLAY_MESSAGE) .name("Display system announcement message") - .description("If set to True, the \"System announcement message\" will be displayed in a banner to all authenticate SonarQube users.") + .description("If set to True, the \"System announcement message\" will be displayed in a warning banner to all SonarQube users.") .defaultValue(Boolean.toString(false)) .subCategory(SUB_SYSTEM_ANNOUNCEMENT) .category(CoreProperties.CATEGORY_GENERAL) -- 2.39.5