import SuggestionsProvider from '../../components/embed-docs-modal/SuggestionsProvider';
import NCDAutoUpdateMessage from '../../components/new-code-definition/NCDAutoUpdateMessage';
import Workspace from '../../components/workspace/Workspace';
+import CalculationChangeMessage from './calculation-notification/CalculationChangeMessage';
import GlobalFooter from './GlobalFooter';
-import StartupModal from './StartupModal';
-import SystemAnnouncement from './SystemAnnouncement';
import IndexationContextProvider from './indexation/IndexationContextProvider';
import IndexationNotification from './indexation/IndexationNotification';
import LanguagesContextProvider from './languages/LanguagesContextProvider';
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';
/*
<NCDAutoUpdateMessage />
<UpdateNotification dismissable />
<GlobalNav location={location} />
+ <CalculationChangeMessage />
{/* The following is the portal anchor point for the component nav
* See ComponentContainer.tsx
*/}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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 React from 'react';
+import { Outlet, Route } from 'react-router-dom';
+import { byRole, byText } from '~sonar-aligned/helpers/testSelector';
+import { ComponentQualifier } from '~sonar-aligned/types/component';
+import SettingsServiceMock from '../../../api/mocks/SettingsServiceMock';
+import { renderAppRoutes } from '../../../helpers/testReactTestingUtils';
+import { SettingsKey } from '../../../types/settings';
+import CalculationChangeMessage from '../calculation-notification/CalculationChangeMessage';
+
+const ui = {
+ alert: byRole('alert'),
+ learnMoreLink: byRole('link', { name: 'learn_more open_in_new_tab' }),
+
+ alertText: (qualifier: string) => byText(`notification.calculation_change.message.${qualifier}`),
+};
+
+const settingsHandler = new SettingsServiceMock();
+
+beforeEach(() => {
+ settingsHandler.reset();
+});
+
+it.each([
+ ['Project', '/projects', ComponentQualifier.Project],
+ ['Portfolios', '/portfolios', ComponentQualifier.Portfolio],
+])('should render on %s page', (_, path, qualifier) => {
+ render(path);
+ expect(ui.alert.get()).toBeInTheDocument();
+ expect(ui.alertText(qualifier).get()).toBeInTheDocument();
+ expect(ui.learnMoreLink.get()).toBeInTheDocument();
+});
+
+it.each([
+ ['Project', '/projects', ComponentQualifier.Project],
+ ['Portfolios', '/portfolios', ComponentQualifier.Portfolio],
+])('should not render on %s page if isLegacy', (_, path, qualifier) => {
+ settingsHandler.set(SettingsKey.LegacyMode, 'true');
+ render(path);
+ expect(ui.alert.get()).toBeInTheDocument();
+ expect(ui.alertText(qualifier).get()).toBeInTheDocument();
+ expect(ui.learnMoreLink.get()).toBeInTheDocument();
+});
+
+it('should not render on other page', () => {
+ render('/other');
+ expect(ui.alert.query()).not.toBeInTheDocument();
+ expect(ui.alertText(ComponentQualifier.Project).query()).not.toBeInTheDocument();
+ expect(ui.learnMoreLink.query()).not.toBeInTheDocument();
+});
+
+function render(indexPath = '/projects') {
+ renderAppRoutes(indexPath, () => (
+ <Route
+ path="/"
+ element={
+ <>
+ <CalculationChangeMessage />
+ <Outlet />
+ </>
+ }
+ >
+ <Route path="projects" element={<div>Projects</div>} />
+ <Route path="portfolios" element={<div>Portfolios</div>} />
+ <Route path="other" element={<div>Other page</div>} />
+ </Route>
+ ));
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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 React from 'react';
+import { FormattedMessage } from 'react-intl';
+import { useLocation } from '~sonar-aligned/components/hoc/withRouter';
+import { ComponentQualifier } from '~sonar-aligned/types/component';
+import DocumentationLink from '../../../components/common/DocumentationLink';
+import DismissableAlert from '../../../components/ui/DismissableAlert';
+import { DocLink } from '../../../helpers/doc-links';
+import { translate } from '../../../helpers/l10n';
+import { useIsLegacyCCTMode } from '../../../queries/settings';
+import { Dict } from '../../../types/types';
+
+const SHOW_MESSAGE_PATHS: Dict<ComponentQualifier> = {
+ '/projects': ComponentQualifier.Project,
+ '/portfolios': ComponentQualifier.Portfolio,
+};
+
+const ALERT_KEY = 'sonarqube.dismissed_calculation_change_alert';
+
+export default function CalculationChangeMessage() {
+ const location = useLocation();
+ const { data: isLegacy } = useIsLegacyCCTMode();
+
+ if (isLegacy || !Object.keys(SHOW_MESSAGE_PATHS).includes(location.pathname)) {
+ return null;
+ }
+
+ return (
+ <DismissableAlert variant="info" alertKey={ALERT_KEY + SHOW_MESSAGE_PATHS[location.pathname]}>
+ <FormattedMessage
+ id={`notification.calculation_change.message.${SHOW_MESSAGE_PATHS[location.pathname]}`}
+ values={{
+ link: (
+ <DocumentationLink to={DocLink.MetricDefinitions}>
+ {translate('learn_more')}
+ </DocumentationLink>
+ ),
+ }}
+ />
+ </DismissableAlert>
+ );
+}
React.useEffect(() => {
if (get(DISMISSED_ALERT_STORAGE_KEY, alertKey) !== 'true') {
setShow(true);
+ } else {
+ setShow(false);
}
}, [alertKey]);
notification.dispatcher.CeReportTaskFailure=Background tasks in failure on my administered projects
notification.dispatcher.CeReportTaskFailure.project=Background tasks in failure
notification.dispatcher.description_x=Check to receive notification for {0}
+notification.calculation_change.message.TRK=The way we calculate ratings has changed and it might affect your security, reliability, maintainability and security review ratings. {link}
+notification.calculation_change.message.VW=The way we calculate ratings has changed and it might affect your releasability, security, reliability, maintainability and security review ratings. {link}
#------------------------------------------------------------------------------
#
alert.dismiss=Dismiss this message
-
#------------------------------------------------------------------------------
#
# USER