]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-16205 Improve code sharing with the developer extension
authorPhilippe Perrin <philippe.perrin@sonarsource.com>
Fri, 29 Apr 2022 16:25:13 +0000 (18:25 +0200)
committersonartech <sonartech@sonarsource.com>
Tue, 3 May 2022 20:03:09 +0000 (20:03 +0000)
29 files changed:
server/sonar-web/src/main/js/app/components/GlobalMessagesContainer.tsx
server/sonar-web/src/main/js/app/components/__tests__/GlobalMessagesContainer-it.ts
server/sonar-web/src/main/js/app/components/extensions/Extension.tsx
server/sonar-web/src/main/js/app/components/extensions/exposeLibraries.ts
server/sonar-web/src/main/js/app/utils/__tests__/globalMessagesService-test.ts [deleted file]
server/sonar-web/src/main/js/app/utils/globalMessagesService.ts [deleted file]
server/sonar-web/src/main/js/apps/projectDeletion/Form.tsx
server/sonar-web/src/main/js/apps/projectQualityGate/ProjectQualityGateApp.tsx
server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/ProjectQualityGateApp-test.tsx
server/sonar-web/src/main/js/apps/projectQualityProfiles/ProjectQualityProfilesApp.tsx
server/sonar-web/src/main/js/apps/projectQualityProfiles/__tests__/ProjectQualityProfilesApp-test.tsx
server/sonar-web/src/main/js/apps/projectsManagement/BulkApplyTemplateModal.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/Details.tsx
server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotOpenInIdeButton.tsx
server/sonar-web/src/main/js/apps/security-hotspots/components/assignee/Assignee.tsx
server/sonar-web/src/main/js/apps/security-hotspots/components/assignee/__tests__/Assignee-test.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__/Logout-test.tsx
server/sonar-web/src/main/js/apps/users/components/PasswordForm.tsx
server/sonar-web/src/main/js/components/controls/ComponentReportActions.tsx
server/sonar-web/src/main/js/components/controls/Select.tsx
server/sonar-web/src/main/js/components/controls/__tests__/ComponentReportActions-test.tsx
server/sonar-web/src/main/js/helpers/__tests__/error-test.ts
server/sonar-web/src/main/js/helpers/__tests__/globalMessages-test.ts [new file with mode: 0644]
server/sonar-web/src/main/js/helpers/error.ts
server/sonar-web/src/main/js/helpers/globalMessages.ts [new file with mode: 0644]
server/sonar-web/src/main/js/types/browser.ts
server/sonar-web/src/main/js/types/extension.ts

index 742d449ac58f0951d7f2b1ab23d7d5ada716e7ab..0618a6befa03d98b6043ffcdc3492ec4b92608f7 100644 (file)
@@ -19,9 +19,9 @@
  */
 import styled from '@emotion/styled';
 import React from 'react';
+import { registerListener, unregisterListener } from '../../helpers/globalMessages';
 import { Message } from '../../types/globalMessages';
 import { zIndexes } from '../theme';
-import { registerListener, unregisterListener } from '../utils/globalMessagesService';
 import GlobalMessage from './GlobalMessage';
 
 const MESSAGE_DISPLAY_TIME = 5000;
index 9a6cc31ecb8925fec254292d8678ebca1cc3fd8c..cc705e4d3b862b02c212ac47d9d093856f046b27 100644 (file)
@@ -18,8 +18,8 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import { screen } from '@testing-library/react';
+import { addGlobalErrorMessage, addGlobalSuccessMessage } from '../../../helpers/globalMessages';
 import { renderComponentApp } from '../../../helpers/testReactTestingUtils';
-import { addGlobalErrorMessage, addGlobalSuccessMessage } from '../../utils/globalMessagesService';
 
 it('should display messages', () => {
   jest.useFakeTimers();
index 563c1c1efcf042ed288cc995f3d54539d216353e..e13e596fd832e28bfd4f83c8169ff95b2eaca62a 100644 (file)
@@ -22,6 +22,7 @@ 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';
@@ -30,7 +31,6 @@ import { ExtensionStartMethod } from '../../../types/extension';
 import { Dict, Extension as TypeExtension } from '../../../types/types';
 import { CurrentUser } from '../../../types/users';
 import * as theme from '../../theme';
-import { addGlobalErrorMessage } from '../../utils/globalMessagesService';
 import withAppStateContext from '../app-state/withAppStateContext';
 import withCurrentUserContext from '../current-user/withCurrentUserContext';
 
index 154fb38d9ff69c71c9f92ebf2dea28b777170704..b534527ce4f3f398e0a17f3b33e5b75c96f67c78 100644 (file)
@@ -86,6 +86,7 @@ import {
   sortBranches
 } from '../../../helpers/branch-like';
 import { throwGlobalError } from '../../../helpers/error';
+import { addGlobalSuccessMessage } from '../../../helpers/globalMessages';
 import handleRequiredAuthentication from '../../../helpers/handleRequiredAuthentication';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
 import * as measures from '../../../helpers/measures';
@@ -117,7 +118,6 @@ import {
   getMeasureHistoryUrl,
   getRulesUrl
 } from '../../../helpers/urls';
-import { addGlobalSuccessMessage } from '../../utils/globalMessagesService';
 import A11ySkipTarget from '../a11y/A11ySkipTarget';
 import Suggestions from '../embed-docs-modal/Suggestions';
 
diff --git a/server/sonar-web/src/main/js/app/utils/__tests__/globalMessagesService-test.ts b/server/sonar-web/src/main/js/app/utils/__tests__/globalMessagesService-test.ts
deleted file mode 100644 (file)
index 0d40247..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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 { MessageLevel } from '../../../types/globalMessages';
-import {
-  addGlobalErrorMessage,
-  addGlobalSuccessMessage,
-  registerListener
-} from '../globalMessagesService';
-
-it('should work as expected', () => {
-  const listener1 = jest.fn();
-  registerListener(listener1);
-
-  addGlobalErrorMessage('test');
-
-  expect(listener1).toBeCalledWith(
-    expect.objectContaining({ text: 'test', level: MessageLevel.Error })
-  );
-
-  listener1.mockClear();
-  const listener2 = jest.fn();
-  registerListener(listener2);
-
-  addGlobalSuccessMessage('test');
-
-  expect(listener1).toBeCalledWith(
-    expect.objectContaining({ text: 'test', level: MessageLevel.Success })
-  );
-  expect(listener2).toBeCalledWith(
-    expect.objectContaining({ text: 'test', level: MessageLevel.Success })
-  );
-});
diff --git a/server/sonar-web/src/main/js/app/utils/globalMessagesService.ts b/server/sonar-web/src/main/js/app/utils/globalMessagesService.ts
deleted file mode 100644 (file)
index 127cf1c..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * 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 { uniqueId } from 'lodash';
-import { parseError } from '../../helpers/request';
-import { Message, MessageLevel } from '../../types/globalMessages';
-
-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);
-}
-
-export function addGlobalErrorMessageFromAPI(param: any) {
-  if (param instanceof Response) {
-    return parseError(param).then(addGlobalErrorMessage, () => {
-      /* 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 aa519367723452e195df0131ce0134f5558d265d..6f2de530e763578376ea734b76f7ce969d210dc6 100644 (file)
 import * as React from 'react';
 import { deleteApplication } from '../../api/application';
 import { deletePortfolio, deleteProject } from '../../api/components';
-import { addGlobalSuccessMessage } from '../../app/utils/globalMessagesService';
 import { Button } from '../../components/controls/buttons';
 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';
index e86d860f149b5dabd2528c481f977e44e84a4ad9..8f5df7ab9a1b9794dd16c4f01b96c04e68ec2acc 100644 (file)
@@ -26,8 +26,8 @@ import {
   getGateForProject,
   searchProjects
 } from '../../api/quality-gates';
-import { addGlobalSuccessMessage } from '../../app/utils/globalMessagesService';
 import handleRequiredAuthorization from '../../app/utils/handleRequiredAuthorization';
+import { addGlobalSuccessMessage } from '../../helpers/globalMessages';
 import { translate } from '../../helpers/l10n';
 import { Component, QualityGate } from '../../types/types';
 import { USE_SYSTEM_DEFAULT } from './constants';
index 0d22641ab3b8d6551d73d4bf96c8ade98e50cb88..a45b3468083d6f5b1b5b85e3c453621326042bc0 100644 (file)
@@ -62,7 +62,7 @@ jest.mock('../../../api/quality-gates', () => {
   };
 });
 
-jest.mock('../../../app/utils/globalMessagesService', () => ({
+jest.mock('../../../helpers/globalMessages', () => ({
   addGlobalSuccessMessage: jest.fn()
 }));
 
index 930558eef263be7c906b0a879d439eecb1715fb2..25455df146631de7d438b7cfb491b5c6101d8add 100644 (file)
@@ -26,8 +26,8 @@ import {
   Profile,
   searchQualityProfiles
 } from '../../api/quality-profiles';
-import { addGlobalSuccessMessage } from '../../app/utils/globalMessagesService';
 import handleRequiredAuthorization from '../../app/utils/handleRequiredAuthorization';
+import { addGlobalSuccessMessage } from '../../helpers/globalMessages';
 import { translateWithParameters } from '../../helpers/l10n';
 import { isDefined } from '../../helpers/types';
 import { Component } from '../../types/types';
index df5c7cf9553a3b44f09f261da905715df73bb51d..90777d416e7140ce57b1b110a7a465f8937ad0ef 100644 (file)
@@ -71,7 +71,7 @@ jest.mock('../../../api/quality-profiles', () => {
   };
 });
 
-jest.mock('../../../app/utils/globalMessagesService', () => ({
+jest.mock('../../../helpers/globalMessages', () => ({
   addGlobalSuccessMessage: jest.fn()
 }));
 
index 8a4c7da23b0a602c97bcc2a7cf86ad0ad2e62d5c..00a6e848cd743eab89246458ac259bc4d5668945 100644 (file)
@@ -19,7 +19,6 @@
  */
 import * as React from 'react';
 import { bulkApplyTemplate, getPermissionTemplates } from '../../api/permissions';
-import { addGlobalErrorMessageFromAPI } from '../../app/utils/globalMessagesService';
 import { ResetButtonLink, SubmitButton } from '../../components/controls/buttons';
 import Modal from '../../components/controls/Modal';
 import Select from '../../components/controls/Select';
@@ -27,6 +26,7 @@ import { Alert } from '../../components/ui/Alert';
 import MandatoryFieldMarker from '../../components/ui/MandatoryFieldMarker';
 import MandatoryFieldsExplanation from '../../components/ui/MandatoryFieldsExplanation';
 import { toNotSoISOString } from '../../helpers/dates';
+import { addGlobalErrorMessageFromAPI } from '../../helpers/globalMessages';
 import { translate, translateWithParameters } from '../../helpers/l10n';
 import { PermissionTemplate } from '../../types/types';
 
index 3767d0df7b9c1238c8cac15c011ac50f0214a410..ca292d3d87059d71f709995325a39b31aea89775 100644 (file)
@@ -20,8 +20,8 @@
 import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
 import { fetchQualityGate } from '../../../api/quality-gates';
-import { addGlobalSuccessMessage } from '../../../app/utils/globalMessagesService';
 import DeferredSpinner from '../../../components/ui/DeferredSpinner';
+import { addGlobalSuccessMessage } from '../../../helpers/globalMessages';
 import { translate } from '../../../helpers/l10n';
 import { Condition, QualityGate } from '../../../types/types';
 import { addCondition, checkIfDefault, deleteCondition, replaceCondition } from '../utils';
index 8f6fa5f60a80d0a8db6e79e11af39f996d59eb97..c7f5bfbe1b58375f561d6703444655b7b16b45d9 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
-import {
-  addGlobalErrorMessage,
-  addGlobalSuccessMessage
-} from '../../../app/utils/globalMessagesService';
 import { Button } from '../../../components/controls/buttons';
 import { DropdownOverlay } from '../../../components/controls/Dropdown';
 import Toggler from '../../../components/controls/Toggler';
 import DeferredSpinner from '../../../components/ui/DeferredSpinner';
+import { addGlobalErrorMessage, addGlobalSuccessMessage } from '../../../helpers/globalMessages';
 import { translate } from '../../../helpers/l10n';
 import { openHotspot, probeSonarLintServers } from '../../../helpers/sonarlint';
 import { Ide } from '../../../types/sonarlint';
index e0e4470308094b80ae7928cad1b569fc37bd5186..642e7a0ca2fef8be32bf902c24576d4dc1ec2de3 100644 (file)
@@ -20,7 +20,7 @@
 import * as React from 'react';
 import { assignSecurityHotspot } from '../../../../api/security-hotspots';
 import withCurrentUserContext from '../../../../app/components/current-user/withCurrentUserContext';
-import { addGlobalSuccessMessage } from '../../../../app/utils/globalMessagesService';
+import { addGlobalSuccessMessage } from '../../../../helpers/globalMessages';
 import { translate, translateWithParameters } from '../../../../helpers/l10n';
 import { Hotspot, HotspotResolution, HotspotStatus } from '../../../../types/security-hotspots';
 import { CurrentUser, isLoggedIn, UserActive } from '../../../../types/users';
index b95aad482a004ab0bffbc07ef08319384c50f3f1..cc9839ef45d13b07bd138333110cc6547fb4c142 100644 (file)
@@ -20,7 +20,7 @@
 import { shallow } from 'enzyme';
 import * as React from 'react';
 import { assignSecurityHotspot } from '../../../../../api/security-hotspots';
-import { addGlobalSuccessMessage } from '../../../../../app/utils/globalMessagesService';
+import { addGlobalSuccessMessage } from '../../../../../helpers/globalMessages';
 import { mockHotspot } from '../../../../../helpers/mocks/security-hotspots';
 import { mockCurrentUser, mockUser } from '../../../../../helpers/testMocks';
 import { waitAndUpdate } from '../../../../../helpers/testUtils';
@@ -33,7 +33,7 @@ jest.mock('../../../../../api/security-hotspots', () => ({
   assignSecurityHotspot: jest.fn()
 }));
 
-jest.mock('../../../../../app/utils/globalMessagesService', () => ({
+jest.mock('../../../../../helpers/globalMessages', () => ({
   addGlobalSuccessMessage: jest.fn()
 }));
 
index e4a2a2a46e57111410c3230d772a6ca1e213657f..bb7fcf719c3124ed3228e438120c34bd15c627e3 100644 (file)
@@ -21,7 +21,7 @@ import { Location } from 'history';
 import * as React from 'react';
 import { logIn } from '../../../api/auth';
 import { getIdentityProviders } from '../../../api/users';
-import { addGlobalErrorMessage } from '../../../app/utils/globalMessagesService';
+import { addGlobalErrorMessage } from '../../../helpers/globalMessages';
 import { translate } from '../../../helpers/l10n';
 import { getReturnUrl } from '../../../helpers/urls';
 import { IdentityProvider } from '../../../types/types';
index 4064b879c8cb13ed2309e5ac1db0ab901e2e3409..95bc68c8896c864a16606e99d7649c51e4ff59f3 100644 (file)
@@ -20,7 +20,7 @@
 import * as React from 'react';
 import { logOut } from '../../../api/auth';
 import RecentHistory from '../../../app/components/RecentHistory';
-import { addGlobalErrorMessage } from '../../../app/utils/globalMessagesService';
+import { addGlobalErrorMessage } from '../../../helpers/globalMessages';
 import { translate } from '../../../helpers/l10n';
 import { getBaseUrl } from '../../../helpers/system';
 
index 01e81b89e138de90a67c88ee8201a88b5c89a89e..9ff8fa85c6f62d0dd96685f72fc8bd16785bed48 100644 (file)
@@ -20,7 +20,7 @@
 import { shallow } from 'enzyme';
 import * as React from 'react';
 import { logOut } from '../../../../api/auth';
-import { addGlobalErrorMessage } from '../../../../app/utils/globalMessagesService';
+import { addGlobalErrorMessage } from '../../../../helpers/globalMessages';
 import { waitAndUpdate } from '../../../../helpers/testUtils';
 import Logout from '../Logout';
 
@@ -28,7 +28,7 @@ jest.mock('../../../../api/auth', () => ({
   logOut: jest.fn().mockResolvedValue(true)
 }));
 
-jest.mock('../../../../app/utils/globalMessagesService', () => ({
+jest.mock('../../../../helpers/globalMessages', () => ({
   addGlobalErrorMessage: jest.fn()
 }));
 
index 75f32f7c300959d7e3b56a299db8997956780a00..6aec452fe45ee7f629f565d8479548ed7c9d0472 100644 (file)
  */
 import * as React from 'react';
 import { changePassword } from '../../../api/users';
-import { addGlobalSuccessMessage } from '../../../app/utils/globalMessagesService';
 import { ResetButtonLink, SubmitButton } from '../../../components/controls/buttons';
 import Modal from '../../../components/controls/Modal';
 import { Alert } from '../../../components/ui/Alert';
 import MandatoryFieldMarker from '../../../components/ui/MandatoryFieldMarker';
 import MandatoryFieldsExplanation from '../../../components/ui/MandatoryFieldsExplanation';
 import { throwGlobalError } from '../../../helpers/error';
+import { addGlobalSuccessMessage } from '../../../helpers/globalMessages';
 import { translate } from '../../../helpers/l10n';
 import { parseError } from '../../../helpers/request';
 import { User } from '../../../types/users';
index 5cb685c46e98273e2b9386ed632eb4a9a4994740..38b294b267077c3b79d3f052705adedc45dcbb25 100644 (file)
@@ -25,7 +25,7 @@ import {
 } from '../../api/component-report';
 import withAppStateContext from '../../app/components/app-state/withAppStateContext';
 import withCurrentUserContext from '../../app/components/current-user/withCurrentUserContext';
-import { addGlobalSuccessMessage } from '../../app/utils/globalMessagesService';
+import { addGlobalSuccessMessage } from '../../helpers/globalMessages';
 import { translate, translateWithParameters } from '../../helpers/l10n';
 import { AppState } from '../../types/appstate';
 import { Branch } from '../../types/branch-like';
index 87cf5d50d15da1ad6a1e80d87bbea187666d7a86..d6de185293fa11a7db6f8657a1afeb64a11a05cf 100644 (file)
@@ -22,9 +22,11 @@ import classNames from 'classnames';
 import { omit } from 'lodash';
 import * as React from 'react';
 import ReactSelect, {
+  components,
   GroupTypeBase,
   IndicatorProps,
   NamedProps,
+  OptionProps,
   OptionTypeBase,
   StylesConfig
 } from 'react-select';
@@ -94,6 +96,9 @@ export function multiValueRemove<
   return <div {...props.innerProps}>×</div>;
 }
 
+export type SelectOptionProps<T, IsMulti extends boolean> = OptionProps<T, IsMulti>;
+export const SelectOption = components.Option;
+
 /* Keeping it as a class to simplify a dozen tests */
 export default class Select<
   Option,
index c0c419e1ea6aa1d9c7b0ffc6f5fcfb0ab8901eb7..6bade6685170d59b29740013736c8ae647c4b14f 100644 (file)
@@ -24,7 +24,7 @@ import {
   subscribeToEmailReport,
   unsubscribeFromEmailReport
 } from '../../../api/component-report';
-import { addGlobalSuccessMessage } from '../../../app/utils/globalMessagesService';
+import { addGlobalSuccessMessage } from '../../../helpers/globalMessages';
 import { mockBranch } from '../../../helpers/mocks/branch-like';
 import { mockComponent } from '../../../helpers/mocks/component';
 import { mockComponentReportStatus } from '../../../helpers/mocks/component-report';
@@ -49,7 +49,7 @@ jest.mock('../../../helpers/system', () => ({
   getBaseUrl: jest.fn().mockReturnValue('baseUrl')
 }));
 
-jest.mock('../../../app/utils/globalMessagesService', () => ({
+jest.mock('../../../helpers/globalMessages', () => ({
   addGlobalSuccessMessage: jest.fn()
 }));
 
index cf126933261d63a7f01a7aed88aff301ff4b3005..8615870ad6d74e327330df599b5e2735f16a5a71 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 '../../app/utils/globalMessagesService';
 import { throwGlobalError } from '../error';
+import { addGlobalErrorMessage } from '../globalMessages';
 
-jest.mock('../../app/utils/globalMessagesService', () => ({
+jest.mock('../../helpers/globalMessages', () => ({
   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
new file mode 100644 (file)
index 0000000..c6842d9
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * 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 { 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).toBeCalledWith(
+    expect.objectContaining({ text: 'test', level: MessageLevel.Error })
+  );
+
+  listener1.mockClear();
+  const listener2 = jest.fn();
+  registerListener(listener2);
+
+  addGlobalSuccessMessage('test');
+
+  expect(listener1).toBeCalledWith(
+    expect.objectContaining({ text: 'test', level: MessageLevel.Success })
+  );
+  expect(listener2).toBeCalledWith(
+    expect.objectContaining({ text: 'test', level: MessageLevel.Success })
+  );
+});
index 70ffdcc1795514bf7fceccdc6278ab322746dbc8..7356741dea7762ac36523b43f502e26a0e3ca624 100644 (file)
@@ -17,7 +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 { addGlobalErrorMessage } from '../app/utils/globalMessagesService';
+import { addGlobalErrorMessage } from './globalMessages';
 import { parseError } from './request';
 
 export function throwGlobalError(param: Response | any): Promise<Response | any> {
diff --git a/server/sonar-web/src/main/js/helpers/globalMessages.ts b/server/sonar-web/src/main/js/helpers/globalMessages.ts
new file mode 100644 (file)
index 0000000..dc5f2f0
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * 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 { 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);
+}
+
+export function addGlobalErrorMessageFromAPI(param: Response | string) {
+  if (param instanceof Response) {
+    return parseError(param).then(addGlobalErrorMessage, () => {
+      /* 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 b2adcb34d6d6ba17f138b484e83ecb768547f44e..ce0d3ea65c72c83464165459e2742fecae4a0174 100644 (file)
@@ -29,4 +29,6 @@ export interface EnhancedWindow extends Window {
 
   registerExtension: (key: string, start: ExtensionStartMethod, providesCSSFile?: boolean) => void;
   setWebAnalyticsPageChangeHandler: (pageHandler: (pathname: string) => void) => void;
+  t: (...keys: string[]) => string;
+  tp: (messageKey: string, ...parameters: Array<string | number>) => string;
 }
index 2540bc44cfec3c94772932eaea8344d21662756a..3defc91892da4405a8a56773eb64ddfad80c62c7 100644 (file)
@@ -21,7 +21,7 @@ import { IntlShape } from 'react-intl';
 import { Location, Router } from '../components/hoc/withRouter';
 import { AppState } from './appstate';
 import { L10nBundle } from './l10nBundle';
-import { Dict } from './types';
+import { Component, Dict } from './types';
 import { CurrentUser } from './users';
 
 export enum AdminPageExtension {
@@ -40,6 +40,7 @@ export interface ExtensionStartMethod {
 export interface ExtensionStartMethodParameter {
   appState: AppState;
   el: HTMLElement | undefined | null;
+  component?: Component;
   currentUser: CurrentUser;
   intl: IntlShape;
   location: Location;