aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src
diff options
context:
space:
mode:
authorJeremy Davis <jeremy.davis@sonarsource.com>2022-04-20 12:10:30 +0200
committersonartech <sonartech@sonarsource.com>2022-04-22 20:03:02 +0000
commitb69c2cc6a20c6c50bcc93be8d74012c31ffcefe3 (patch)
tree3fdd4cc5273b845cd6364a15765152cc64135f68 /server/sonar-web/src
parentb4e61d3047b13ab82102d360271c9d67ad9fdcaf (diff)
downloadsonarqube-b69c2cc6a20c6c50bcc93be8d74012c31ffcefe3.tar.gz
sonarqube-b69c2cc6a20c6c50bcc93be8d74012c31ffcefe3.zip
[NO JIRA] Extract global messages and remove redux
Diffstat (limited to 'server/sonar-web/src')
-rw-r--r--server/sonar-web/src/main/js/app/components/GlobalContainer.tsx2
-rw-r--r--server/sonar-web/src/main/js/app/components/GlobalMessage.tsx (renamed from server/sonar-web/src/main/js/components/controls/GlobalMessages.tsx)83
-rw-r--r--server/sonar-web/src/main/js/app/components/GlobalMessagesContainer.tsx88
-rw-r--r--server/sonar-web/src/main/js/app/components/PluginRiskConsent.tsx3
-rw-r--r--server/sonar-web/src/main/js/app/components/ResetPassword.tsx3
-rw-r--r--server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx3
-rw-r--r--server/sonar-web/src/main/js/app/components/__tests__/GlobalMessagesContainer-it.ts (renamed from server/sonar-web/src/main/js/store/rootReducer.ts)22
-rw-r--r--server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalContainer-test.tsx.snap1
-rw-r--r--server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/PluginRiskConsent-test.tsx.snap1
-rw-r--r--server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/ResetPassword-test.tsx.snap1
-rw-r--r--server/sonar-web/src/main/js/app/components/extensions/Extension.tsx17
-rw-r--r--server/sonar-web/src/main/js/app/components/extensions/ProjectAdminPageExtension.tsx8
-rw-r--r--server/sonar-web/src/main/js/app/components/extensions/__tests__/Extension-test.tsx4
-rw-r--r--server/sonar-web/src/main/js/app/components/extensions/__tests__/ProjectAdminPageExtension-test.tsx3
-rw-r--r--server/sonar-web/src/main/js/app/components/extensions/__tests__/__snapshots__/Extension-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/app/components/extensions/__tests__/__snapshots__/ProjectAdminPageExtension-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/app/components/extensions/__tests__/__snapshots__/ProjectPageExtension-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/app/components/extensions/exposeLibraries.ts2
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNav-test.tsx4
-rw-r--r--server/sonar-web/src/main/js/app/utils/__tests__/globalMessagesService-test.ts (renamed from server/sonar-web/src/main/js/store/__tests__/globalMessages-test.ts)45
-rw-r--r--server/sonar-web/src/main/js/app/utils/addGlobalSuccessMessage.ts26
-rw-r--r--server/sonar-web/src/main/js/app/utils/getStore.ts31
-rw-r--r--server/sonar-web/src/main/js/app/utils/globalMessagesService.ts (renamed from server/sonar-web/src/main/js/store/utils/configureStore.ts)40
-rw-r--r--server/sonar-web/src/main/js/app/utils/startReactApp.tsx164
-rw-r--r--server/sonar-web/src/main/js/apps/change-admin-password/ChangeAdminPasswordAppRenderer.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/change-admin-password/__tests__/ChangeAdminPasswordApp-test.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/change-admin-password/__tests__/__snapshots__/ChangeAdminPasswordAppRenderer-test.tsx.snap4
-rw-r--r--server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/AssigneeFacet-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/projectDeletion/Form.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/projectQualityGate/ProjectQualityGateApp.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/ProjectQualityGateApp-test.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/projectQualityProfiles/ProjectQualityProfilesApp.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/projectQualityProfiles/__tests__/ProjectQualityProfilesApp-test.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/Details.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotOpenInIdeButton.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/assignee/Assignee.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/assignee/__tests__/Assignee-test.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/sessions/components/Login.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/sessions/components/LoginContainer.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/sessions/components/Logout.tsx10
-rw-r--r--server/sonar-web/src/main/js/apps/sessions/components/__tests__/Logout-test.tsx9
-rw-r--r--server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/Login-test.tsx.snap3
-rw-r--r--server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/Logout-test.tsx.snap1
-rw-r--r--server/sonar-web/src/main/js/apps/users/components/PasswordForm.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/controls/ComponentReportActions.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/controls/__tests__/ComponentReportActions-test.tsx6
-rw-r--r--server/sonar-web/src/main/js/components/controls/__tests__/GlobalMessages-test.tsx78
-rw-r--r--server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/GlobalMessages-test.tsx.snap212
-rw-r--r--server/sonar-web/src/main/js/helpers/__tests__/error-test.ts24
-rw-r--r--server/sonar-web/src/main/js/helpers/error.ts14
-rw-r--r--server/sonar-web/src/main/js/helpers/testMocks.ts5
-rw-r--r--server/sonar-web/src/main/js/helpers/testReactTestingUtils.tsx28
-rw-r--r--server/sonar-web/src/main/js/store/globalMessages.ts84
-rw-r--r--server/sonar-web/src/main/js/types/extension.ts3
-rw-r--r--server/sonar-web/src/main/js/types/globalMessages.ts (renamed from server/sonar-web/src/main/js/app/utils/addGlobalErrorMessage.ts)13
55 files changed, 325 insertions, 777 deletions
diff --git a/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx b/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx
index 3ee41a0606e..b02e8c98ebc 100644
--- a/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx
+++ b/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx
@@ -24,7 +24,6 @@ import A11ySkipLinks from './a11y/A11ySkipLinks';
import BranchStatusContextProvider from './branch-status/BranchStatusContextProvider';
import SuggestionsProvider from './embed-docs-modal/SuggestionsProvider';
import GlobalFooter from './GlobalFooter';
-import GlobalMessagesContainer from './GlobalMessagesContainer';
import IndexationContextProvider from './indexation/IndexationContextProvider';
import IndexationNotification from './indexation/IndexationNotification';
import LanguagesContextProvider from './languages/LanguagesContextProvider';
@@ -57,7 +56,6 @@ export default function GlobalContainer(props: Props) {
<LanguagesContextProvider>
<MetricsContextProvider>
<GlobalNav location={props.location} />
- <GlobalMessagesContainer />
<IndexationNotification />
<UpdateNotification dismissable={true} />
{props.children}
diff --git a/server/sonar-web/src/main/js/components/controls/GlobalMessages.tsx b/server/sonar-web/src/main/js/app/components/GlobalMessage.tsx
index 1598d35e80b..9a0a64e1a2d 100644
--- a/server/sonar-web/src/main/js/components/controls/GlobalMessages.tsx
+++ b/server/sonar-web/src/main/js/app/components/GlobalMessage.tsx
@@ -20,69 +20,32 @@
import { keyframes } from '@emotion/react';
import styled from '@emotion/styled';
import * as React from 'react';
-import { colors, sizes, zIndexes } from '../../app/theme';
+import { ClearButton } from '../../components/controls/buttons';
import { cutLongWords } from '../../helpers/path';
-import { ClearButton } from './buttons';
+import { Message } from '../../types/globalMessages';
+import { colors, sizes } from '../theme';
-interface IMessage {
- id: string;
- level: 'ERROR' | 'SUCCESS';
- message: string;
-}
-
-export interface GlobalMessagesProps {
+export interface GlobalMessageProps {
closeGlobalMessage: (id: string) => void;
- messages: IMessage[];
+ message: Message;
}
-export default function GlobalMessages({ closeGlobalMessage, messages }: GlobalMessagesProps) {
- if (messages.length === 0) {
- return null;
- }
-
+export default function GlobalMessage(props: GlobalMessageProps) {
+ const { message } = props;
return (
- <MessagesContainer>
- {messages.map(message => (
- <GlobalMessage closeGlobalMessage={closeGlobalMessage} key={message.id} message={message} />
- ))}
- </MessagesContainer>
- );
-}
-
-const MessagesContainer = styled.div`
- position: fixed;
- z-index: ${zIndexes.processContainerZIndex};
- top: 0;
- left: 50%;
- width: 350px;
- margin-left: -175px;
-`;
-
-export class GlobalMessage extends React.PureComponent<{
- closeGlobalMessage: (id: string) => void;
- message: IMessage;
-}> {
- handleClose = () => {
- this.props.closeGlobalMessage(this.props.message.id);
- };
-
- render() {
- const { message } = this.props;
- return (
- <Message
- data-test={`global-message__${message.level}`}
+ <MessageBox
+ data-test={`global-message__${message.level}`}
+ level={message.level}
+ role={message.level === 'SUCCESS' ? 'status' : 'alert'}>
+ {cutLongWords(message.text)}
+ <CloseButton
+ className="button-small"
+ color="#fff"
level={message.level}
- role={message.level === 'SUCCESS' ? 'status' : 'alert'}>
- {cutLongWords(message.message)}
- <CloseButton
- className="button-small"
- color="#fff"
- level={message.level}
- onClick={this.handleClose}
- />
- </Message>
- );
- }
+ onClick={() => props.closeGlobalMessage(message.id)}
+ />
+ </MessageBox>
+ );
}
const appearAnim = keyframes`
@@ -94,7 +57,7 @@ const appearAnim = keyframes`
}
`;
-const Message = styled.div<Pick<IMessage, 'level'>>`
+const MessageBox = styled.div<Pick<Message, 'level'>>`
position: relative;
padding: 0 30px 0 10px;
line-height: ${sizes.controlHeight};
@@ -112,13 +75,13 @@ const Message = styled.div<Pick<IMessage, 'level'>>`
}
`;
-const CloseButton = styled(ClearButton)<Pick<IMessage, 'level'>>`
+const CloseButton = styled(ClearButton)<Pick<Message, 'level'>>`
position: absolute;
top: calc(${sizes.gridSize} / 4);
right: calc(${sizes.gridSize} / 4);
- &:hover svg,
- &:focus svg {
+ &.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
index ac544be2cfa..742d449ac58 100644
--- a/server/sonar-web/src/main/js/app/components/GlobalMessagesContainer.tsx
+++ b/server/sonar-web/src/main/js/app/components/GlobalMessagesContainer.tsx
@@ -17,15 +17,85 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { connect } from 'react-redux';
-import GlobalMessages from '../../components/controls/GlobalMessages';
-import { closeGlobalMessage } from '../../store/globalMessages';
-import { getGlobalMessages, Store } from '../../store/rootReducer';
+import styled from '@emotion/styled';
+import React from 'react';
+import { Message } from '../../types/globalMessages';
+import { zIndexes } from '../theme';
+import { registerListener, unregisterListener } from '../utils/globalMessagesService';
+import GlobalMessage from './GlobalMessage';
-const mapStateToProps = (state: Store) => ({
- messages: getGlobalMessages(state)
-});
+const MESSAGE_DISPLAY_TIME = 5000;
+const MAX_MESSAGES = 3;
-const mapDispatchToProps = { closeGlobalMessage };
+interface State {
+ messages: Message[];
+}
-export default connect(mapStateToProps, mapDispatchToProps)(GlobalMessages);
+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.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>
+ {messages.map(message => (
+ <GlobalMessage
+ closeGlobalMessage={this.closeMessage}
+ key={message.id}
+ message={message}
+ />
+ ))}
+ </MessagesContainer>
+ );
+ }
+}
+
+const MessagesContainer = styled.div`
+ position: fixed;
+ z-index: ${zIndexes.processContainerZIndex};
+ top: 0;
+ left: 50%;
+ width: 350px;
+ margin-left: -175px;
+`;
diff --git a/server/sonar-web/src/main/js/app/components/PluginRiskConsent.tsx b/server/sonar-web/src/main/js/app/components/PluginRiskConsent.tsx
index 6b560a90ca2..394fa4a10b3 100644
--- a/server/sonar-web/src/main/js/app/components/PluginRiskConsent.tsx
+++ b/server/sonar-web/src/main/js/app/components/PluginRiskConsent.tsx
@@ -28,7 +28,6 @@ import { Permissions } from '../../types/permissions';
import { RiskConsent } from '../../types/plugins';
import { SettingsKey } from '../../types/settings';
import { LoggedInUser } from '../../types/users';
-import GlobalMessagesContainer from './GlobalMessagesContainer';
import './PluginRiskConsent.css';
export interface PluginRiskConsentProps {
@@ -59,8 +58,6 @@ export function PluginRiskConsent(props: PluginRiskConsentProps) {
return (
<div className="plugin-risk-consent-page">
- <GlobalMessagesContainer />
-
<div className="plugin-risk-consent-content boxed-group">
<div className="boxed-group-inner text-center">
<h1 className="big-spacer-bottom">{translate('plugin_risk_consent.title')}</h1>
diff --git a/server/sonar-web/src/main/js/app/components/ResetPassword.tsx b/server/sonar-web/src/main/js/app/components/ResetPassword.tsx
index 183cd2accb8..1bb9f1db4c3 100644
--- a/server/sonar-web/src/main/js/app/components/ResetPassword.tsx
+++ b/server/sonar-web/src/main/js/app/components/ResetPassword.tsx
@@ -23,7 +23,6 @@ import { whenLoggedIn } from '../../components/hoc/whenLoggedIn';
import { translate } from '../../helpers/l10n';
import { getBaseUrl } from '../../helpers/system';
import { LoggedInUser } from '../../types/users';
-import GlobalMessagesContainer from './GlobalMessagesContainer';
export interface ResetPasswordProps {
currentUser: LoggedInUser;
@@ -33,8 +32,6 @@ export function ResetPassword({ currentUser }: ResetPasswordProps) {
return (
<div className="page-wrapper-simple">
<div className="page-simple">
- <GlobalMessagesContainer />
-
<h1 className="text-center huge">{translate('my_account.reset_password')}</h1>
<p className="text-center huge-spacer-top huge-spacer-bottom">
{translate('my_account.reset_password.explain')}
diff --git a/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx b/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx
index f1c4226abca..e9e3361093a 100644
--- a/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx
@@ -77,9 +77,6 @@ jest.mock('../../../api/alm-settings', () => ({
validateProjectAlmBinding: jest.fn().mockResolvedValue(undefined)
}));
-// mock this, because some of its children are using redux store
-jest.mock('../nav/component/ComponentNav', () => () => null);
-
jest.mock('../../utils/handleRequiredAuthorization', () => ({
__esModule: true,
default: jest.fn()
diff --git a/server/sonar-web/src/main/js/store/rootReducer.ts b/server/sonar-web/src/main/js/app/components/__tests__/GlobalMessagesContainer-it.ts
index 2a68fa75667..cdab690199a 100644
--- a/server/sonar-web/src/main/js/store/rootReducer.ts
+++ b/server/sonar-web/src/main/js/app/components/__tests__/GlobalMessagesContainer-it.ts
@@ -17,17 +17,17 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { combineReducers } from 'redux';
-import globalMessages, * as fromGlobalMessages from './globalMessages';
+import { screen } from '@testing-library/react';
+import { renderComponentApp } from '../../../helpers/testReactTestingUtils';
+import { addGlobalErrorMessage, addGlobalSuccessMessage } from '../../utils/globalMessagesService';
+import GlobalMessagesContainer from '../GlobalMessagesContainer';
-export type Store = {
- globalMessages: fromGlobalMessages.State;
-};
+it('should display messages', () => {
+ renderComponentApp('sonarqube', GlobalMessagesContainer);
-export default combineReducers<Store>({
- globalMessages
-});
+ addGlobalErrorMessage('This is an error');
+ addGlobalSuccessMessage('This was a triumph!');
-export function getGlobalMessages(state: Store) {
- return fromGlobalMessages.getGlobalMessages(state.globalMessages);
-}
+ expect(screen.getByRole('alert')).toHaveTextContent('This is an error');
+ expect(screen.getByRole('status')).toHaveTextContent('This was a triumph!');
+});
diff --git a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalContainer-test.tsx.snap b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalContainer-test.tsx.snap
index 2f15d0754f4..16b94edc8ff 100644
--- a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalContainer-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalContainer-test.tsx.snap
@@ -33,7 +33,6 @@ exports[`should render correctly 1`] = `
}
}
/>
- <Connect(GlobalMessages) />
<withCurrentUserContext(withIndexationContext(IndexationNotification)) />
<withCurrentUserContext(withAppStateContext(UpdateNotification))
dismissable={true}
diff --git a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/PluginRiskConsent-test.tsx.snap b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/PluginRiskConsent-test.tsx.snap
index 5e5cb14f15e..ec59ee3ae60 100644
--- a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/PluginRiskConsent-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/PluginRiskConsent-test.tsx.snap
@@ -4,7 +4,6 @@ exports[`should render correctly: default 1`] = `
<div
className="plugin-risk-consent-page"
>
- <Connect(GlobalMessages) />
<div
className="plugin-risk-consent-content boxed-group"
>
diff --git a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/ResetPassword-test.tsx.snap b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/ResetPassword-test.tsx.snap
index cf445a7dcb5..e6107f27865 100644
--- a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/ResetPassword-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/ResetPassword-test.tsx.snap
@@ -7,7 +7,6 @@ exports[`should render correctly 1`] = `
<div
className="page-simple"
>
- <Connect(GlobalMessages) />
<h1
className="text-center huge"
>
diff --git a/server/sonar-web/src/main/js/app/components/extensions/Extension.tsx b/server/sonar-web/src/main/js/app/components/extensions/Extension.tsx
index 1220526fb6a..563c1c1efcf 100644
--- a/server/sonar-web/src/main/js/app/components/extensions/Extension.tsx
+++ b/server/sonar-web/src/main/js/app/components/extensions/Extension.tsx
@@ -20,19 +20,17 @@
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
import { injectIntl, WrappedComponentProps } from 'react-intl';
-import { connect } from 'react-redux';
import { Location, Router, withRouter } from '../../../components/hoc/withRouter';
import { getExtensionStart } from '../../../helpers/extensions';
import { translate } from '../../../helpers/l10n';
import { getCurrentL10nBundle } from '../../../helpers/l10nBundle';
import { getBaseUrl } from '../../../helpers/system';
-import { addGlobalErrorMessage } from '../../../store/globalMessages';
import { AppState } from '../../../types/appstate';
import { ExtensionStartMethod } from '../../../types/extension';
import { Dict, Extension as TypeExtension } from '../../../types/types';
import { CurrentUser } from '../../../types/users';
import * as theme from '../../theme';
-import getStore from '../../utils/getStore';
+import { addGlobalErrorMessage } from '../../utils/globalMessagesService';
import withAppStateContext from '../app-state/withAppStateContext';
import withCurrentUserContext from '../current-user/withCurrentUserContext';
@@ -41,7 +39,6 @@ interface Props extends WrappedComponentProps {
currentUser: CurrentUser;
extension: TypeExtension;
location: Location;
- onFail: (message: string) => void;
options?: Dict<any>;
router: Router;
}
@@ -73,10 +70,8 @@ export class Extension extends React.PureComponent<Props, State> {
}
handleStart = (start: ExtensionStartMethod) => {
- const store = getStore();
const result = start({
appState: this.props.appState,
- store,
el: this.container,
currentUser: this.props.currentUser,
intl: this.props.intl,
@@ -98,7 +93,7 @@ export class Extension extends React.PureComponent<Props, State> {
};
handleFailure = () => {
- this.props.onFail(translate('page_extension_failed'));
+ addGlobalErrorMessage(translate('page_extension_failed'));
};
startExtension() {
@@ -128,10 +123,4 @@ export class Extension extends React.PureComponent<Props, State> {
}
}
-export default injectIntl(
- withRouter(
- withAppStateContext(
- withCurrentUserContext(connect(null, { onFail: addGlobalErrorMessage })(Extension))
- )
- )
-);
+export default injectIntl(withRouter(withAppStateContext(withCurrentUserContext(Extension))));
diff --git a/server/sonar-web/src/main/js/app/components/extensions/ProjectAdminPageExtension.tsx b/server/sonar-web/src/main/js/app/components/extensions/ProjectAdminPageExtension.tsx
index 72befde92a8..efc6ae1a1ae 100644
--- a/server/sonar-web/src/main/js/app/components/extensions/ProjectAdminPageExtension.tsx
+++ b/server/sonar-web/src/main/js/app/components/extensions/ProjectAdminPageExtension.tsx
@@ -18,8 +18,6 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { connect } from 'react-redux';
-import { addGlobalErrorMessage } from '../../../store/globalMessages';
import { Component } from '../../../types/types';
import NotFound from '../NotFound';
import Extension from './Extension';
@@ -29,7 +27,7 @@ export interface ProjectAdminPageExtensionProps {
params: { extensionKey: string; pluginKey: string };
}
-export function ProjectAdminPageExtension(props: ProjectAdminPageExtensionProps) {
+export default function ProjectAdminPageExtension(props: ProjectAdminPageExtensionProps) {
const {
component,
params: { extensionKey, pluginKey }
@@ -45,7 +43,3 @@ export function ProjectAdminPageExtension(props: ProjectAdminPageExtensionProps)
<NotFound withContainer={false} />
);
}
-
-const mapDispatchToProps = { onFail: addGlobalErrorMessage };
-
-export default connect(null, mapDispatchToProps)(ProjectAdminPageExtension);
diff --git a/server/sonar-web/src/main/js/app/components/extensions/__tests__/Extension-test.tsx b/server/sonar-web/src/main/js/app/components/extensions/__tests__/Extension-test.tsx
index 08ff7438d87..3001705b905 100644
--- a/server/sonar-web/src/main/js/app/components/extensions/__tests__/Extension-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/extensions/__tests__/Extension-test.tsx
@@ -19,6 +19,7 @@
*/
import { mount } from 'enzyme';
import * as React from 'react';
+import { IntlShape } from 'react-intl';
import { getExtensionStart } from '../../../../helpers/extensions';
import {
mockAppState,
@@ -93,9 +94,8 @@ function shallowRender(props: Partial<Extension['props']> = {}) {
appState={mockAppState()}
currentUser={mockCurrentUser()}
extension={{ key: 'foo', name: 'Foo' }}
- intl={{} as any}
+ intl={{} as IntlShape}
location={mockLocation()}
- onFail={jest.fn()}
router={mockRouter()}
{...props}
/>
diff --git a/server/sonar-web/src/main/js/app/components/extensions/__tests__/ProjectAdminPageExtension-test.tsx b/server/sonar-web/src/main/js/app/components/extensions/__tests__/ProjectAdminPageExtension-test.tsx
index f3b7a57bfc4..7b29077a35f 100644
--- a/server/sonar-web/src/main/js/app/components/extensions/__tests__/ProjectAdminPageExtension-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/extensions/__tests__/ProjectAdminPageExtension-test.tsx
@@ -20,8 +20,7 @@
import { shallow } from 'enzyme';
import * as React from 'react';
import { mockComponent } from '../../../../helpers/mocks/component';
-import {
- ProjectAdminPageExtension,
+import ProjectAdminPageExtension, {
ProjectAdminPageExtensionProps
} from '../ProjectAdminPageExtension';
diff --git a/server/sonar-web/src/main/js/app/components/extensions/__tests__/__snapshots__/Extension-test.tsx.snap b/server/sonar-web/src/main/js/app/components/extensions/__tests__/__snapshots__/Extension-test.tsx.snap
index 4ab2a2f58c2..4821cb22c56 100644
--- a/server/sonar-web/src/main/js/app/components/extensions/__tests__/__snapshots__/Extension-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/extensions/__tests__/__snapshots__/Extension-test.tsx.snap
@@ -36,7 +36,6 @@ exports[`should render React extensions correctly 1`] = `
"state": Object {},
}
}
- onFail={[MockFunction]}
router={
Object {
"createHref": [MockFunction],
@@ -96,7 +95,6 @@ exports[`should render React extensions correctly 2`] = `
"state": Object {},
}
}
- onFail={[MockFunction]}
router={
Object {
"createHref": [MockFunction],
diff --git a/server/sonar-web/src/main/js/app/components/extensions/__tests__/__snapshots__/ProjectAdminPageExtension-test.tsx.snap b/server/sonar-web/src/main/js/app/components/extensions/__tests__/__snapshots__/ProjectAdminPageExtension-test.tsx.snap
index 41c11c85e36..690ff078cd9 100644
--- a/server/sonar-web/src/main/js/app/components/extensions/__tests__/__snapshots__/ProjectAdminPageExtension-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/extensions/__tests__/__snapshots__/ProjectAdminPageExtension-test.tsx.snap
@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`should render correctly: extension exists 1`] = `
-<injectIntl(withRouter(withAppStateContext(withCurrentUserContext(Connect(Extension)))))
+<injectIntl(withRouter(withAppStateContext(withCurrentUserContext(Extension))))
extension={
Object {
"key": "foo/bar",
diff --git a/server/sonar-web/src/main/js/app/components/extensions/__tests__/__snapshots__/ProjectPageExtension-test.tsx.snap b/server/sonar-web/src/main/js/app/components/extensions/__tests__/__snapshots__/ProjectPageExtension-test.tsx.snap
index bb710428d4c..c99ec489367 100644
--- a/server/sonar-web/src/main/js/app/components/extensions/__tests__/__snapshots__/ProjectPageExtension-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/extensions/__tests__/__snapshots__/ProjectPageExtension-test.tsx.snap
@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`should render correctly 1`] = `
-<injectIntl(withRouter(withAppStateContext(withCurrentUserContext(Connect(Extension)))))
+<injectIntl(withRouter(withAppStateContext(withCurrentUserContext(Extension))))
extension={
Object {
"key": "plugin-key/extension-key",
diff --git a/server/sonar-web/src/main/js/app/components/extensions/exposeLibraries.ts b/server/sonar-web/src/main/js/app/components/extensions/exposeLibraries.ts
index 7650130479e..154fb38d9ff 100644
--- a/server/sonar-web/src/main/js/app/components/extensions/exposeLibraries.ts
+++ b/server/sonar-web/src/main/js/app/components/extensions/exposeLibraries.ts
@@ -117,7 +117,7 @@ import {
getMeasureHistoryUrl,
getRulesUrl
} from '../../../helpers/urls';
-import addGlobalSuccessMessage from '../../utils/addGlobalSuccessMessage';
+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/components/nav/global/__tests__/GlobalNav-test.tsx b/server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNav-test.tsx
index 49b64ed497c..d85531ebc4d 100644
--- a/server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNav-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNav-test.tsx
@@ -22,10 +22,6 @@ import * as React from 'react';
import { waitAndUpdate } from '../../../../../helpers/testUtils';
import { GlobalNav, GlobalNavProps } from '../GlobalNav';
-// Solve redux warning issue "No reducer provided for key":
-// https://stackoverflow.com/questions/43375079/redux-warning-only-appearing-in-tests
-jest.mock('../../../../../store/rootReducer');
-
const location = { pathname: '' };
it('should render correctly', async () => {
diff --git a/server/sonar-web/src/main/js/store/__tests__/globalMessages-test.ts b/server/sonar-web/src/main/js/app/utils/__tests__/globalMessagesService-test.ts
index a5ceed74a08..0d402470d43 100644
--- a/server/sonar-web/src/main/js/store/__tests__/globalMessages-test.ts
+++ b/server/sonar-web/src/main/js/app/utils/__tests__/globalMessagesService-test.ts
@@ -17,28 +17,33 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import globalMessagesReducer, { MessageLevel } from '../globalMessages';
+import { MessageLevel } from '../../../types/globalMessages';
+import {
+ addGlobalErrorMessage,
+ addGlobalSuccessMessage,
+ registerListener
+} from '../globalMessagesService';
-describe('globalMessagesReducer', () => {
- it('should handle ADD_GLOBAL_MESSAGE', () => {
- const actionAttributes = { id: 'id', message: 'There was an error', level: MessageLevel.Error };
+it('should work as expected', () => {
+ const listener1 = jest.fn();
+ registerListener(listener1);
- expect(
- globalMessagesReducer([], {
- type: 'ADD_GLOBAL_MESSAGE',
- ...actionAttributes
- })
- ).toEqual([actionAttributes]);
- });
+ addGlobalErrorMessage('test');
- it('should handle CLOSE_GLOBAL_MESSAGE', () => {
- const state = [
- { id: 'm1', message: 'message 1', level: MessageLevel.Success },
- { id: 'm2', message: 'message 2', level: MessageLevel.Success }
- ];
+ expect(listener1).toBeCalledWith(
+ expect.objectContaining({ text: 'test', level: MessageLevel.Error })
+ );
- expect(globalMessagesReducer(state, { type: 'CLOSE_GLOBAL_MESSAGE', id: 'm2' })).toEqual([
- state[0]
- ]);
- });
+ 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/addGlobalSuccessMessage.ts b/server/sonar-web/src/main/js/app/utils/addGlobalSuccessMessage.ts
deleted file mode 100644
index e631beefd68..00000000000
--- a/server/sonar-web/src/main/js/app/utils/addGlobalSuccessMessage.ts
+++ /dev/null
@@ -1,26 +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 * as globalMessages from '../../store/globalMessages';
-import getStore from './getStore';
-
-export default function addGlobalSuccessMessage(message: string): void {
- const store = getStore();
- store.dispatch(globalMessages.addGlobalSuccessMessage(message));
-}
diff --git a/server/sonar-web/src/main/js/app/utils/getStore.ts b/server/sonar-web/src/main/js/app/utils/getStore.ts
deleted file mode 100644
index 251dd073983..00000000000
--- a/server/sonar-web/src/main/js/app/utils/getStore.ts
+++ /dev/null
@@ -1,31 +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 { Store } from 'redux';
-import rootReducer, { Store as State } from '../../store/rootReducer';
-import configureStore from '../../store/utils/configureStore';
-
-let store: Store<State, any>;
-
-const createStore = () => {
- store = configureStore(rootReducer);
- return store;
-};
-
-export default () => (store ? store : createStore());
diff --git a/server/sonar-web/src/main/js/store/utils/configureStore.ts b/server/sonar-web/src/main/js/app/utils/globalMessagesService.ts
index 65459f8eea3..fb3c82b6234 100644
--- a/server/sonar-web/src/main/js/store/utils/configureStore.ts
+++ b/server/sonar-web/src/main/js/app/utils/globalMessagesService.ts
@@ -17,22 +17,38 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { applyMiddleware, compose, createStore } from 'redux';
-import thunk, { ThunkMiddleware } from 'redux-thunk';
-type RootReducer = typeof import('../rootReducer').default;
-type State = import('../rootReducer').Store;
+import { uniqueId } from 'lodash';
+import { Message, MessageLevel } from '../../types/globalMessages';
-const middlewares = [thunk as ThunkMiddleware<State, any>];
-const composed = [];
+const listeners: Array<(message: Message) => void> = [];
-if (process.env.NODE_ENV === 'development') {
- const { __REDUX_DEVTOOLS_EXTENSION__ } = window as any;
- composed.push(__REDUX_DEVTOOLS_EXTENSION__ ? __REDUX_DEVTOOLS_EXTENSION__() : (f: Function) => f);
+export function registerListener(callback: (message: Message) => void) {
+ listeners.push(callback);
}
-const finalCreateStore = compose(applyMiddleware(...middlewares), ...composed)(createStore);
+export function unregisterListener(callback: (message: Message) => void) {
+ const index = listeners.indexOf(callback);
-export default function configureStore(rootReducer: RootReducer, initialState?: State) {
- return finalCreateStore(rootReducer, initialState);
+ 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 addGlobalSuccessMessage(text: string) {
+ addMessage(text, MessageLevel.Success);
}
diff --git a/server/sonar-web/src/main/js/app/utils/startReactApp.tsx b/server/sonar-web/src/main/js/app/utils/startReactApp.tsx
index fc17431e3fa..622c56db585 100644
--- a/server/sonar-web/src/main/js/app/utils/startReactApp.tsx
+++ b/server/sonar-web/src/main/js/app/utils/startReactApp.tsx
@@ -24,7 +24,6 @@ import * as React from 'react';
import { render } from 'react-dom';
import { HelmetProvider } from 'react-helmet-async';
import { IntlProvider } from 'react-intl';
-import { Provider } from 'react-redux';
import { IndexRoute, Redirect, Route, RouteConfig, RouteProps, Router } from 'react-router';
import accountRoutes from '../../apps/account/routes';
import auditLogsRoutes from '../../apps/audit-logs/routes';
@@ -66,11 +65,11 @@ import App from '../components/App';
import AppStateContextProvider from '../components/app-state/AppStateContextProvider';
import CurrentUserContextProvider from '../components/current-user/CurrentUserContextProvider';
import GlobalContainer from '../components/GlobalContainer';
+import GlobalMessagesContainer from '../components/GlobalMessagesContainer';
import { PageContext } from '../components/indexation/PageUnavailableDueToIndexation';
import MigrationContainer from '../components/MigrationContainer';
import NonAdminPagesContainer from '../components/NonAdminPagesContainer';
import exportModulesAsGlobals from './exportModulesAsGlobals';
-import getStore from './getStore';
function handleUpdate(this: { state: { location: Location } }) {
const { action } = this.state.location;
@@ -283,107 +282,102 @@ export default function startReactApp(lang: string, appState: AppState, currentU
const el = document.getElementById('content');
const history = getHistory();
- const store = getStore();
render(
<HelmetProvider>
- <Provider store={store}>
- <AppStateContextProvider appState={appState}>
- <CurrentUserContextProvider currentUser={currentUser}>
- <IntlProvider defaultLocale={lang} locale={lang}>
- <Router history={history} onUpdate={handleUpdate}>
- {renderRedirects()}
+ <AppStateContextProvider appState={appState}>
+ <CurrentUserContextProvider currentUser={currentUser}>
+ <IntlProvider defaultLocale={lang} locale={lang}>
+ <GlobalMessagesContainer />
+ <Router history={history} onUpdate={handleUpdate}>
+ {renderRedirects()}
- <Route
- path="formatting/help"
- component={lazyLoadComponent(() => import('../components/FormattingHelp'))}
- />
-
- <Route component={lazyLoadComponent(() => import('../components/SimpleContainer'))}>
- <Route path="maintenance">{maintenanceRoutes}</Route>
- <Route path="setup">{setupRoutes}</Route>
- </Route>
-
- <Route component={MigrationContainer}>
- <Route
- component={lazyLoadComponent(() =>
- import('../components/SimpleSessionsContainer')
- )}>
- <RouteWithChildRoutes path="/sessions" childRoutes={sessionsRoutes} />
- </Route>
+ <Route
+ path="formatting/help"
+ component={lazyLoadComponent(() => import('../components/FormattingHelp'))}
+ />
- <Route path="/" component={App}>
- <IndexRoute
- component={lazyLoadComponent(() => import('../components/Landing'))}
- />
+ <Route component={lazyLoadComponent(() => import('../components/SimpleContainer'))}>
+ <Route path="maintenance">{maintenanceRoutes}</Route>
+ <Route path="setup">{setupRoutes}</Route>
+ </Route>
- <Route component={GlobalContainer}>
- <RouteWithChildRoutes path="account" childRoutes={accountRoutes} />
- <RouteWithChildRoutes path="coding_rules" childRoutes={codingRulesRoutes} />
- <RouteWithChildRoutes
- path="documentation"
- childRoutes={documentationRoutes}
- />
- <Route
- path="extension/:pluginKey/:extensionKey"
- component={lazyLoadComponent(() =>
- import('../components/extensions/GlobalPageExtension')
- )}
- />
- <Route
- path="issues"
- component={withIndexationGuard(Issues, PageContext.Issues)}
- />
- <RouteWithChildRoutes path="projects" childRoutes={projectsRoutes} />
- <RouteWithChildRoutes path="quality_gates" childRoutes={qualityGatesRoutes} />
- <Route
- path="portfolios"
- component={lazyLoadComponent(() =>
- import('../components/extensions/PortfoliosPage')
- )}
- />
- <RouteWithChildRoutes path="profiles" childRoutes={qualityProfilesRoutes} />
- <RouteWithChildRoutes path="web_api" childRoutes={webAPIRoutes} />
+ <Route component={MigrationContainer}>
+ <Route
+ component={lazyLoadComponent(() =>
+ import('../components/SimpleSessionsContainer')
+ )}>
+ <RouteWithChildRoutes path="/sessions" childRoutes={sessionsRoutes} />
+ </Route>
- {renderComponentRoutes()}
+ <Route path="/" component={App}>
+ <IndexRoute
+ component={lazyLoadComponent(() => import('../components/Landing'))}
+ />
- {renderAdminRoutes()}
- </Route>
- <Route
- // We don't want this route to have any menu.
- // That is why we can not have it under the accountRoutes
- path="account/reset_password"
- component={lazyLoadComponent(() => import('../components/ResetPassword'))}
- />
+ <Route component={GlobalContainer}>
+ <RouteWithChildRoutes path="account" childRoutes={accountRoutes} />
+ <RouteWithChildRoutes path="coding_rules" childRoutes={codingRulesRoutes} />
+ <RouteWithChildRoutes path="documentation" childRoutes={documentationRoutes} />
<Route
- // We don't want this route to have any menu. This is why we define it here
- // rather than under the admin routes.
- path="admin/change_admin_password"
+ path="extension/:pluginKey/:extensionKey"
component={lazyLoadComponent(() =>
- import('../../apps/change-admin-password/ChangeAdminPasswordApp')
+ import('../components/extensions/GlobalPageExtension')
)}
/>
<Route
- // We don't want this route to have any menu. This is why we define it here
- // rather than under the admin routes.
- path="admin/plugin_risk_consent"
- component={lazyLoadComponent(() => import('../components/PluginRiskConsent'))}
+ path="issues"
+ component={withIndexationGuard(Issues, PageContext.Issues)}
/>
+ <RouteWithChildRoutes path="projects" childRoutes={projectsRoutes} />
+ <RouteWithChildRoutes path="quality_gates" childRoutes={qualityGatesRoutes} />
<Route
- path="not_found"
- component={lazyLoadComponent(() => import('../components/NotFound'))}
- />
- <Route
- path="*"
- component={lazyLoadComponent(() => import('../components/NotFound'))}
+ path="portfolios"
+ component={lazyLoadComponent(() =>
+ import('../components/extensions/PortfoliosPage')
+ )}
/>
+ <RouteWithChildRoutes path="profiles" childRoutes={qualityProfilesRoutes} />
+ <RouteWithChildRoutes path="web_api" childRoutes={webAPIRoutes} />
+
+ {renderComponentRoutes()}
+
+ {renderAdminRoutes()}
</Route>
+ <Route
+ // We don't want this route to have any menu.
+ // That is why we can not have it under the accountRoutes
+ path="account/reset_password"
+ component={lazyLoadComponent(() => import('../components/ResetPassword'))}
+ />
+ <Route
+ // We don't want this route to have any menu. This is why we define it here
+ // rather than under the admin routes.
+ path="admin/change_admin_password"
+ component={lazyLoadComponent(() =>
+ import('../../apps/change-admin-password/ChangeAdminPasswordApp')
+ )}
+ />
+ <Route
+ // We don't want this route to have any menu. This is why we define it here
+ // rather than under the admin routes.
+ path="admin/plugin_risk_consent"
+ component={lazyLoadComponent(() => import('../components/PluginRiskConsent'))}
+ />
+ <Route
+ path="not_found"
+ component={lazyLoadComponent(() => import('../components/NotFound'))}
+ />
+ <Route
+ path="*"
+ component={lazyLoadComponent(() => import('../components/NotFound'))}
+ />
</Route>
- </Router>
- </IntlProvider>
- </CurrentUserContextProvider>
- </AppStateContextProvider>
- </Provider>
+ </Route>
+ </Router>
+ </IntlProvider>
+ </CurrentUserContextProvider>
+ </AppStateContextProvider>
</HelmetProvider>,
el
);
diff --git a/server/sonar-web/src/main/js/apps/change-admin-password/ChangeAdminPasswordAppRenderer.tsx b/server/sonar-web/src/main/js/apps/change-admin-password/ChangeAdminPasswordAppRenderer.tsx
index 448bb44a8fb..8536936cac5 100644
--- a/server/sonar-web/src/main/js/apps/change-admin-password/ChangeAdminPasswordAppRenderer.tsx
+++ b/server/sonar-web/src/main/js/apps/change-admin-password/ChangeAdminPasswordAppRenderer.tsx
@@ -18,7 +18,6 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import GlobalMessagesContainer from '../../app/components/GlobalMessagesContainer';
import { SubmitButton } from '../../components/controls/buttons';
import { Location } from '../../components/hoc/withRouter';
import { Alert } from '../../components/ui/Alert';
@@ -70,8 +69,6 @@ export default function ChangeAdminPasswordAppRenderer(props: ChangeAdminPasswor
</Alert>
) : (
<>
- <GlobalMessagesContainer />
-
<h1 className="text-center bg-danger big padded">
{translate('users.change_admin_password.instance_is_at_risk')}
</h1>
diff --git a/server/sonar-web/src/main/js/apps/change-admin-password/__tests__/ChangeAdminPasswordApp-test.tsx b/server/sonar-web/src/main/js/apps/change-admin-password/__tests__/ChangeAdminPasswordApp-test.tsx
index fe8e3e54412..054078b14cb 100644
--- a/server/sonar-web/src/main/js/apps/change-admin-password/__tests__/ChangeAdminPasswordApp-test.tsx
+++ b/server/sonar-web/src/main/js/apps/change-admin-password/__tests__/ChangeAdminPasswordApp-test.tsx
@@ -25,10 +25,6 @@ import { waitAndUpdate } from '../../../helpers/testUtils';
import { ChangeAdminPasswordApp } from '../ChangeAdminPasswordApp';
import { DEFAULT_ADMIN_LOGIN, DEFAULT_ADMIN_PASSWORD } from '../constants';
-jest.mock('react-redux', () => ({
- connect: jest.fn(() => (a: any) => a)
-}));
-
jest.mock('../../../api/users', () => ({
changePassword: jest.fn().mockResolvedValue(null)
}));
diff --git a/server/sonar-web/src/main/js/apps/change-admin-password/__tests__/__snapshots__/ChangeAdminPasswordAppRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/change-admin-password/__tests__/__snapshots__/ChangeAdminPasswordAppRenderer-test.tsx.snap
index 836b65c7375..c962cf50736 100644
--- a/server/sonar-web/src/main/js/apps/change-admin-password/__tests__/__snapshots__/ChangeAdminPasswordAppRenderer-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/change-admin-password/__tests__/__snapshots__/ChangeAdminPasswordAppRenderer-test.tsx.snap
@@ -9,7 +9,6 @@ exports[`should render correctly: cannot submit 1`] = `
<div
className="page-simple"
>
- <Connect(GlobalMessages) />
<h1
className="text-center bg-danger big padded"
>
@@ -94,7 +93,6 @@ exports[`should render correctly: default 1`] = `
<div
className="page-simple"
>
- <Connect(GlobalMessages) />
<h1
className="text-center bg-danger big padded"
>
@@ -179,7 +177,6 @@ exports[`should render correctly: submitting 1`] = `
<div
className="page-simple"
>
- <Connect(GlobalMessages) />
<h1
className="text-center bg-danger big padded"
>
@@ -292,7 +289,6 @@ exports[`should render correctly: trying to use default admin password 1`] = `
<div
className="page-simple"
>
- <Connect(GlobalMessages) />
<h1
className="text-center bg-danger big padded"
>
diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/AssigneeFacet-test.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/AssigneeFacet-test.tsx
index a133e859fc1..f3461980844 100644
--- a/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/AssigneeFacet-test.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/AssigneeFacet-test.tsx
@@ -22,8 +22,6 @@ import * as React from 'react';
import { Query } from '../../utils';
import AssigneeFacet from '../AssigneeFacet';
-jest.mock('../../../../store/rootReducer', () => ({}));
-
it('should render', () => {
expect(shallowRender({ assignees: ['foo'] })).toMatchSnapshot();
});
diff --git a/server/sonar-web/src/main/js/apps/projectDeletion/Form.tsx b/server/sonar-web/src/main/js/apps/projectDeletion/Form.tsx
index 01a1b86645f..aa519367723 100644
--- a/server/sonar-web/src/main/js/apps/projectDeletion/Form.tsx
+++ b/server/sonar-web/src/main/js/apps/projectDeletion/Form.tsx
@@ -20,7 +20,7 @@
import * as React from 'react';
import { deleteApplication } from '../../api/application';
import { deletePortfolio, deleteProject } from '../../api/components';
-import addGlobalSuccessMessage from '../../app/utils/addGlobalSuccessMessage';
+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';
diff --git a/server/sonar-web/src/main/js/apps/projectQualityGate/ProjectQualityGateApp.tsx b/server/sonar-web/src/main/js/apps/projectQualityGate/ProjectQualityGateApp.tsx
index f5b13837fbb..e86d860f149 100644
--- a/server/sonar-web/src/main/js/apps/projectQualityGate/ProjectQualityGateApp.tsx
+++ b/server/sonar-web/src/main/js/apps/projectQualityGate/ProjectQualityGateApp.tsx
@@ -26,7 +26,7 @@ import {
getGateForProject,
searchProjects
} from '../../api/quality-gates';
-import addGlobalSuccessMessage from '../../app/utils/addGlobalSuccessMessage';
+import { addGlobalSuccessMessage } from '../../app/utils/globalMessagesService';
import handleRequiredAuthorization from '../../app/utils/handleRequiredAuthorization';
import { translate } from '../../helpers/l10n';
import { Component, QualityGate } from '../../types/types';
diff --git a/server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/ProjectQualityGateApp-test.tsx b/server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/ProjectQualityGateApp-test.tsx
index 79a458c4baf..0d22641ab3b 100644
--- a/server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/ProjectQualityGateApp-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/ProjectQualityGateApp-test.tsx
@@ -62,7 +62,9 @@ jest.mock('../../../api/quality-gates', () => {
};
});
-jest.mock('../../../app/utils/addGlobalSuccessMessage', () => jest.fn());
+jest.mock('../../../app/utils/globalMessagesService', () => ({
+ addGlobalSuccessMessage: jest.fn()
+}));
jest.mock('../../../app/utils/handleRequiredAuthorization', () => jest.fn());
diff --git a/server/sonar-web/src/main/js/apps/projectQualityProfiles/ProjectQualityProfilesApp.tsx b/server/sonar-web/src/main/js/apps/projectQualityProfiles/ProjectQualityProfilesApp.tsx
index 4a997f43086..930558eef26 100644
--- a/server/sonar-web/src/main/js/apps/projectQualityProfiles/ProjectQualityProfilesApp.tsx
+++ b/server/sonar-web/src/main/js/apps/projectQualityProfiles/ProjectQualityProfilesApp.tsx
@@ -26,7 +26,7 @@ import {
Profile,
searchQualityProfiles
} from '../../api/quality-profiles';
-import addGlobalSuccessMessage from '../../app/utils/addGlobalSuccessMessage';
+import { addGlobalSuccessMessage } from '../../app/utils/globalMessagesService';
import handleRequiredAuthorization from '../../app/utils/handleRequiredAuthorization';
import { translateWithParameters } from '../../helpers/l10n';
import { isDefined } from '../../helpers/types';
diff --git a/server/sonar-web/src/main/js/apps/projectQualityProfiles/__tests__/ProjectQualityProfilesApp-test.tsx b/server/sonar-web/src/main/js/apps/projectQualityProfiles/__tests__/ProjectQualityProfilesApp-test.tsx
index 7e0bb24ba5f..df5c7cf9553 100644
--- a/server/sonar-web/src/main/js/apps/projectQualityProfiles/__tests__/ProjectQualityProfilesApp-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projectQualityProfiles/__tests__/ProjectQualityProfilesApp-test.tsx
@@ -71,7 +71,9 @@ jest.mock('../../../api/quality-profiles', () => {
};
});
-jest.mock('../../../app/utils/addGlobalSuccessMessage', () => jest.fn());
+jest.mock('../../../app/utils/globalMessagesService', () => ({
+ addGlobalSuccessMessage: jest.fn()
+}));
jest.mock('../../../app/utils/handleRequiredAuthorization', () => jest.fn());
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/Details.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/Details.tsx
index 3132ff9cffb..3767d0df7b9 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/Details.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/Details.tsx
@@ -20,7 +20,7 @@
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
import { fetchQualityGate } from '../../../api/quality-gates';
-import addGlobalSuccessMessage from '../../../app/utils/addGlobalSuccessMessage';
+import { addGlobalSuccessMessage } from '../../../app/utils/globalMessagesService';
import DeferredSpinner from '../../../components/ui/DeferredSpinner';
import { translate } from '../../../helpers/l10n';
import { Condition, QualityGate } from '../../../types/types';
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotOpenInIdeButton.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotOpenInIdeButton.tsx
index af216e4f858..8f6fa5f60a8 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotOpenInIdeButton.tsx
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotOpenInIdeButton.tsx
@@ -18,8 +18,10 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import addGlobalErrorMessage from '../../../app/utils/addGlobalErrorMessage';
-import addGlobalSuccessMessage from '../../../app/utils/addGlobalSuccessMessage';
+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';
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/assignee/Assignee.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/assignee/Assignee.tsx
index 51f34af34bc..e0e44703080 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/components/assignee/Assignee.tsx
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/assignee/Assignee.tsx
@@ -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/addGlobalSuccessMessage';
+import { addGlobalSuccessMessage } from '../../../../app/utils/globalMessagesService';
import { translate, translateWithParameters } from '../../../../helpers/l10n';
import { Hotspot, HotspotResolution, HotspotStatus } from '../../../../types/security-hotspots';
import { CurrentUser, isLoggedIn, UserActive } from '../../../../types/users';
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/assignee/__tests__/Assignee-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/assignee/__tests__/Assignee-test.tsx
index b67579254b7..b95aad482a0 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/components/assignee/__tests__/Assignee-test.tsx
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/assignee/__tests__/Assignee-test.tsx
@@ -20,7 +20,7 @@
import { shallow } from 'enzyme';
import * as React from 'react';
import { assignSecurityHotspot } from '../../../../../api/security-hotspots';
-import addGlobalSuccessMessage from '../../../../../app/utils/addGlobalSuccessMessage';
+import { addGlobalSuccessMessage } from '../../../../../app/utils/globalMessagesService';
import { mockHotspot } from '../../../../../helpers/mocks/security-hotspots';
import { mockCurrentUser, mockUser } from '../../../../../helpers/testMocks';
import { waitAndUpdate } from '../../../../../helpers/testUtils';
@@ -33,7 +33,9 @@ jest.mock('../../../../../api/security-hotspots', () => ({
assignSecurityHotspot: jest.fn()
}));
-jest.mock('../../../../../app/utils/addGlobalSuccessMessage', () => jest.fn());
+jest.mock('../../../../../app/utils/globalMessagesService', () => ({
+ addGlobalSuccessMessage: jest.fn()
+}));
it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot();
diff --git a/server/sonar-web/src/main/js/apps/sessions/components/Login.tsx b/server/sonar-web/src/main/js/apps/sessions/components/Login.tsx
index 1934450b65d..1de29b310b3 100644
--- a/server/sonar-web/src/main/js/apps/sessions/components/Login.tsx
+++ b/server/sonar-web/src/main/js/apps/sessions/components/Login.tsx
@@ -18,7 +18,6 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import GlobalMessagesContainer from '../../../app/components/GlobalMessagesContainer';
import { Location, withRouter } from '../../../components/hoc/withRouter';
import { Alert } from '../../../components/ui/Alert';
import { translate } from '../../../helpers/l10n';
@@ -55,8 +54,6 @@ export function Login(props: LoginProps) {
)}
<LoginForm collapsed={identityProviders.length > 0} onSubmit={props.onSubmit} />
-
- <GlobalMessagesContainer />
</div>
);
}
diff --git a/server/sonar-web/src/main/js/apps/sessions/components/LoginContainer.tsx b/server/sonar-web/src/main/js/apps/sessions/components/LoginContainer.tsx
index 294c57e47e8..e4a2a2a46e5 100644
--- a/server/sonar-web/src/main/js/apps/sessions/components/LoginContainer.tsx
+++ b/server/sonar-web/src/main/js/apps/sessions/components/LoginContainer.tsx
@@ -21,7 +21,8 @@ import { Location } from 'history';
import * as React from 'react';
import { logIn } from '../../../api/auth';
import { getIdentityProviders } from '../../../api/users';
-import addGlobalErrorMessage from '../../../app/utils/addGlobalErrorMessage';
+import { addGlobalErrorMessage } from '../../../app/utils/globalMessagesService';
+import { translate } from '../../../helpers/l10n';
import { getReturnUrl } from '../../../helpers/urls';
import { IdentityProvider } from '../../../types/types';
import Login from './Login';
@@ -66,7 +67,7 @@ export class LoginContainer extends React.PureComponent<Props, State> {
return logIn(id, password)
.then(this.handleSuccessfulLogin)
.catch(() => {
- addGlobalErrorMessage('Authentication failed');
+ addGlobalErrorMessage(translate('login.authentication_failed'));
return Promise.reject();
});
};
diff --git a/server/sonar-web/src/main/js/apps/sessions/components/Logout.tsx b/server/sonar-web/src/main/js/apps/sessions/components/Logout.tsx
index 4e4cae3b245..4064b879c8c 100644
--- a/server/sonar-web/src/main/js/apps/sessions/components/Logout.tsx
+++ b/server/sonar-web/src/main/js/apps/sessions/components/Logout.tsx
@@ -19,13 +19,12 @@
*/
import * as React from 'react';
import { logOut } from '../../../api/auth';
-import GlobalMessagesContainer from '../../../app/components/GlobalMessagesContainer';
import RecentHistory from '../../../app/components/RecentHistory';
-import addGlobalErrorMessage from '../../../app/utils/addGlobalErrorMessage';
+import { addGlobalErrorMessage } from '../../../app/utils/globalMessagesService';
import { translate } from '../../../helpers/l10n';
import { getBaseUrl } from '../../../helpers/system';
-export class Logout extends React.PureComponent<{}> {
+export default class Logout extends React.PureComponent<{}> {
componentDidMount() {
logOut()
.then(() => {
@@ -33,18 +32,15 @@ export class Logout extends React.PureComponent<{}> {
window.location.replace(getBaseUrl() + '/');
})
.catch(() => {
- addGlobalErrorMessage('Logout failed');
+ addGlobalErrorMessage(translate('login.logout_failed'));
});
}
render() {
return (
<div className="page page-limited">
- <GlobalMessagesContainer />
<div className="text-center">{translate('logging_out')}</div>
</div>
);
}
}
-
-export default Logout;
diff --git a/server/sonar-web/src/main/js/apps/sessions/components/__tests__/Logout-test.tsx b/server/sonar-web/src/main/js/apps/sessions/components/__tests__/Logout-test.tsx
index 5b0d0012913..01e81b89e13 100644
--- a/server/sonar-web/src/main/js/apps/sessions/components/__tests__/Logout-test.tsx
+++ b/server/sonar-web/src/main/js/apps/sessions/components/__tests__/Logout-test.tsx
@@ -20,17 +20,16 @@
import { shallow } from 'enzyme';
import * as React from 'react';
import { logOut } from '../../../../api/auth';
-import addGlobalErrorMessage from '../../../../app/utils/addGlobalErrorMessage';
+import { addGlobalErrorMessage } from '../../../../app/utils/globalMessagesService';
import { waitAndUpdate } from '../../../../helpers/testUtils';
-import { Logout } from '../Logout';
+import Logout from '../Logout';
jest.mock('../../../../api/auth', () => ({
logOut: jest.fn().mockResolvedValue(true)
}));
-jest.mock('../../../../app/utils/addGlobalErrorMessage', () => ({
- __esModule: true,
- default: jest.fn()
+jest.mock('../../../../app/utils/globalMessagesService', () => ({
+ addGlobalErrorMessage: jest.fn()
}));
const originalLocation = window.location;
diff --git a/server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/Login-test.tsx.snap b/server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/Login-test.tsx.snap
index d9ee013c6b7..22b570e17b1 100644
--- a/server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/Login-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/Login-test.tsx.snap
@@ -34,7 +34,6 @@ exports[`should render correctly: with authorization error 1`] = `
collapsed={true}
onSubmit={[MockFunction]}
/>
- <Connect(GlobalMessages) />
</div>
`;
@@ -65,7 +64,6 @@ exports[`should render correctly: with identity providers 1`] = `
collapsed={true}
onSubmit={[MockFunction]}
/>
- <Connect(GlobalMessages) />
</div>
`;
@@ -83,6 +81,5 @@ exports[`should render correctly: without any identity providers 1`] = `
collapsed={false}
onSubmit={[MockFunction]}
/>
- <Connect(GlobalMessages) />
</div>
`;
diff --git a/server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/Logout-test.tsx.snap b/server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/Logout-test.tsx.snap
index f510e29fcf7..49bf97a3c59 100644
--- a/server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/Logout-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/Logout-test.tsx.snap
@@ -4,7 +4,6 @@ exports[`should not redirect if logout fails 1`] = `
<div
className="page page-limited"
>
- <Connect(GlobalMessages) />
<div
className="text-center"
>
diff --git a/server/sonar-web/src/main/js/apps/users/components/PasswordForm.tsx b/server/sonar-web/src/main/js/apps/users/components/PasswordForm.tsx
index 86f9416a7fe..75f32f7c300 100644
--- a/server/sonar-web/src/main/js/apps/users/components/PasswordForm.tsx
+++ b/server/sonar-web/src/main/js/apps/users/components/PasswordForm.tsx
@@ -19,7 +19,7 @@
*/
import * as React from 'react';
import { changePassword } from '../../../api/users';
-import addGlobalSuccessMessage from '../../../app/utils/addGlobalSuccessMessage';
+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';
diff --git a/server/sonar-web/src/main/js/components/controls/ComponentReportActions.tsx b/server/sonar-web/src/main/js/components/controls/ComponentReportActions.tsx
index dcb3359e167..5cb685c46e9 100644
--- a/server/sonar-web/src/main/js/components/controls/ComponentReportActions.tsx
+++ b/server/sonar-web/src/main/js/components/controls/ComponentReportActions.tsx
@@ -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/addGlobalSuccessMessage';
+import { addGlobalSuccessMessage } from '../../app/utils/globalMessagesService';
import { translate, translateWithParameters } from '../../helpers/l10n';
import { AppState } from '../../types/appstate';
import { Branch } from '../../types/branch-like';
diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/ComponentReportActions-test.tsx b/server/sonar-web/src/main/js/components/controls/__tests__/ComponentReportActions-test.tsx
index 4e53e1a3982..c0c419e1ea6 100644
--- a/server/sonar-web/src/main/js/components/controls/__tests__/ComponentReportActions-test.tsx
+++ b/server/sonar-web/src/main/js/components/controls/__tests__/ComponentReportActions-test.tsx
@@ -24,7 +24,7 @@ import {
subscribeToEmailReport,
unsubscribeFromEmailReport
} from '../../../api/component-report';
-import addGlobalSuccessMessage from '../../../app/utils/addGlobalSuccessMessage';
+import { addGlobalSuccessMessage } from '../../../app/utils/globalMessagesService';
import { mockBranch } from '../../../helpers/mocks/branch-like';
import { mockComponent } from '../../../helpers/mocks/component';
import { mockComponentReportStatus } from '../../../helpers/mocks/component-report';
@@ -49,7 +49,9 @@ jest.mock('../../../helpers/system', () => ({
getBaseUrl: jest.fn().mockReturnValue('baseUrl')
}));
-jest.mock('../../../app/utils/addGlobalSuccessMessage', () => jest.fn());
+jest.mock('../../../app/utils/globalMessagesService', () => ({
+ addGlobalSuccessMessage: jest.fn()
+}));
beforeEach(jest.clearAllMocks);
diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/GlobalMessages-test.tsx b/server/sonar-web/src/main/js/components/controls/__tests__/GlobalMessages-test.tsx
deleted file mode 100644
index f7022a78ceb..00000000000
--- a/server/sonar-web/src/main/js/components/controls/__tests__/GlobalMessages-test.tsx
+++ /dev/null
@@ -1,78 +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 { shallow } from 'enzyme';
-import { matchers } from '@emotion/jest';
-import * as React from 'react';
-import { colors } from '../../../app/theme';
-import GlobalMessages, { GlobalMessagesProps } from '../GlobalMessages';
-
-expect.extend(matchers);
-
-it('should not render when no message', () => {
- expect(shallowRender({ messages: [] }).type()).toBeNull();
-});
-
-it('should render correctly with a message', () => {
- const wrapper = shallowRender();
- expect(wrapper).toMatchSnapshot();
- expect(
- wrapper
- .find('GlobalMessage')
- .first()
- .dive()
- ).toMatchSnapshot();
- expect(
- wrapper
- .find('GlobalMessage')
- .last()
- .dive()
- ).toMatchSnapshot();
-});
-
-it('should render with correct css', () => {
- const wrapper = shallowRender();
- expect(wrapper.render()).toMatchSnapshot();
- expect(
- wrapper
- .find('GlobalMessage')
- .first()
- .render()
- ).toHaveStyleRule('background-color', colors.red);
-
- expect(
- wrapper
- .find('GlobalMessage')
- .last()
- .render()
- ).toHaveStyleRule('background-color', colors.green);
-});
-
-function shallowRender(props: Partial<GlobalMessagesProps> = {}) {
- return shallow(
- <GlobalMessages
- closeGlobalMessage={jest.fn()}
- messages={[
- { id: '1', level: 'ERROR', message: 'Test' },
- { id: '2', level: 'SUCCESS', message: 'Test 2' }
- ]}
- {...props}
- />
- );
-}
diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/GlobalMessages-test.tsx.snap b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/GlobalMessages-test.tsx.snap
deleted file mode 100644
index af1e41ecb7a..00000000000
--- a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/GlobalMessages-test.tsx.snap
+++ /dev/null
@@ -1,212 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly with a message 1`] = `
-<Styled(div)>
- <GlobalMessage
- closeGlobalMessage={[MockFunction]}
- key="1"
- message={
- Object {
- "id": "1",
- "level": "ERROR",
- "message": "Test",
- }
- }
- />
- <GlobalMessage
- closeGlobalMessage={[MockFunction]}
- key="2"
- message={
- Object {
- "id": "2",
- "level": "SUCCESS",
- "message": "Test 2",
- }
- }
- />
-</Styled(div)>
-`;
-
-exports[`should render correctly with a message 2`] = `
-<Styled(div)
- data-test="global-message__ERROR"
- level="ERROR"
- role="alert"
->
- Test
- <Styled(ClearButton)
- className="button-small"
- color="#fff"
- level="ERROR"
- onClick={[Function]}
- />
-</Styled(div)>
-`;
-
-exports[`should render correctly with a message 3`] = `
-<Styled(div)
- data-test="global-message__SUCCESS"
- level="SUCCESS"
- role="status"
->
- Test 2
- <Styled(ClearButton)
- className="button-small"
- color="#fff"
- level="SUCCESS"
- onClick={[Function]}
- />
-</Styled(div)>
-`;
-
-exports[`should render with correct css 1`] = `
-@keyframes animation-0 {
- from {
- opacity: 0;
- }
-
- to {
- opacity: 1;
- }
-}
-
-@keyframes animation-0 {
- from {
- opacity: 0;
- }
-
- to {
- opacity: 1;
- }
-}
-
-.emotion-4 {
- position: fixed;
- z-index: 7000;
- top: 0;
- left: 50%;
- width: 350px;
- margin-left: -175px;
-}
-
-.emotion-1 {
- position: relative;
- padding: 0 30px 0 10px;
- line-height: 24px;
- border-radius: 0 0 3px 3px;
- box-sizing: border-box;
- color: #ffffff;
- background-color: #d4333f;
- text-align: center;
- opacity: 0;
- -webkit-animation: animation-0 0.2s ease forwards;
- animation: animation-0 0.2s ease forwards;
-}
-
-.emotion-1+.emotion-1 {
- margin-top: calc(8px / 2);
- border-radius: 3px;
-}
-
-.emotion-0 {
- position: absolute;
- top: calc(8px / 4);
- right: calc(8px / 4);
-}
-
-.emotion-0:hover svg,
-.emotion-0:focus svg {
- color: #d4333f;
-}
-
-.emotion-3 {
- position: relative;
- padding: 0 30px 0 10px;
- line-height: 24px;
- border-radius: 0 0 3px 3px;
- box-sizing: border-box;
- color: #ffffff;
- background-color: #00aa00;
- text-align: center;
- opacity: 0;
- -webkit-animation: animation-0 0.2s ease forwards;
- animation: animation-0 0.2s ease forwards;
-}
-
-.emotion-3+.emotion-3 {
- margin-top: calc(8px / 2);
- border-radius: 3px;
-}
-
-.emotion-2 {
- position: absolute;
- top: calc(8px / 4);
- right: calc(8px / 4);
-}
-
-.emotion-2:hover svg,
-.emotion-2:focus svg {
- color: #00aa00;
-}
-
-<div
- class="emotion-4"
->
- <div
- class="emotion-1"
- data-test="global-message__ERROR"
- role="alert"
- >
- Test
- <button
- class="button button-small emotion-0 button-icon"
- level="ERROR"
- style="color:#fff"
- type="button"
- >
- <svg
- height="16"
- space="preserve"
- style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421"
- version="1.1"
- viewBox="0 0 16 16"
- width="16"
- xlink="http://www.w3.org/1999/xlink"
- >
- <path
- d="M14 4.242L11.758 2l-3.76 3.76L4.242 2 2 4.242l3.756 3.756L2 11.758 4.242 14l3.756-3.76 3.76 3.76L14 11.758l-3.76-3.76L14 4.242z"
- style="fill:currentColor"
- />
- </svg>
- </button>
- </div>
- <div
- class="emotion-3"
- data-test="global-message__SUCCESS"
- role="status"
- >
- Test 2
- <button
- class="button button-small emotion-2 button-icon"
- level="SUCCESS"
- style="color:#fff"
- type="button"
- >
- <svg
- height="16"
- space="preserve"
- style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421"
- version="1.1"
- viewBox="0 0 16 16"
- width="16"
- xlink="http://www.w3.org/1999/xlink"
- >
- <path
- d="M14 4.242L11.758 2l-3.76 3.76L4.242 2 2 4.242l3.756 3.756L2 11.758 4.242 14l3.756-3.76 3.76 3.76L14 11.758l-3.76-3.76L14 4.242z"
- style="fill:currentColor"
- />
- </svg>
- </button>
- </div>
-</div>
-`;
diff --git a/server/sonar-web/src/main/js/helpers/__tests__/error-test.ts b/server/sonar-web/src/main/js/helpers/__tests__/error-test.ts
index ca6f81a60e9..cf126933261 100644
--- a/server/sonar-web/src/main/js/helpers/__tests__/error-test.ts
+++ b/server/sonar-web/src/main/js/helpers/__tests__/error-test.ts
@@ -17,19 +17,27 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import getStore from '../../app/utils/getStore';
+import { addGlobalErrorMessage } from '../../app/utils/globalMessagesService';
import { throwGlobalError } from '../error';
+jest.mock('../../app/utils/globalMessagesService', () => ({
+ addGlobalErrorMessage: jest.fn()
+}));
+
beforeAll(() => {
jest.useFakeTimers();
});
+beforeEach(() => {
+ jest.clearAllMocks();
+});
+
afterAll(() => {
jest.runOnlyPendingTimers();
jest.useRealTimers();
});
-it('should put the error message in the store', async () => {
+it('should display the error message', async () => {
const response = new Response();
response.json = jest.fn().mockResolvedValue({ errors: [{ msg: 'error 1' }] });
@@ -40,13 +48,10 @@ it('should put the error message in the store', async () => {
})
.catch(() => {});
- expect(getStore().getState().globalMessages[0]).toMatchObject({
- level: 'ERROR',
- message: 'error 1'
- });
+ expect(addGlobalErrorMessage).toBeCalledWith('error 1');
});
-it('should put a default error messsage in the store', async () => {
+it('should display the default error messsage', async () => {
const response = new Response();
response.json = jest.fn().mockResolvedValue({});
@@ -57,10 +62,7 @@ it('should put a default error messsage in the store', async () => {
})
.catch(() => {});
- expect(getStore().getState().globalMessages[0]).toMatchObject({
- level: 'ERROR',
- message: 'default_error_message'
- });
+ expect(addGlobalErrorMessage).toBeCalledWith('default_error_message');
});
it('should handle weird response types', () => {
diff --git a/server/sonar-web/src/main/js/helpers/error.ts b/server/sonar-web/src/main/js/helpers/error.ts
index 78f2fa12d9a..70ffdcc1795 100644
--- a/server/sonar-web/src/main/js/helpers/error.ts
+++ b/server/sonar-web/src/main/js/helpers/error.ts
@@ -17,13 +17,10 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import getStore from '../app/utils/getStore';
-import { addGlobalErrorMessage } from '../store/globalMessages';
+import { addGlobalErrorMessage } from '../app/utils/globalMessagesService';
import { parseError } from './request';
export function throwGlobalError(param: Response | any): Promise<Response | any> {
- const store = getStore();
-
if (param.response instanceof Response) {
/* eslint-disable-next-line no-console */
console.warn('DEPRECATED: response should not be wrapped, pass it directly.');
@@ -32,12 +29,9 @@ export function throwGlobalError(param: Response | any): Promise<Response | any>
if (param instanceof Response) {
return parseError(param)
- .then(
- message => {
- store.dispatch(addGlobalErrorMessage(message));
- },
- () => {}
- )
+ .then(addGlobalErrorMessage, () => {
+ /* ignore parsing errors */
+ })
.then(() => Promise.reject(param));
}
diff --git a/server/sonar-web/src/main/js/helpers/testMocks.ts b/server/sonar-web/src/main/js/helpers/testMocks.ts
index ff43df271a4..437ed0de99e 100644
--- a/server/sonar-web/src/main/js/helpers/testMocks.ts
+++ b/server/sonar-web/src/main/js/helpers/testMocks.ts
@@ -19,7 +19,6 @@
*/
import { Location, LocationDescriptor } from 'history';
import { InjectedRouter } from 'react-router';
-import { createStore, Store } from 'redux';
import { DocumentationEntry } from '../apps/documentation/utils';
import { Exporter, Profile } from '../apps/quality-profiles/types';
import { AppState } from '../types/appstate';
@@ -712,10 +711,6 @@ export function mockStandaloneSysInfo(overrides: Partial<any> = {}): SysInfoStan
};
}
-export function mockStore(state: any = {}, reducer = (state: any) => state): Store {
- return createStore(reducer, state);
-}
-
export function mockUser(overrides: Partial<User> = {}): User {
return {
active: true,
diff --git a/server/sonar-web/src/main/js/helpers/testReactTestingUtils.tsx b/server/sonar-web/src/main/js/helpers/testReactTestingUtils.tsx
index 989783705fb..e5119855674 100644
--- a/server/sonar-web/src/main/js/helpers/testReactTestingUtils.tsx
+++ b/server/sonar-web/src/main/js/helpers/testReactTestingUtils.tsx
@@ -22,7 +22,6 @@ import { History } from 'history';
import * as React from 'react';
import { HelmetProvider } from 'react-helmet-async';
import { IntlProvider } from 'react-intl';
-import { Provider } from 'react-redux';
import {
createMemoryHistory,
Route,
@@ -32,15 +31,12 @@ import {
withRouter,
WithRouterProps
} from 'react-router';
-import { Store } from 'redux';
import AdminContext from '../app/components/AdminContext';
import AppStateContextProvider from '../app/components/app-state/AppStateContextProvider';
import CurrentUserContextProvider from '../app/components/current-user/CurrentUserContextProvider';
import { LanguagesContext } from '../app/components/languages/LanguagesContext';
import { MetricsContext } from '../app/components/metrics/MetricsContext';
-import getStore from '../app/utils/getStore';
import { RouteWithChildRoutes } from '../app/utils/startReactApp';
-import { Store as State } from '../store/rootReducer';
import { AppState } from '../types/appstate';
import { Dict, Extension, Languages, Metric, SysStatus } from '../types/types';
import { CurrentUser } from '../types/users';
@@ -49,7 +45,6 @@ import { mockAppState, mockCurrentUser } from './testMocks';
interface RenderContext {
metrics?: Dict<Metric>;
- store?: Store<State, any>;
history?: History;
appState?: AppState;
languages?: Languages;
@@ -141,7 +136,6 @@ function renderRoutedApp(
currentUser = mockCurrentUser(),
navigateTo = indexPath,
metrics = DEFAULT_METRICS,
- store = getStore(),
appState = mockAppState(),
history = createMemoryHistory(),
languages = {}
@@ -152,18 +146,16 @@ function renderRoutedApp(
<HelmetProvider context={{}}>
<IntlProvider defaultLocale="en" locale="en">
<MetricsContext.Provider value={metrics}>
- <Provider store={store}>
- <LanguagesContext.Provider value={languages}>
- <CurrentUserContextProvider currentUser={currentUser}>
- <AppStateContextProvider appState={appState}>
- <Router history={history}>
- {children}
- <Route path="*" component={CatchAll} />
- </Router>
- </AppStateContextProvider>
- </CurrentUserContextProvider>
- </LanguagesContext.Provider>
- </Provider>
+ <LanguagesContext.Provider value={languages}>
+ <CurrentUserContextProvider currentUser={currentUser}>
+ <AppStateContextProvider appState={appState}>
+ <Router history={history}>
+ {children}
+ <Route path="*" component={CatchAll} />
+ </Router>
+ </AppStateContextProvider>
+ </CurrentUserContextProvider>
+ </LanguagesContext.Provider>
</MetricsContext.Provider>
</IntlProvider>
</HelmetProvider>
diff --git a/server/sonar-web/src/main/js/store/globalMessages.ts b/server/sonar-web/src/main/js/store/globalMessages.ts
deleted file mode 100644
index 58776e7d253..00000000000
--- a/server/sonar-web/src/main/js/store/globalMessages.ts
+++ /dev/null
@@ -1,84 +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 { Dispatch } from 'redux';
-import { ActionType } from '../types/actions';
-
-export enum MessageLevel {
- Error = 'ERROR',
- Success = 'SUCCESS'
-}
-
-interface Message {
- id: string;
- message: string;
- level: MessageLevel;
-}
-
-const MESSAGE_DISPLAY_TIME = 5000;
-
-/* Action creators */
-
-function addGlobalMessageActionCreator(id: string, message: string, level: MessageLevel) {
- return { type: 'ADD_GLOBAL_MESSAGE', message, level, id };
-}
-
-export function closeGlobalMessage(id: string) {
- return { type: 'CLOSE_GLOBAL_MESSAGE', id };
-}
-
-type Action =
- | ActionType<typeof addGlobalMessageActionCreator, 'ADD_GLOBAL_MESSAGE'>
- | ActionType<typeof closeGlobalMessage, 'CLOSE_GLOBAL_MESSAGE'>;
-
-function addGlobalMessage(message: string, level: MessageLevel) {
- return (dispatch: Dispatch) => {
- const id = uniqueId('global-message-');
- dispatch(addGlobalMessageActionCreator(id, message, level));
- setTimeout(() => dispatch(closeGlobalMessage(id)), MESSAGE_DISPLAY_TIME);
- };
-}
-
-export function addGlobalErrorMessage(message: string) {
- return addGlobalMessage(message, MessageLevel.Error);
-}
-
-export function addGlobalSuccessMessage(message: string) {
- return addGlobalMessage(message, MessageLevel.Success);
-}
-
-export type State = Message[];
-
-export default function globalMessagesReducer(state: State = [], action: Action): State {
- switch (action.type) {
- case 'ADD_GLOBAL_MESSAGE':
- return [{ id: action.id, message: action.message, level: action.level }];
-
- case 'CLOSE_GLOBAL_MESSAGE':
- return state.filter(message => message.id !== action.id);
-
- default:
- return state;
- }
-}
-
-export function getGlobalMessages(state: State) {
- return state;
-}
diff --git a/server/sonar-web/src/main/js/types/extension.ts b/server/sonar-web/src/main/js/types/extension.ts
index 73324b6ce66..2540bc44cfe 100644
--- a/server/sonar-web/src/main/js/types/extension.ts
+++ b/server/sonar-web/src/main/js/types/extension.ts
@@ -18,9 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { IntlShape } from 'react-intl';
-import { Store as ReduxStore } from 'redux';
import { Location, Router } from '../components/hoc/withRouter';
-import { Store } from '../store/rootReducer';
import { AppState } from './appstate';
import { L10nBundle } from './l10nBundle';
import { Dict } from './types';
@@ -41,7 +39,6 @@ export interface ExtensionStartMethod {
export interface ExtensionStartMethodParameter {
appState: AppState;
- store: ReduxStore<Store, any>;
el: HTMLElement | undefined | null;
currentUser: CurrentUser;
intl: IntlShape;
diff --git a/server/sonar-web/src/main/js/app/utils/addGlobalErrorMessage.ts b/server/sonar-web/src/main/js/types/globalMessages.ts
index 39ba68b70c0..dec0e9a3c8e 100644
--- a/server/sonar-web/src/main/js/app/utils/addGlobalErrorMessage.ts
+++ b/server/sonar-web/src/main/js/types/globalMessages.ts
@@ -17,10 +17,13 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import * as globalMessages from '../../store/globalMessages';
-import getStore from './getStore';
+export enum MessageLevel {
+ Error = 'ERROR',
+ Success = 'SUCCESS'
+}
-export default function addGlobalErrorMessage(message: string): void {
- const store = getStore();
- store.dispatch(globalMessages.addGlobalErrorMessage(message));
+export interface Message {
+ id: string;
+ level: MessageLevel;
+ text: string;
}