]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-19032 Migrate global messages to MIUI
authorDavid Cho-Lerat <david.cho-lerat@sonarsource.com>
Fri, 16 Feb 2024 10:24:05 +0000 (11:24 +0100)
committersonartech <sonartech@sonarsource.com>
Thu, 7 Mar 2024 20:02:26 +0000 (20:02 +0000)
54 files changed:
server/sonar-web/design-system/src/components/index.ts
server/sonar-web/design-system/src/components/toast-message/__tests__/toast-utils-test.tsx
server/sonar-web/design-system/src/components/toast-message/toast-utils.tsx
server/sonar-web/src/main/js/app/components/ComponentContainerNotFound.tsx
server/sonar-web/src/main/js/app/components/GlobalContainer.tsx
server/sonar-web/src/main/js/app/components/GlobalMessage.tsx [deleted file]
server/sonar-web/src/main/js/app/components/GlobalMessagesContainer.tsx [deleted file]
server/sonar-web/src/main/js/app/components/__tests__/GlobalMessagesContainer-it.tsx [deleted file]
server/sonar-web/src/main/js/app/components/extensions/Extension.tsx
server/sonar-web/src/main/js/app/components/extensions/__tests__/Extension-test.tsx
server/sonar-web/src/main/js/app/components/extensions/exposeLibraries.ts
server/sonar-web/src/main/js/app/components/nav/settings/PendingPluginsActionNotif.tsx
server/sonar-web/src/main/js/app/index.ts
server/sonar-web/src/main/js/app/styles/GlobalStyles.tsx
server/sonar-web/src/main/js/app/utils/startReactApp.tsx
server/sonar-web/src/main/js/apps/audit-logs/components/DownloadButton.tsx
server/sonar-web/src/main/js/apps/background-tasks/components/TaskExecutionTime.tsx
server/sonar-web/src/main/js/apps/permissions/project/components/PublicProjectDisclaimer.tsx
server/sonar-web/src/main/js/apps/projectDeletion/App.tsx
server/sonar-web/src/main/js/apps/projectDeletion/Form.tsx
server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaLinks.tsx
server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/ProjectQualityGateApp-it.tsx
server/sonar-web/src/main/js/apps/projectsManagement/BulkApplyTemplateModal.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/RenameQualityGateForm.tsx
server/sonar-web/src/main/js/apps/security-hotspots/__tests__/SecurityHotspotsApp-it.tsx
server/sonar-web/src/main/js/apps/security-hotspots/components/Assignee.tsx
server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotOpenInIdeButton.tsx
server/sonar-web/src/main/js/apps/sessions/components/LoginContainer.tsx
server/sonar-web/src/main/js/apps/sessions/components/Logout.tsx
server/sonar-web/src/main/js/apps/sessions/components/__tests__/Login-it.tsx
server/sonar-web/src/main/js/apps/sessions/components/__tests__/Logout-it.tsx
server/sonar-web/src/main/js/apps/settings/components/AnalysisScope.tsx
server/sonar-web/src/main/js/apps/settings/components/DefinitionsList.tsx
server/sonar-web/src/main/js/apps/settings/components/inputs/InputForFormattedText.tsx
server/sonar-web/src/main/js/apps/settings/components/inputs/InputForJSON.tsx
server/sonar-web/src/main/js/apps/users/components/PasswordForm.tsx
server/sonar-web/src/main/js/apps/users/components/UserForm.tsx
server/sonar-web/src/main/js/apps/webhooks/components/PageActions.tsx
server/sonar-web/src/main/js/components/SourceViewer/SourceViewer.tsx
server/sonar-web/src/main/js/components/common/RestartButton.tsx
server/sonar-web/src/main/js/components/controls/ComponentReportActions.tsx
server/sonar-web/src/main/js/components/controls/__tests__/ComponentReportActions-test.tsx
server/sonar-web/src/main/js/components/new-code-definition/__tests__/NCDAutoUpdateMessage-test.tsx
server/sonar-web/src/main/js/components/tutorials/components/GradleBuild.tsx
server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/CFamilly.tsx
server/sonar-web/src/main/js/components/tutorials/jenkins/buildtool-steps/DotNet.tsx
server/sonar-web/src/main/js/components/tutorials/other/TokenStep.tsx
server/sonar-web/src/main/js/helpers/__tests__/error-test.ts
server/sonar-web/src/main/js/helpers/__tests__/globalMessages-test.ts [deleted file]
server/sonar-web/src/main/js/helpers/error.ts
server/sonar-web/src/main/js/helpers/globalMessages.ts
server/sonar-web/src/main/js/helpers/testReactTestingUtils.tsx
server/sonar-web/src/main/js/queries/quality-gates.ts
server/sonar-web/src/main/js/types/globalMessages.ts [deleted file]

index c23b99a2d68b197f22cf27462f49d7ba3dc4b8d3..b5b2e73515a9edd5b5b8ad1d7a563c789586f8d2 100644 (file)
@@ -17,6 +17,7 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+
 export * from './Accordion';
 export { Badge } from './Badge';
 export * from './Banner';
@@ -78,7 +79,7 @@ export * from './Tags';
 export * from './Text';
 export * from './TextAccordion';
 export * from './Title';
-export { ToggleButton } from './ToggleButton';
+export * from './ToggleButton';
 export { Tooltip } from './Tooltip';
 export { TopBar } from './TopBar';
 export * from './TreeMap';
index 7c3478474ef257a10242eb579ea22411d33afcb7..41d63e54447bb6198c432508781d7e6185df1bfc 100644 (file)
@@ -33,7 +33,7 @@ it('should call react-toastify with the right args', () => {
   addGlobalErrorMessage(<span>error</span>, { position: 'top-left' });
 
   expect(toast).toHaveBeenCalledWith(
-    <div className="fs-mask sw-body-sm sw-p-3 sw-pb-4" data-test="global-message__ERROR">
+    <div className="fs-mask sw-body-sm sw-p-3 sw-pb-4" data-testid="global-message__ERROR">
       <span>error</span>
     </div>,
     { icon: <FlagErrorIcon />, type: 'error', position: 'top-left' },
@@ -42,7 +42,7 @@ it('should call react-toastify with the right args', () => {
   addGlobalSuccessMessage('it worked');
 
   expect(toast).toHaveBeenCalledWith(
-    <div className="fs-mask sw-body-sm sw-p-3 sw-pb-4" data-test="global-message__SUCCESS">
+    <div className="fs-mask sw-body-sm sw-p-3 sw-pb-4" data-testid="global-message__SUCCESS">
       it worked
     </div>,
     { icon: <FlagSuccessIcon />, type: 'success' },
index af5340cc2d38260d44e88867cc6c6adeb5e1f8af..4aba037b8a04d5964568c2921d485fb00a98a284 100644 (file)
@@ -45,7 +45,7 @@ export function dismissAllGlobalMessages() {
 
 function createToast(message: ReactNode, level: MessageLevel, overrides?: ToastOptions) {
   return toast(
-    <div className="fs-mask sw-body-sm sw-p-3 sw-pb-4" data-test={`global-message__${level}`}>
+    <div className="fs-mask sw-body-sm sw-p-3 sw-pb-4" data-testid={`global-message__${level}`}>
       {message}
     </div>,
     {
index b96e54a5d4dc7dcb95e4817e48ee485f42bc91b1..f12021725d342dc203bed03b7d9cc72ff195ab2e 100644 (file)
@@ -17,7 +17,9 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { Card, CenteredLayout, Link, SubHeading } from 'design-system/lib';
+
+import { Link } from '@sonarsource/echoes-react';
+import { Card, CenteredLayout, SubHeading } from 'design-system';
 import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
 import { translate } from '../../helpers/l10n';
index d8aa49d5ce25c1fef9af0250a82da0eba051c5c3..2669f611a71e7741a309928354f228491a2a4864 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+
 import { ThemeProvider } from '@emotion/react';
 import styled from '@emotion/styled';
-import { lightTheme, themeColor, ToastMessageContainer } from 'design-system';
+import { lightTheme, themeColor } from 'design-system';
 import * as React from 'react';
 import { Outlet, useLocation } from 'react-router-dom';
 import A11yProvider from '../../components/a11y/A11yProvider';
@@ -28,14 +29,14 @@ import SuggestionsProvider from '../../components/embed-docs-modal/SuggestionsPr
 import NCDAutoUpdateMessage from '../../components/new-code-definition/NCDAutoUpdateMessage';
 import Workspace from '../../components/workspace/Workspace';
 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';
 
 /*
@@ -89,7 +90,6 @@ export default function GlobalContainer() {
             >
               <div className="page-container">
                 <Workspace>
-                  <ToastMessageContainer />
                   <IndexationContextProvider>
                     <LanguagesContextProvider>
                       <MetricsContextProvider>
diff --git a/server/sonar-web/src/main/js/app/components/GlobalMessage.tsx b/server/sonar-web/src/main/js/app/components/GlobalMessage.tsx
deleted file mode 100644 (file)
index e6063e1..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * 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 { keyframes } from '@emotion/react';
-import styled from '@emotion/styled';
-import * as React from 'react';
-import { ClearButton } from '../../components/controls/buttons';
-import { cutLongWords } from '../../helpers/path';
-import { Message } from '../../types/globalMessages';
-import { colors, sizes } from '../theme';
-
-export interface GlobalMessageProps {
-  closeGlobalMessage: (id: string) => void;
-  message: Message;
-}
-
-export default function GlobalMessage(props: GlobalMessageProps) {
-  const { message } = props;
-  return (
-    <MessageBox data-testid={`global-message__${message.level}`} level={message.level}>
-      {cutLongWords(message.text)}
-      <CloseButton
-        className="button-small"
-        color="#fff"
-        level={message.level}
-        onClick={() => props.closeGlobalMessage(message.id)}
-      />
-    </MessageBox>
-  );
-}
-
-const appearAnim = keyframes`
-  from {
-    opacity: 0;
-  }
-  to {
-    opacity: 1;
-  }
-`;
-
-const MessageBox = styled.div<Pick<Message, 'level'>>`
-  position: relative;
-  padding: 0 30px 0 10px;
-  line-height: ${sizes.controlHeight};
-  border-radius: 0 0 3px 3px;
-  box-sizing: border-box;
-  color: #ffffff;
-  background-color: ${({ level }) => (level === 'SUCCESS' ? colors.green : colors.red)};
-  text-align: center;
-  opacity: 0;
-  animation: ${appearAnim} 0.2s ease forwards;
-
-  margin-top: calc(${sizes.gridSize} / 2);
-  border-radius: 3px;
-`;
-
-const CloseButton = styled(ClearButton)<Pick<Message, 'level'>>`
-  position: absolute;
-  top: calc(${sizes.gridSize} / 4);
-  right: calc(${sizes.gridSize} / 4);
-
-  &.button-icon:hover svg,
-  &.button-icon:focus svg {
-    color: ${({ level }) => (level === 'SUCCESS' ? colors.green : colors.red)};
-  }
-`;
diff --git a/server/sonar-web/src/main/js/app/components/GlobalMessagesContainer.tsx b/server/sonar-web/src/main/js/app/components/GlobalMessagesContainer.tsx
deleted file mode 100644 (file)
index 9d976c3..0000000
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * 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 styled from '@emotion/styled';
-import React from 'react';
-import { registerListener, unregisterListener } from '../../helpers/globalMessages';
-import { Message, MessageLevel } from '../../types/globalMessages';
-import { zIndexes } from '../theme';
-import GlobalMessage from './GlobalMessage';
-
-const MESSAGE_DISPLAY_TIME = 10000;
-const MAX_MESSAGES = 3;
-
-interface State {
-  messages: Message[];
-}
-
-export default class GlobalMessagesContainer extends React.Component<{}, State> {
-  mounted = false;
-
-  constructor(props: {}) {
-    super(props);
-
-    this.state = {
-      messages: [],
-    };
-  }
-
-  componentDidMount() {
-    this.mounted = true;
-    registerListener(this.handleAddMessage);
-  }
-
-  componentWillUnmount() {
-    this.mounted = false;
-    unregisterListener(this.handleAddMessage);
-  }
-
-  handleAddMessage = (message: Message) => {
-    if (
-      this.mounted &&
-      !this.state.messages.some((m) => m.level === MessageLevel.Error && m.text === message.text)
-    ) {
-      this.setState(({ messages }) => ({
-        messages: [...messages, message].slice(-MAX_MESSAGES),
-      }));
-
-      setTimeout(() => {
-        this.closeMessage(message.id);
-      }, MESSAGE_DISPLAY_TIME);
-    }
-  };
-
-  closeMessage = (messageId: string) => {
-    if (this.mounted) {
-      this.setState(({ messages }) => {
-        return { messages: messages.filter((m) => m.id !== messageId) };
-      });
-    }
-  };
-
-  render() {
-    const { messages } = this.state;
-
-    if (messages.length === 0) {
-      return null;
-    }
-
-    return (
-      <MessagesContainer>
-        <div role="alert">
-          {messages
-            .filter((m) => m.level === MessageLevel.Error)
-            .map((message) => (
-              <GlobalMessage
-                closeGlobalMessage={this.closeMessage}
-                key={message.id}
-                message={message}
-              />
-            ))}
-        </div>
-        <output>
-          {messages
-            .filter((m) => m.level === MessageLevel.Success)
-            .map((message) => (
-              <GlobalMessage
-                closeGlobalMessage={this.closeMessage}
-                key={message.id}
-                message={message}
-              />
-            ))}
-        </output>
-      </MessagesContainer>
-    );
-  }
-}
-
-const MessagesContainer = styled.div`
-  position: fixed;
-  z-index: ${zIndexes.processContainerZIndex};
-  top: 0;
-  left: 50%;
-  width: 350px;
-  margin-left: -175px;
-  z-index: 8600;
-`;
diff --git a/server/sonar-web/src/main/js/app/components/__tests__/GlobalMessagesContainer-it.tsx b/server/sonar-web/src/main/js/app/components/__tests__/GlobalMessagesContainer-it.tsx
deleted file mode 100644 (file)
index ac01279..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * 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 { act, screen } from '@testing-library/react';
-import React from 'react';
-import { addGlobalErrorMessage, addGlobalSuccessMessage } from '../../../helpers/globalMessages';
-import { renderApp } from '../../../helpers/testReactTestingUtils';
-
-function NullComponent() {
-  return null;
-}
-
-it('should display messages', async () => {
-  jest.useFakeTimers();
-
-  // we render anything, the GlobalMessageContainer is rendered independently from routing
-  renderApp('sonarqube', <NullComponent />);
-
-  act(() => {
-    addGlobalErrorMessage('This is an error');
-    addGlobalSuccessMessage('This was a triumph!');
-  });
-
-  expect(screen.getByRole('alert')).toHaveTextContent('This is an error');
-  expect(screen.getByRole('status')).toHaveTextContent('This was a triumph!');
-
-  // No duplicate message
-  act(() => {
-    addGlobalErrorMessage('This is an error');
-  });
-
-  expect(screen.getByRole('alert')).toHaveTextContent(/^This is an error$/);
-  act(() => {
-    addGlobalSuccessMessage('This was a triumph!');
-  });
-  expect(await screen.findByRole('status')).toHaveTextContent(
-    /^This was a triumph!This was a triumph!$/,
-  );
-
-  act(() => {
-    jest.runAllTimers();
-  });
-  expect(screen.queryByRole('alert')).not.toBeInTheDocument();
-  expect(screen.queryByRole('status')).not.toBeInTheDocument();
-
-  jest.useRealTimers();
-});
index 1b79c3fad1cea09a5bc463b33d6284a2d5adc646..92941228fff9dec1597df8f5d203458ab04f1127 100644 (file)
 
 import { withTheme } from '@emotion/react';
 import { QueryClient } from '@tanstack/react-query';
-import { Theme } from 'design-system';
+import { addGlobalErrorMessage, Theme } from 'design-system';
 import { isEqual } from 'lodash';
 import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
 import { injectIntl, WrappedComponentProps } from 'react-intl';
 import { Location, Router, withRouter } from '../../../components/hoc/withRouter';
 import { getExtensionStart } from '../../../helpers/extensions';
-import { addGlobalErrorMessage } from '../../../helpers/globalMessages';
 import { translate } from '../../../helpers/l10n';
 import { getCurrentL10nBundle } from '../../../helpers/l10nBundle';
 import { getBaseUrl } from '../../../helpers/system';
@@ -127,6 +126,7 @@ class Extension extends React.PureComponent<ExtensionProps, State> {
     return (
       <div>
         <Helmet title={this.props.extension.name} />
+
         {this.state.extensionElement ? (
           this.state.extensionElement
         ) : (
index a023f43de8ed891a53607d8dc6720ddb4ef995f6..eaf06a5ab205e4b8b744a4514c85ceb09c7e1528 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+
 import { screen, waitFor } from '@testing-library/react';
 import userEvent from '@testing-library/user-event';
-import { lightTheme } from 'design-system';
+import { addGlobalErrorMessage, lightTheme } from 'design-system';
 import * as React from 'react';
 import { IntlShape } from 'react-intl';
 import { getEnhancedWindow } from '../../../../helpers/browser';
 import { installExtensionsHandler } from '../../../../helpers/extensionsHandler';
-import { addGlobalErrorMessage } from '../../../../helpers/globalMessages';
 import {
   mockAppState,
   mockCurrentUser,
@@ -35,7 +35,10 @@ import { renderComponent } from '../../../../helpers/testReactTestingUtils';
 import { ExtensionStartMethodParameter } from '../../../../types/extension';
 import Extension, { ExtensionProps } from '../Extension';
 
-jest.mock('../../../../helpers/globalMessages');
+jest.mock('design-system', () => ({
+  ...jest.requireActual('design-system'),
+  addGlobalErrorMessage: jest.fn(),
+}));
 
 beforeAll(() => {
   installExtensionsHandler();
index 01601e814540356e7e5f00142c60d2ce4c87ca97..366ab15e7d086b077fdffde4f31d7826261b9c00 100644 (file)
@@ -17,8 +17,9 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+
+import { addGlobalSuccessMessage } from 'design-system';
 import { throwGlobalError } from '../../../helpers/error';
-import { addGlobalSuccessMessage } from '../../../helpers/globalMessages';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
 import {
   get,
@@ -48,6 +49,7 @@ const exposeLibraries = () => {
     throwGlobalError,
     addGlobalSuccessMessage,
   };
+
   global.t = translate;
   global.tp = translateWithParameters;
 };
index 0aebad7f66c20a83e21fdd654823a0c682c05130..64f1e6ec0af50c2629b8a021b4aa1a2cfce7ff42 100644 (file)
@@ -17,7 +17,8 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { ButtonSecondary, FlagMessage } from 'design-system/lib';
+
+import { ButtonSecondary, FlagMessage } from 'design-system';
 import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
 import { cancelPendingPlugins } from '../../../../api/plugins';
index 9f8575a64ef8e963d1470c4f6d151e076c167dbd..14599c2d5331c5b704ddc319d6a4d4ad57ad3f22 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+
 /* NOTE: esbuild will transpile the _syntax_ down to what the TARGET_BROWSERS (in config/utils) */
 /* understand. It will _not_, however, polyfill missing API methods, such as                    */
 /* String.prototype.replaceAll. This is why we also import core-js.                             */
 import 'core-js/stable';
 /*                                                                                              */
 import axios from 'axios';
+import { addGlobalErrorMessage } from 'design-system';
 import 'react-day-picker/dist/style.css';
 import { getAvailableFeatures } from '../api/features';
 import { getGlobalNavigation } from '../api/navigation';
 import { getCurrentUser } from '../api/users';
 import { installExtensionsHandler, installWebAnalyticsHandler } from '../helpers/extensionsHandler';
-import { addGlobalErrorMessage } from '../helpers/globalMessages';
 import { loadL10nBundle } from '../helpers/l10nBundle';
 import { axiosToCatch, parseErrorResponse } from '../helpers/request';
 import { getBaseUrl, getSystemStatus, initAppVariables } from '../helpers/system';
@@ -45,14 +46,17 @@ async function initApplication() {
   axiosToCatch.defaults.headers.patch['Content-Type'] = 'application/merge-patch+json';
   axios.defaults.headers.patch['Content-Type'] = 'application/merge-patch+json';
   axios.defaults.baseURL = getBaseUrl();
+
   axios.interceptors.response.use(
     (response) => response.data,
     (error) => {
       const { response } = error;
       addGlobalErrorMessage(parseErrorResponse(response));
+
       return Promise.reject(response);
     },
   );
+
   const [l10nBundle, currentUser, appState, availableFeatures] = await Promise.all([
     loadL10nBundle(),
     isMainApp() ? getCurrentUser() : undefined,
@@ -70,6 +74,7 @@ async function initApplication() {
 
 function isMainApp() {
   const { pathname } = window.location;
+
   return (
     getSystemStatus() === 'UP' &&
     !pathname.startsWith(`${getBaseUrl()}/sessions`) &&
index 8db4ac406d685cc0e60983e4a4933cb3dc2ff5b4..46fb9294f489b4b20abe30dcf86bb808f8c4bf20 100644 (file)
@@ -19,7 +19,7 @@
  */
 
 import { Global, css, useTheme } from '@emotion/react';
-import { themeColor } from 'design-system/lib';
+import { themeColor } from 'design-system';
 import React from 'react';
 import twDefaultTheme from 'tailwindcss/defaultTheme';
 
index 553ec297a82dd954b754bd68f601c0f03f2c1816..e7ccd54642f4ab079503a9ba2f12bec3f3b30d7a 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+
 import { ThemeProvider } from '@emotion/react';
 import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
-import { lightTheme } from 'design-system';
+import { ToastMessageContainer, lightTheme } from 'design-system';
 import * as React from 'react';
 import { createRoot } from 'react-dom/client';
 import { Helmet, HelmetProvider } from 'react-helmet-async';
@@ -78,7 +79,6 @@ import ComponentContainer from '../components/ComponentContainer';
 import DocumentationRedirect from '../components/DocumentationRedirect';
 import FormattingHelp from '../components/FormattingHelp';
 import GlobalContainer from '../components/GlobalContainer';
-import GlobalMessagesContainer from '../components/GlobalMessagesContainer';
 import Landing from '../components/Landing';
 import MigrationContainer from '../components/MigrationContainer';
 import NonAdminPagesContainer from '../components/NonAdminPagesContainer';
@@ -274,7 +274,7 @@ export default function startReactApp(
               <ThemeProvider theme={lightTheme}>
                 <QueryClientProvider client={queryClient}>
                   <GlobalStyles />
-                  <GlobalMessagesContainer />
+                  <ToastMessageContainer />
                   <Helmet titleTemplate={translate('page_title.template.default')} />
                   <RouterProvider router={router} />
                 </QueryClientProvider>
index 1ed64994ced42f1b2fbeae6d2a76146bda03650e..d59dad97e29691323b36344daa805f4a50e3fdf8 100644 (file)
@@ -17,8 +17,9 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+
 import { endOfDay, startOfDay, subDays } from 'date-fns';
-import { ButtonPrimary } from 'design-system/lib';
+import { ButtonPrimary } from 'design-system';
 import * as React from 'react';
 import { now } from '../../../helpers/dates';
 import { translate } from '../../../helpers/l10n';
index 3ca52926956e86fe702f82c11e84fad924b74b7f..be4b3fa74dd2d1cb5848afa5c8250f29f4f874e6 100644 (file)
@@ -17,7 +17,8 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { NumericalCell } from 'design-system/lib';
+
+import { NumericalCell } from 'design-system';
 import * as React from 'react';
 import { formatDuration } from '../utils';
 
index 8b51fba1e0518bd92340e4a7d2a63c19096b8082..b2d68fc6ca90405800e54dd87b89e8adfd931f5d 100644 (file)
@@ -17,7 +17,8 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { FlagMessage } from 'design-system/lib';
+
+import { FlagMessage } from 'design-system';
 import * as React from 'react';
 import ConfirmModal from '../../../../components/controls/ConfirmModal';
 import { translate, translateWithParameters } from '../../../../helpers/l10n';
index 1519ad79778bbc07c10baee1eedd1e5176026292..d104562c7024716b6b5e08249465c6004f5b7f59 100644 (file)
@@ -17,7 +17,8 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { CenteredLayout, PageContentFontWrapper } from 'design-system/lib';
+
+import { CenteredLayout, PageContentFontWrapper } from 'design-system';
 import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
 import { ComponentContext } from '../../app/components/componentContext/ComponentContext';
index df1be57a521279bc8a44729d0b4e3353afdf7521..ee777e8da5d2e14ebd9180ba765779a832698204 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { DangerButtonPrimary } from 'design-system/lib';
+
+import { DangerButtonPrimary, addGlobalSuccessMessage } from 'design-system';
 import * as React from 'react';
 import { deleteApplication } from '../../api/application';
 import { deletePortfolio, deleteProject } from '../../api/project-management';
 import ConfirmButton from '../../components/controls/ConfirmButton';
 import { Router, withRouter } from '../../components/hoc/withRouter';
-import { addGlobalSuccessMessage } from '../../helpers/globalMessages';
 import { translate, translateWithParameters } from '../../helpers/l10n';
 import { isApplication, isPortfolioLike } from '../../types/component';
 import { Component } from '../../types/types';
@@ -38,6 +38,7 @@ export class Form extends React.PureComponent<Props> {
     const { component } = this.props;
     let deleteMethod = deleteProject;
     let redirectTo = '/';
+
     if (isPortfolioLike(component.qualifier)) {
       deleteMethod = deletePortfolio;
       redirectTo = '/portfolios';
@@ -50,6 +51,7 @@ export class Form extends React.PureComponent<Props> {
     addGlobalSuccessMessage(
       translateWithParameters('project_deletion.resource_deleted', component.name),
     );
+
     this.props.router.replace(redirectTo);
   };
 
index bc1e8936ad405b72ce6bcc5686acab8d48fc8326..1d3189f1b094228b18b95f286bc28f6907fb2d7f 100644 (file)
@@ -17,7 +17,8 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { SubHeading } from 'design-system/lib';
+
+import { SubHeading } from 'design-system';
 import React from 'react';
 import MetaLink from '../../../../components/common/MetaLink';
 import { translate } from '../../../../helpers/l10n';
index 4fc40ff5682ba5abea7dbaee8dffd24ac89075d8..dadd712217ef601111c63bdab7c7ce089d118569 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+
+import { waitFor } from '@testing-library/react';
 import userEvent from '@testing-library/user-event';
-import { addGlobalSuccessMessage } from 'design-system';
+import { addGlobalErrorMessage, addGlobalSuccessMessage } from 'design-system';
 import selectEvent from 'react-select-event';
 import { QualityGatesServiceMock } from '../../../api/mocks/QualityGatesServiceMock';
 import handleRequiredAuthorization from '../../../app/utils/handleRequiredAuthorization';
@@ -56,7 +58,6 @@ const ui = {
 
   saveButton: byRole('button', { name: 'save' }),
   noConditionsNewCodeWarning: byText('project_quality_gate.no_condition_on_new_code'),
-  alertMessage: byText('unknown'),
 };
 
 beforeAll(() => {
@@ -109,7 +110,10 @@ it('renders nothing and shows alert when any API fails', async () => {
   handler.setThrowOnGetGateForProject(true);
   renderProjectQualityGateApp();
 
-  expect(await ui.alertMessage.find()).toBeInTheDocument();
+  await waitFor(() => {
+    expect(addGlobalErrorMessage).toHaveBeenCalledWith('unknown');
+  });
+
   expect(ui.qualityGateHeading.query()).not.toBeInTheDocument();
 });
 
index bf45c9e1962025172c655b11271f071269f4d5ea..cc84a71c1274e16cc2f337e014d7d2a81ea8f6e6 100644 (file)
@@ -17,6 +17,7 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+
 import {
   ButtonPrimary,
   FlagMessage,
@@ -70,6 +71,7 @@ export default class BulkApplyTemplateModal extends React.PureComponent<Props, S
 
   loadPermissionTemplates() {
     this.setState({ loading: true });
+
     getPermissionTemplates().then(
       ({ permissionTemplates }) => {
         if (this.mounted) {
@@ -93,9 +95,11 @@ export default class BulkApplyTemplateModal extends React.PureComponent<Props, S
     event.preventDefault();
     const { analyzedBefore } = this.props;
     const { permissionTemplate } = this.state;
+
     if (permissionTemplate) {
       this.setState({ submitting: true });
       const selection = this.props.selection.filter((s) => !s.managed);
+
       const parameters = selection.length
         ? {
             projects: selection.map((s) => s.key).join(),
@@ -109,6 +113,7 @@ export default class BulkApplyTemplateModal extends React.PureComponent<Props, S
             q: this.props.query || undefined,
             templateId: permissionTemplate,
           };
+
       bulkApplyTemplate(parameters).then(
         () => {
           if (this.mounted) {
@@ -160,6 +165,7 @@ export default class BulkApplyTemplateModal extends React.PureComponent<Props, S
         </FlagMessage>
       );
     }
+
     return (
       <FlagMessage variant="warning" className="sw-my-2">
         {translateWithParameters(
@@ -180,6 +186,7 @@ export default class BulkApplyTemplateModal extends React.PureComponent<Props, S
       this.state.permissionTemplates !== undefined
         ? this.state.permissionTemplates.map((t) => ({ label: t.name, value: t.id }))
         : [];
+
     return (
       <FormField htmlFor="bulk-apply-template-input" label={translate('template')} required>
         <InputSelect
@@ -219,6 +226,7 @@ export default class BulkApplyTemplateModal extends React.PureComponent<Props, S
         )}
       </form>
     );
+
     return (
       <Modal
         isScrollable={false}
index bc37aba48fb49a6fc85408c0a60285a704d03d23..cf7bdcea560bfcd9f0efd14973b7ce77ce1f208c 100644 (file)
@@ -17,7 +17,8 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { ButtonPrimary, FormField, InputField, Modal } from 'design-system/lib';
+
+import { ButtonPrimary, FormField, InputField, Modal } from 'design-system';
 import * as React from 'react';
 import { useRouter } from '../../../components/hoc/withRouter';
 import MandatoryFieldsExplanation from '../../../components/ui/MandatoryFieldsExplanation';
index 18bd0d2432aafe52e11df029f33904878def043e..d25e5890ff9cdb6a972c9cedb8f1bdf770fd42d4 100644 (file)
@@ -199,7 +199,7 @@ describe('CRUD', () => {
     await user.click(ui.activeAssignee.get());
     await user.click(ui.currentUserSelectionItem.get());
 
-    expect(ui.successGlobalMessage.get()).toHaveTextContent(`hotspots.assign.success.foo`);
+    expect(await ui.successGlobalMessage.find()).toHaveTextContent(`hotspots.assign.success.foo`);
     expect(ui.activeAssignee.get()).toHaveTextContent('foo');
   });
 
@@ -214,7 +214,9 @@ describe('CRUD', () => {
 
     expect(getUsers).toHaveBeenLastCalledWith({ q: 'User' });
     await user.keyboard('{Enter}');
-    expect(ui.successGlobalMessage.get()).toHaveTextContent(`hotspots.assign.success.User John`);
+    expect(await ui.successGlobalMessage.find()).toHaveTextContent(
+      `hotspots.assign.success.User John`,
+    );
   });
 
   it('should be able to change the status of a hotspot', async () => {
index f0f6d1a80602491bae1d669ceb52d317fed48297..0b1ea976370284121d2ec3d9018808e1b0cb0774 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { LabelValueSelectOption, SearchSelectDropdown } from 'design-system';
+
+import {
+  LabelValueSelectOption,
+  SearchSelectDropdown,
+  addGlobalSuccessMessage,
+} from 'design-system';
 import { noop } from 'lodash';
 import * as React from 'react';
 import { Options, SingleValue } from 'react-select';
@@ -25,7 +30,6 @@ import { assignSecurityHotspot } from '../../../api/security-hotspots';
 import { getUsers } from '../../../api/users';
 import { CurrentUserContext } from '../../../app/components/current-user/CurrentUserContext';
 import Avatar from '../../../components/ui/Avatar';
-import { addGlobalSuccessMessage } from '../../../helpers/globalMessages';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
 import { Hotspot, HotspotResolution, HotspotStatus } from '../../../types/security-hotspots';
 import { RestUser, isLoggedIn, isUserActive } from '../../../types/users';
@@ -47,6 +51,7 @@ export default function Assignee(props: Props) {
   const {
     hotspot: { assigneeUser, status, resolution, key },
   } = props;
+
   const { currentUser } = React.useContext(CurrentUserContext);
 
   const allowCurrentUserSelection =
@@ -88,6 +93,7 @@ export default function Assignee(props: Props) {
             value: u.login,
             Icon: renderAvatar(u.name, u.avatar),
           }));
+
         cb(options);
       })
       .catch(() => {
@@ -102,6 +108,7 @@ export default function Assignee(props: Props) {
       })
         .then(() => {
           props.onAssigneeChange();
+
           addGlobalSuccessMessage(
             userOption.value
               ? translateWithParameters('hotspots.assign.success', userOption.label)
index acd81abda1f5719fc79b69294d6a3924884e937c..6b067444615ec94d402fbf1a49ba4526329089b7 100644 (file)
@@ -17,6 +17,7 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+
 import {
   ButtonSecondary,
   DropdownMenu,
@@ -25,9 +26,10 @@ import {
   PopupPlacement,
   PopupZLevel,
   Spinner,
+  addGlobalErrorMessage,
+  addGlobalSuccessMessage,
 } from 'design-system';
 import * as React from 'react';
-import { addGlobalErrorMessage, addGlobalSuccessMessage } from '../../../helpers/globalMessages';
 import { translate } from '../../../helpers/l10n';
 import { openHotspot, probeSonarLintServers } from '../../../helpers/sonarlint';
 import { Ide } from '../../../types/sonarlint';
@@ -61,6 +63,7 @@ export default class HotspotOpenInIdeButton extends React.PureComponent<Props, S
   handleOnClick = async () => {
     this.setState({ loading: true, ides: [] });
     const ides = await probeSonarLintServers();
+
     if (ides.length === 0) {
       if (this.mounted) {
         this.setState({ loading: false });
@@ -76,6 +79,7 @@ export default class HotspotOpenInIdeButton extends React.PureComponent<Props, S
   openHotspot = (ide: Ide) => {
     this.setState({ loading: true, ides: [] as Ide[] });
     const { projectKey, hotspotKey } = this.props;
+
     return openHotspot(ide.port, projectKey, hotspotKey)
       .then(this.showSuccess)
       .catch(this.showError)
@@ -94,6 +98,7 @@ export default class HotspotOpenInIdeButton extends React.PureComponent<Props, S
 
   render() {
     const { ides, loading } = this.state;
+
     return (
       <div>
         <DropdownToggler
@@ -107,6 +112,7 @@ export default class HotspotOpenInIdeButton extends React.PureComponent<Props, S
               {ides.map((ide) => {
                 const { ideName, description } = ide;
                 const label = ideName + (description ? ` - ${description}` : '');
+
                 return (
                   <ItemButton
                     key={ide.port}
index aed2b8a8520135fce079de0f47670618edb8a35c..ffa535c4ab32b8deda73e3a996a12e9915c9e51b 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+
+import { addGlobalErrorMessage } from 'design-system';
 import * as React from 'react';
 import { logIn } from '../../../api/auth';
 import { getLoginMessage } from '../../../api/settings';
 import { getIdentityProviders } from '../../../api/users';
 import { Location, withRouter } from '../../../components/hoc/withRouter';
-import { addGlobalErrorMessage } from '../../../helpers/globalMessages';
 import { translate } from '../../../helpers/l10n';
 import { getReturnUrl } from '../../../helpers/urls';
 import { IdentityProvider } from '../../../types/types';
@@ -75,6 +76,7 @@ export class LoginContainer extends React.PureComponent<Props, State> {
   async loadLoginMessage() {
     try {
       const { message } = await getLoginMessage();
+
       if (this.mounted) {
         this.setState({ message });
       }
index 92f0943b897af211cf03f5164002384d88acddeb..3f7a91b5e80703e96da6643afeacc3461b893c38 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { CenteredLayout, PageContentFontWrapper } from 'design-system';
+
+import { CenteredLayout, PageContentFontWrapper, addGlobalErrorMessage } from 'design-system';
 import * as React from 'react';
 import { logOut } from '../../../api/auth';
 import RecentHistory from '../../../app/components/RecentHistory';
-import { addGlobalErrorMessage } from '../../../helpers/globalMessages';
 import { translate } from '../../../helpers/l10n';
 import { getBaseUrl } from '../../../helpers/system';
 
index d107860992c1473ef4e98c388250a236742628de..2415c3ddfa84ffa4f2ef9b7bd22bde05c3104e88 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+
 import { screen, waitFor } from '@testing-library/react';
 import userEvent from '@testing-library/user-event';
+import { addGlobalErrorMessage } from 'design-system';
 import * as React from 'react';
 import { getLoginMessage } from '../../../../api/settings';
 import { getIdentityProviders } from '../../../../api/users';
-import { addGlobalErrorMessage } from '../../../../helpers/globalMessages';
 import { mockLocation } from '../../../../helpers/testMocks';
 import { renderComponent } from '../../../../helpers/testReactTestingUtils';
 import { byLabelText, byRole } from '../../../../helpers/testSelector';
@@ -45,7 +46,8 @@ jest.mock('../../../../api/settings', () => ({
   getLoginMessage: jest.fn().mockResolvedValue({ message: '' }),
 }));
 
-jest.mock('../../../../helpers/globalMessages', () => ({
+jest.mock('design-system', () => ({
+  ...jest.requireActual('design-system'),
   addGlobalErrorMessage: jest.fn(),
 }));
 
index 7e9d559f14f266801ea7d16a8ad7c539880fa53b..117879ba8675e0d49844e4e4ec5fb1a1471cd10f 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+
 import { screen, waitFor } from '@testing-library/react';
+import { addGlobalErrorMessage } from 'design-system';
 import * as React from 'react';
 import { logOut } from '../../../../api/auth';
 import RecentHistory from '../../../../app/components/RecentHistory';
-import { addGlobalErrorMessage } from '../../../../helpers/globalMessages';
 import { renderComponent } from '../../../../helpers/testReactTestingUtils';
 import Logout from '../Logout';
 
@@ -29,7 +30,8 @@ jest.mock('../../../../api/auth', () => ({
   logOut: jest.fn().mockResolvedValue(true),
 }));
 
-jest.mock('../../../../helpers/globalMessages', () => ({
+jest.mock('design-system', () => ({
+  ...jest.requireActual('design-system'),
   addGlobalErrorMessage: jest.fn(),
 }));
 
index 498619d4d6e222bbb3c5f875b342284fe88ddd45..47fe27f2bf1ca639944c5eb0b7a01d39f3a07b70 100644 (file)
@@ -17,8 +17,9 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+
 import styled from '@emotion/styled';
-import { LightLabel } from 'design-system/lib';
+import { LightLabel } from 'design-system';
 import * as React from 'react';
 import DocumentationLink from '../../../components/common/DocumentationLink';
 import { translate } from '../../../helpers/l10n';
index 3ad4304f129630b5360eedfdabeadf216decf0e5..e9a2060a8259c6ed6efbceed6f406880b0452850 100644 (file)
@@ -17,8 +17,9 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+
 import styled from '@emotion/styled';
-import { themeBorder } from 'design-system/lib';
+import { themeBorder } from 'design-system';
 import * as React from 'react';
 import { SettingDefinitionAndValue } from '../../../types/settings';
 import { Component } from '../../../types/types';
index d6d51556af12bbb63bd48600a330a45cbfbaad5a..de9029a024172bca9192405655c6f21ee22f2eb0 100644 (file)
@@ -17,7 +17,8 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { InputTextArea } from 'design-system/lib';
+
+import { InputTextArea } from 'design-system';
 import * as React from 'react';
 import FormattingTipsWithLink from '../../../../components/common/FormattingTipsWithLink';
 import { Button } from '../../../../components/controls/buttons';
index b2cfb8c0179ae8b13f311a4ce4b1ae072fbac2ab..d4cadbe0665cb324f8ae76f39dfee4d23381e3dc 100644 (file)
@@ -17,7 +17,8 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { ButtonPrimary, FlagMessage, InputTextArea } from 'design-system/lib';
+
+import { ButtonPrimary, FlagMessage, InputTextArea } from 'design-system';
 import * as React from 'react';
 import { translate } from '../../../../helpers/l10n';
 import { DefaultSpecializedInputProps, getPropertyName } from '../../utils';
index bb7ffc683188caa7562914d36a00aa6e43ce399a..bbece8dbdb0e3701a762f7356522d6413c180e3f 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { ButtonPrimary, FlagMessage, FormField, InputField, Modal } from 'design-system';
+
+import {
+  ButtonPrimary,
+  FlagMessage,
+  FormField,
+  InputField,
+  Modal,
+  addGlobalSuccessMessage,
+} from 'design-system';
 import * as React from 'react';
 import { changePassword } from '../../../api/users';
 import { CurrentUserContext } from '../../../app/components/current-user/CurrentUserContext';
-import { addGlobalSuccessMessage } from '../../../helpers/globalMessages';
 import { translate } from '../../../helpers/l10n';
 import { ChangePasswordResults, RestUserDetailed, isLoggedIn } from '../../../types/users';
 
@@ -35,9 +42,11 @@ const PASSWORD_FORM_ID = 'user-password-form';
 export default function PasswordForm(props: Props) {
   const { user } = props;
   const [confirmPassword, setConfirmPassword] = React.useState('');
+
   const [errorTranslationKey, setErrorTranslationKey] = React.useState<string | undefined>(
     undefined,
   );
+
   const [newPassword, setNewPassword] = React.useState('');
   const [oldPassword, setOldPassword] = React.useState('');
   const [submitting, setSubmitting] = React.useState(false);
@@ -58,8 +67,10 @@ export default function PasswordForm(props: Props) {
 
   const handleChangePassword = (event: React.SyntheticEvent<HTMLFormElement>) => {
     event.preventDefault();
+
     if (newPassword.length > 0 && newPassword === confirmPassword) {
       setSubmitting(true);
+
       changePassword({
         login: user.login,
         password: newPassword,
@@ -108,6 +119,7 @@ export default function PasswordForm(props: Props) {
               <input className="sw-hidden" aria-hidden name="old-password-fake" type="password" />
             </FormField>
           )}
+
           <FormField htmlFor="user-password" label={translate('my_profile.password.new')} required>
             <InputField
               autoFocus
@@ -121,6 +133,7 @@ export default function PasswordForm(props: Props) {
             />
             <input className="sw-hidden" aria-hidden name="password-fake" type="password" />
           </FormField>
+
           <FormField
             htmlFor="confirm-user-password"
             label={translate('my_profile.password.confirm')}
index 8e572e5353ed2d7e109035ec450af5d34e5df068..8fd2f938a4609de54ef7145acc921d4520d245b4 100644 (file)
@@ -17,6 +17,7 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+
 import { AxiosError, AxiosResponse } from 'axios';
 import {
   ButtonPrimary,
@@ -26,10 +27,10 @@ import {
   InputField,
   Modal,
   Spinner,
+  addGlobalErrorMessage,
 } from 'design-system';
 import * as React from 'react';
 import MandatoryFieldsExplanation from '../../../components/ui/MandatoryFieldsExplanation';
-import { addGlobalErrorMessage } from '../../../helpers/globalMessages';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
 import { parseErrorResponse } from '../../../helpers/request';
 import { usePostUserMutation, useUpdateUserMutation } from '../../../queries/users';
@@ -81,6 +82,7 @@ export default function UserForm(props: Props) {
 
   const handleCreateUser = (e: React.SyntheticEvent<HTMLFormElement>) => {
     e.preventDefault();
+
     createUser(
       {
         email: email || undefined,
@@ -96,6 +98,7 @@ export default function UserForm(props: Props) {
   const handleUpdateUser = (e: React.SyntheticEvent<HTMLFormElement>) => {
     e.preventDefault();
     const { user } = props;
+
     updateUser(
       {
         id: user?.id!,
@@ -151,6 +154,7 @@ export default function UserForm(props: Props) {
               {translate('users.cannot_update_delegated_user')}
             </FlagMessage>
           )}
+
           <div className="sw-mb-4">
             <MandatoryFieldsExplanation />
           </div>
@@ -223,6 +227,7 @@ export default function UserForm(props: Props) {
               />
             </FormField>
           )}
+
           <FormField
             description={translate('user.login_or_email_used_as_scm_account')}
             label={translate('my_profile.scm_accounts')}
@@ -236,6 +241,7 @@ export default function UserForm(props: Props) {
                 scmAccount={scm}
               />
             ))}
+
             <div>
               <ButtonSecondary className="it__scm-account-add" onClick={handleAddScmAccount}>
                 {translate('add_verb')}
@@ -247,6 +253,7 @@ export default function UserForm(props: Props) {
       primaryButton={
         <>
           <Spinner loading={isLoadingCreate || isLoadingUserUpdate} />
+
           <ButtonPrimary
             disabled={isLoadingCreate || isLoadingUserUpdate}
             type="submit"
index 5fd3ce27aa957d081d9c3d4c5d10852729f55d4c..57b6c4cc83d163c34e9fc607821655364e5acbfa 100644 (file)
@@ -17,7 +17,8 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { ButtonPrimary } from 'design-system/lib';
+
+import { ButtonPrimary } from 'design-system';
 import * as React from 'react';
 import { useState } from 'react';
 import Tooltip from '../../../components/controls/Tooltip';
index ae22f69629dd8ff6e65fd7a09ce017233fb656a8..d37748db9a61f5754a7d4b09c904d1ca0145684d 100644 (file)
@@ -17,7 +17,8 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { FlagMessage } from 'design-system/lib';
+
+import { FlagMessage } from 'design-system';
 import { intersection } from 'lodash';
 import * as React from 'react';
 import {
index 2ce86b2db296f57aeeaac53a4dfa904b53c7eaff..df29387ebd5c6f39b34929964ddd0c8b07d4196e 100644 (file)
@@ -17,7 +17,8 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { DangerButtonSecondary } from 'design-system/lib';
+
+import { DangerButtonSecondary } from 'design-system';
 import * as React from 'react';
 import { restart } from '../../api/system';
 import ConfirmButton from '../../components/controls/ConfirmButton';
index 39335ff17d6fcf19ea425019ef7c2dc448c46c67..f96fcca20d8ad09a6d6d32e429fd59954664004f 100644 (file)
@@ -17,6 +17,8 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+
+import { addGlobalSuccessMessage } from 'design-system';
 import * as React from 'react';
 import {
   getReportStatus,
@@ -25,7 +27,6 @@ import {
 } from '../../api/component-report';
 import withAppStateContext from '../../app/components/app-state/withAppStateContext';
 import withCurrentUserContext from '../../app/components/current-user/withCurrentUserContext';
-import { addGlobalSuccessMessage } from '../../helpers/globalMessages';
 import { translate, translateWithParameters } from '../../helpers/l10n';
 import { AppState } from '../../types/appstate';
 import { Branch } from '../../types/branch-like';
@@ -54,6 +55,7 @@ export class ComponentReportActions extends React.PureComponent<Props, State> {
   componentDidMount() {
     this.mounted = true;
     const governanceEnabled = this.props.appState.qualifiers.includes(ComponentQualifier.Portfolio);
+
     if (governanceEnabled) {
       this.loadReportStatus();
     }
@@ -80,10 +82,12 @@ export class ComponentReportActions extends React.PureComponent<Props, State> {
     const translationKey = subscribed
       ? 'component_report.subscribe_x_success'
       : 'component_report.unsubscribe_x_success';
+
     const frequencyTranslation = translate(
       'report.frequency',
       status?.componentFrequency ?? status?.globalFrequency ?? '',
     ).toLowerCase();
+
     const qualifierTranslation = translate('qualifier', component.qualifier).toLowerCase();
 
     addGlobalSuccessMessage(
index b056fc863155dab855e8f8f2a020af732be99cb8..bce01b501c0500f1dc0367ad47df452c3b30abfc 100644 (file)
@@ -19,6 +19,7 @@
  */
 import { screen, waitFor } from '@testing-library/react';
 import userEvent from '@testing-library/user-event';
+import { addGlobalSuccessMessage } from 'design-system';
 import * as React from 'react';
 import {
   getReportStatus,
@@ -33,6 +34,11 @@ import { renderApp } from '../../../helpers/testReactTestingUtils';
 import { ComponentQualifier } from '../../../types/component';
 import { ComponentReportActions } from '../ComponentReportActions';
 
+jest.mock('design-system', () => ({
+  ...jest.requireActual('design-system'),
+  addGlobalSuccessMessage: jest.fn(),
+}));
+
 jest.mock('../../../api/component-report', () => ({
   ...jest.requireActual('../../../api/component-report'),
   getReportStatus: jest
@@ -118,7 +124,9 @@ it('should allow user to (un)subscribe', async () => {
   await user.click(subscribeButton);
 
   expect(subscribeToEmailReport).toHaveBeenCalledWith(component.key, branch.name);
-  expect(await screen.findByRole('status')).toBeInTheDocument();
+  expect(addGlobalSuccessMessage).toHaveBeenLastCalledWith(
+    'component_report.subscribe_x_success.report.frequency.monthly.qualifier.trk',
+  );
 
   // And unsubscribe!
   await user.click(button);
@@ -131,8 +139,8 @@ it('should allow user to (un)subscribe', async () => {
   await user.click(unsubscribeButton);
 
   expect(unsubscribeFromEmailReport).toHaveBeenCalledWith(component.key, branch.name);
-  expect(screen.getByRole('status')).toHaveTextContent(
-    'component_report.subscribe_x_s...component_report.unsubscribe_x...',
+  expect(addGlobalSuccessMessage).toHaveBeenLastCalledWith(
+    'component_report.unsubscribe_x_success.report.frequency.monthly.qualifier.trk',
   );
 });
 
index 4cb7063e1f98e7742534df32a86079541cf9dd00..82df19582b8049efefee86e1f0cde78351a754cf 100644 (file)
@@ -77,7 +77,7 @@ describe('Global NCD update notification banner', () => {
 
   it('renders no global banner if user is not global admin', () => {
     const { container } = renderGlobalMessage(mockLoggedInUser());
-    expect(container).toBeEmptyDOMElement();
+    expect(container).toContainHTML('<div><div class="Toastify" /></div>');
   });
 
   it('renders global banner if user is global admin', async () => {
@@ -170,7 +170,7 @@ describe('Project NCD update notification banner', () => {
     const { container } = renderProjectMessage(
       mockComponent({ configuration: { showSettings: false } }),
     );
-    expect(container).toBeEmptyDOMElement();
+    expect(container).toContainHTML('<div><div class="Toastify" /></div>');
   });
 
   it('renders project banner if user is project admin', async () => {
index a09cfb72e06a43e79b8f330c74ee25c81ceeb23a..df697fd690d421cd2269eb8eb692d6825d50e362 100644 (file)
@@ -17,7 +17,8 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { ClipboardIconButton, CodeSnippet, NumberedListItem } from 'design-system/lib';
+
+import { ClipboardIconButton, CodeSnippet, NumberedListItem } from 'design-system';
 import React from 'react';
 import { FormattedMessage } from 'react-intl';
 import { translate } from '../../../helpers/l10n';
index 04bdad7c62b0ffd8abc51d84583931ed7cf95b47..e99fdd77ac709ae0fd669f51daeb288dc2b0153d 100644 (file)
@@ -17,7 +17,8 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { NumberedListItem } from 'design-system/lib';
+
+import { NumberedListItem } from 'design-system';
 import * as React from 'react';
 import { translate } from '../../../../helpers/l10n';
 import { CompilationInfo } from '../../components/CompilationInfo';
index 9a77857159d8b28c2f3ac32581d5754c55d2334e..3e34cb6a2e894060dc133517a03b9daa21ced4ae 100644 (file)
@@ -17,7 +17,8 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { NumberedListItem } from 'design-system/lib';
+
+import { NumberedListItem } from 'design-system';
 import * as React from 'react';
 import { translate } from '../../../../helpers/l10n';
 import { Component } from '../../../../types/types';
index 70a0dcfe57ae7c9de564b1bbb94b76a0cc179ff6..4349810a5fd03fa24444d6ad9c790e071e58950c 100644 (file)
@@ -17,6 +17,7 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+
 import { keyframes } from '@emotion/react';
 import styled from '@emotion/styled';
 import {
@@ -34,9 +35,9 @@ import {
   Note,
   Spinner,
   ToggleButton,
+  ToggleButtonsOption,
   TrashIcon,
 } from 'design-system';
-import { ToggleButtonsOption } from 'design-system/lib/components/ToggleButton';
 import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
 import { SingleValue } from 'react-select';
index 9fd8e5ff4ffd069b482343f2b765c94d59329d6f..495b93d388001ceeba3bd7ae9dc37716c427867a 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+
+import { addGlobalErrorMessage } from 'design-system';
 import { throwGlobalError } from '../error';
-import { addGlobalErrorMessage } from '../globalMessages';
 
-jest.mock('../../helpers/globalMessages', () => ({
+jest.mock('design-system', () => ({
   addGlobalErrorMessage: jest.fn(),
 }));
 
diff --git a/server/sonar-web/src/main/js/helpers/__tests__/globalMessages-test.ts b/server/sonar-web/src/main/js/helpers/__tests__/globalMessages-test.ts
deleted file mode 100644 (file)
index c96d313..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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 { MessageLevel } from '../../types/globalMessages';
-import {
-  addGlobalErrorMessage,
-  addGlobalSuccessMessage,
-  registerListener,
-} from '../globalMessages';
-
-it('should work as expected', () => {
-  const listener1 = jest.fn();
-  registerListener(listener1);
-
-  addGlobalErrorMessage('test');
-
-  expect(listener1).toHaveBeenCalledWith(
-    expect.objectContaining({ text: 'test', level: MessageLevel.Error }),
-  );
-
-  listener1.mockClear();
-  const listener2 = jest.fn();
-  registerListener(listener2);
-
-  addGlobalSuccessMessage('test');
-
-  expect(listener1).toHaveBeenCalledWith(
-    expect.objectContaining({ text: 'test', level: MessageLevel.Success }),
-  );
-  expect(listener2).toHaveBeenCalledWith(
-    expect.objectContaining({ text: 'test', level: MessageLevel.Success }),
-  );
-});
index f97189d19e7e6f8495280f635591dd4bfd24847c..96199726f3596fc5801609462cb521fb3361d1fa 100644 (file)
@@ -17,7 +17,8 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { addGlobalErrorMessage } from './globalMessages';
+
+import { addGlobalErrorMessage } from 'design-system';
 import { parseError } from './request';
 
 export function throwGlobalError(param: Response | any): Promise<Response | any> {
@@ -38,6 +39,7 @@ export function throwGlobalError(param: Response | any): Promise<Response | any>
   // Axios response object
   if (param.data?.message) {
     addGlobalErrorMessage(param.data?.message);
+
     return Promise.reject(param);
   }
 
index 16da32acaac8a778f0a3cee35e7dda4daa0838ff..691fa79f042aeaf4c8137f0c18cbd3a45db347c4 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { uniqueId } from 'lodash';
-import { Message, MessageLevel } from '../types/globalMessages';
-import { parseError } from './request';
-
-const listeners: Array<(message: Message) => void> = [];
-
-export function registerListener(callback: (message: Message) => void) {
-  listeners.push(callback);
-}
-
-export function unregisterListener(callback: (message: Message) => void) {
-  const index = listeners.indexOf(callback);
-
-  if (index > -1) {
-    listeners.splice(index, 1);
-  }
-}
 
-function addMessage(text: string, level: MessageLevel) {
-  listeners.forEach((listener) =>
-    listener({
-      id: uniqueId('global-message-'),
-      level,
-      text,
-    }),
-  );
-}
-
-export function addGlobalErrorMessage(text: string) {
-  addMessage(text, MessageLevel.Error);
-}
+import { addGlobalErrorMessage } from 'design-system';
+import { parseError } from './request';
 
 export function addGlobalErrorMessageFromAPI(param: Response | string) {
   if (param instanceof Response) {
@@ -55,13 +27,10 @@ export function addGlobalErrorMessageFromAPI(param: Response | string) {
       /* ignore parsing errors */
     });
   }
+
   if (typeof param === 'string') {
     return Promise.resolve(param).then(addGlobalErrorMessage);
   }
 
   return Promise.resolve();
 }
-
-export function addGlobalSuccessMessage(text: string) {
-  addMessage(text, MessageLevel.Success);
-}
index 7b02bac6fb1288a2901c374686c8da8765d9b375..750d09ffc76d82d3eb06b7431933d8e85682efa6 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+
 import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
 import { Matcher, RenderResult, render, screen, within } from '@testing-library/react';
 import { UserEvent } from '@testing-library/user-event/dist/types/setup/setup';
+import { ToastMessageContainer } from 'design-system';
 import { omit } from 'lodash';
 import * as React from 'react';
 import { HelmetProvider } from 'react-helmet-async';
@@ -35,7 +37,6 @@ import {
   parsePath,
 } from 'react-router-dom';
 import AdminContext from '../app/components/AdminContext';
-import GlobalMessagesContainer from '../app/components/GlobalMessagesContainer';
 import AppStateContextProvider from '../app/components/app-state/AppStateContextProvider';
 import { AvailableFeaturesContext } from '../app/components/available-features/AvailableFeaturesContext';
 import { ComponentContext } from '../app/components/componentContext/ComponentContext';
@@ -229,7 +230,8 @@ function renderRoutedApp(
                 <AppStateContextProvider appState={appState}>
                   <IndexationContextProvider>
                     <QueryClientProvider client={queryClient}>
-                      <GlobalMessagesContainer />
+                      <ToastMessageContainer />
+
                       <RouterProvider router={router} />
                     </QueryClientProvider>
                   </IndexationContextProvider>
index 7ef74ae3703d700ba60ca5b8cb6b4aa226151179..af8399cd007f53dc5656825fa8155e074c109b74 100644 (file)
@@ -17,7 +17,9 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+
 import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
+import { addGlobalSuccessMessage } from 'design-system';
 import {
   copyQualityGate,
   createCondition,
@@ -32,7 +34,6 @@ import {
   updateCondition,
 } from '../api/quality-gates';
 import { getCorrectCaycCondition } from '../apps/quality-gates/utils';
-import { addGlobalSuccessMessage } from '../helpers/globalMessages';
 import { translate } from '../helpers/l10n';
 import { Condition, QualityGate } from '../types/types';
 
diff --git a/server/sonar-web/src/main/js/types/globalMessages.ts b/server/sonar-web/src/main/js/types/globalMessages.ts
deleted file mode 100644 (file)
index 5694272..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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.
- */
-export enum MessageLevel {
-  Error = 'ERROR',
-  Success = 'SUCCESS',
-}
-
-export interface Message {
-  id: string;
-  level: MessageLevel;
-  text: string;
-}