"react-helmet-async": "1.2.3",
"react-intl": "3.12.1",
"react-modal": "3.14.4",
- "react-redux": "5.1.1",
"react-router": "3.2.6",
"react-select": "4.3.1",
"react-virtualized": "9.22.3",
- "redux": "4.1.2",
- "redux-thunk": "2.4.1",
"regenerator-runtime": "0.13.9",
"rehype-raw": "4.0.2",
"rehype-react": "5.0.0",
"@types/react-dom": "16.8.4",
"@types/react-helmet": "5.0.15",
"@types/react-modal": "3.13.1",
- "@types/react-redux": "6.0.6",
"@types/react-router": "3.0.20",
"@types/react-select": "4.0.16",
"@types/react-virtualized": "9.21.20",
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';
<LanguagesContextProvider>
<MetricsContextProvider>
<GlobalNav location={props.location} />
- <GlobalMessagesContainer />
<IndexationNotification />
<UpdateNotification dismissable={true} />
{props.children}
--- /dev/null
+/*
+ * 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 { keyframes } from '@emotion/react';
+import styled from '@emotion/styled';
+import * as React from 'react';
+import { ClearButton } from '../../components/controls/buttons';
+import { cutLongWords } from '../../helpers/path';
+import { Message } from '../../types/globalMessages';
+import { colors, sizes } from '../theme';
+
+export interface GlobalMessageProps {
+ closeGlobalMessage: (id: string) => void;
+ message: Message;
+}
+
+export default function GlobalMessage(props: GlobalMessageProps) {
+ const { message } = props;
+ return (
+ <MessageBox
+ data-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}
+ onClick={() => props.closeGlobalMessage(message.id)}
+ />
+ </MessageBox>
+ );
+}
+
+const appearAnim = keyframes`
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+`;
+
+const MessageBox = styled.div<Pick<Message, 'level'>>`
+ position: relative;
+ padding: 0 30px 0 10px;
+ line-height: ${sizes.controlHeight};
+ border-radius: 0 0 3px 3px;
+ box-sizing: border-box;
+ color: #ffffff;
+ background-color: ${({ level }) => (level === 'SUCCESS' ? colors.green : colors.red)};
+ text-align: center;
+ opacity: 0;
+ animation: ${appearAnim} 0.2s ease forwards;
+
+ & + & {
+ margin-top: calc(${sizes.gridSize} / 2);
+ border-radius: 3px;
+ }
+`;
+
+const CloseButton = styled(ClearButton)<Pick<Message, 'level'>>`
+ position: absolute;
+ top: calc(${sizes.gridSize} / 4);
+ right: calc(${sizes.gridSize} / 4);
+
+ &.button-icon:hover svg,
+ &.button-icon:focus svg {
+ color: ${({ level }) => (level === 'SUCCESS' ? colors.green : colors.red)};
+ }
+`;
* 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;
+`;
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 {
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>
import { translate } from '../../helpers/l10n';
import { getBaseUrl } from '../../helpers/system';
import { LoggedInUser } from '../../types/users';
-import GlobalMessagesContainer from './GlobalMessagesContainer';
export interface ResetPasswordProps {
currentUser: LoggedInUser;
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')}
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()
--- /dev/null
+/*
+ * 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 { screen } from '@testing-library/react';
+import { renderComponentApp } from '../../../helpers/testReactTestingUtils';
+import { addGlobalErrorMessage, addGlobalSuccessMessage } from '../../utils/globalMessagesService';
+import GlobalMessagesContainer from '../GlobalMessagesContainer';
+
+it('should display messages', () => {
+ renderComponentApp('sonarqube', GlobalMessagesContainer);
+
+ addGlobalErrorMessage('This is an error');
+ addGlobalSuccessMessage('This was a triumph!');
+
+ expect(screen.getByRole('alert')).toHaveTextContent('This is an error');
+ expect(screen.getByRole('status')).toHaveTextContent('This was a triumph!');
+});
}
}
/>
- <Connect(GlobalMessages) />
<withCurrentUserContext(withIndexationContext(IndexationNotification)) />
<withCurrentUserContext(withAppStateContext(UpdateNotification))
dismissable={true}
<div
className="plugin-risk-consent-page"
>
- <Connect(GlobalMessages) />
<div
className="plugin-risk-consent-content boxed-group"
>
<div
className="page-simple"
>
- <Connect(GlobalMessages) />
<h1
className="text-center huge"
>
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';
currentUser: CurrentUser;
extension: TypeExtension;
location: Location;
- onFail: (message: string) => void;
options?: Dict<any>;
router: Router;
}
}
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,
};
handleFailure = () => {
- this.props.onFail(translate('page_extension_failed'));
+ addGlobalErrorMessage(translate('page_extension_failed'));
};
startExtension() {
}
}
-export default injectIntl(
- withRouter(
- withAppStateContext(
- withCurrentUserContext(connect(null, { onFail: addGlobalErrorMessage })(Extension))
- )
- )
-);
+export default injectIntl(withRouter(withAppStateContext(withCurrentUserContext(Extension))));
* 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';
params: { extensionKey: string; pluginKey: string };
}
-export function ProjectAdminPageExtension(props: ProjectAdminPageExtensionProps) {
+export default function ProjectAdminPageExtension(props: ProjectAdminPageExtensionProps) {
const {
component,
params: { extensionKey, pluginKey }
<NotFound withContainer={false} />
);
}
-
-const mapDispatchToProps = { onFail: addGlobalErrorMessage };
-
-export default connect(null, mapDispatchToProps)(ProjectAdminPageExtension);
*/
import { mount } from 'enzyme';
import * as React from 'react';
+import { IntlShape } from 'react-intl';
import { getExtensionStart } from '../../../../helpers/extensions';
import {
mockAppState,
appState={mockAppState()}
currentUser={mockCurrentUser()}
extension={{ key: 'foo', name: 'Foo' }}
- intl={{} as any}
+ intl={{} as IntlShape}
location={mockLocation()}
- onFail={jest.fn()}
router={mockRouter()}
{...props}
/>
import { shallow } from 'enzyme';
import * as React from 'react';
import { mockComponent } from '../../../../helpers/mocks/component';
-import {
- ProjectAdminPageExtension,
+import ProjectAdminPageExtension, {
ProjectAdminPageExtensionProps
} from '../ProjectAdminPageExtension';
"state": Object {},
}
}
- onFail={[MockFunction]}
router={
Object {
"createHref": [MockFunction],
"state": Object {},
}
}
- onFail={[MockFunction]}
router={
Object {
"createHref": [MockFunction],
// 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",
// 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",
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';
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 () => {
--- /dev/null
+/*
+ * 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 })
+ );
+});
+++ /dev/null
-/*
- * 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 addGlobalErrorMessage(message: string): void {
- const store = getStore();
- store.dispatch(globalMessages.addGlobalErrorMessage(message));
-}
+++ /dev/null
-/*
- * 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));
-}
+++ /dev/null
-/*
- * 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());
--- /dev/null
+/*
+ * 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';
+
+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 addGlobalSuccessMessage(text: string) {
+ addMessage(text, MessageLevel.Success);
+}
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';
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;
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
);
* 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';
</Alert>
) : (
<>
- <GlobalMessagesContainer />
-
<h1 className="text-center bg-danger big padded">
{translate('users.change_admin_password.instance_is_at_risk')}
</h1>
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)
}));
<div
className="page-simple"
>
- <Connect(GlobalMessages) />
<h1
className="text-center bg-danger big padded"
>
<div
className="page-simple"
>
- <Connect(GlobalMessages) />
<h1
className="text-center bg-danger big padded"
>
<div
className="page-simple"
>
- <Connect(GlobalMessages) />
<h1
className="text-center bg-danger big padded"
>
<div
className="page-simple"
>
- <Connect(GlobalMessages) />
<h1
className="text-center bg-danger big padded"
>
import { Query } from '../../utils';
import AssigneeFacet from '../AssigneeFacet';
-jest.mock('../../../../store/rootReducer', () => ({}));
-
it('should render', () => {
expect(shallowRender({ assignees: ['foo'] })).toMatchSnapshot();
});
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';
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';
};
});
-jest.mock('../../../app/utils/addGlobalSuccessMessage', () => jest.fn());
+jest.mock('../../../app/utils/globalMessagesService', () => ({
+ addGlobalSuccessMessage: jest.fn()
+}));
jest.mock('../../../app/utils/handleRequiredAuthorization', () => jest.fn());
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';
};
});
-jest.mock('../../../app/utils/addGlobalSuccessMessage', () => jest.fn());
+jest.mock('../../../app/utils/globalMessagesService', () => ({
+ addGlobalSuccessMessage: jest.fn()
+}));
jest.mock('../../../app/utils/handleRequiredAuthorization', () => jest.fn());
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';
* 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';
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';
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';
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();
* 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';
)}
<LoginForm collapsed={identityProviders.length > 0} onSubmit={props.onSubmit} />
-
- <GlobalMessagesContainer />
</div>
);
}
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';
return logIn(id, password)
.then(this.handleSuccessfulLogin)
.catch(() => {
- addGlobalErrorMessage('Authentication failed');
+ addGlobalErrorMessage(translate('login.authentication_failed'));
return Promise.reject();
});
};
*/
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(() => {
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;
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;
collapsed={true}
onSubmit={[MockFunction]}
/>
- <Connect(GlobalMessages) />
</div>
`;
collapsed={true}
onSubmit={[MockFunction]}
/>
- <Connect(GlobalMessages) />
</div>
`;
collapsed={false}
onSubmit={[MockFunction]}
/>
- <Connect(GlobalMessages) />
</div>
`;
<div
className="page page-limited"
>
- <Connect(GlobalMessages) />
<div
className="text-center"
>
*/
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';
} 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';
+++ /dev/null
-/*
- * 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 { keyframes } from '@emotion/react';
-import styled from '@emotion/styled';
-import * as React from 'react';
-import { colors, sizes, zIndexes } from '../../app/theme';
-import { cutLongWords } from '../../helpers/path';
-import { ClearButton } from './buttons';
-
-interface IMessage {
- id: string;
- level: 'ERROR' | 'SUCCESS';
- message: string;
-}
-
-export interface GlobalMessagesProps {
- closeGlobalMessage: (id: string) => void;
- messages: IMessage[];
-}
-
-export default function GlobalMessages({ closeGlobalMessage, messages }: GlobalMessagesProps) {
- if (messages.length === 0) {
- return null;
- }
-
- 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}`}
- 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>
- );
- }
-}
-
-const appearAnim = keyframes`
- from {
- opacity: 0;
- }
- to {
- opacity: 1;
- }
-`;
-
-const Message = styled.div<Pick<IMessage, 'level'>>`
- position: relative;
- padding: 0 30px 0 10px;
- line-height: ${sizes.controlHeight};
- border-radius: 0 0 3px 3px;
- box-sizing: border-box;
- color: #ffffff;
- background-color: ${({ level }) => (level === 'SUCCESS' ? colors.green : colors.red)};
- text-align: center;
- opacity: 0;
- animation: ${appearAnim} 0.2s ease forwards;
-
- & + & {
- margin-top: calc(${sizes.gridSize} / 2);
- border-radius: 3px;
- }
-`;
-
-const CloseButton = styled(ClearButton)<Pick<IMessage, 'level'>>`
- position: absolute;
- top: calc(${sizes.gridSize} / 4);
- right: calc(${sizes.gridSize} / 4);
-
- &:hover svg,
- &:focus svg {
- color: ${({ level }) => (level === 'SUCCESS' ? colors.green : colors.red)};
- }
-`;
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';
getBaseUrl: jest.fn().mockReturnValue('baseUrl')
}));
-jest.mock('../../../app/utils/addGlobalSuccessMessage', () => jest.fn());
+jest.mock('../../../app/utils/globalMessagesService', () => ({
+ addGlobalSuccessMessage: jest.fn()
+}));
beforeEach(jest.clearAllMocks);
+++ /dev/null
-/*
- * 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}
- />
- );
-}
+++ /dev/null
-// 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>
-`;
* 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' }] });
})
.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({});
})
.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', () => {
* 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.');
if (param instanceof Response) {
return parseError(param)
- .then(
- message => {
- store.dispatch(addGlobalErrorMessage(message));
- },
- () => {}
- )
+ .then(addGlobalErrorMessage, () => {
+ /* ignore parsing errors */
+ })
.then(() => Promise.reject(param));
}
*/
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';
};
}
-export function mockStore(state: any = {}, reducer = (state: any) => state): Store {
- return createStore(reducer, state);
-}
-
export function mockUser(overrides: Partial<User> = {}): User {
return {
active: true,
import * as React from 'react';
import { HelmetProvider } from 'react-helmet-async';
import { IntlProvider } from 'react-intl';
-import { Provider } from 'react-redux';
import {
createMemoryHistory,
Route,
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';
interface RenderContext {
metrics?: Dict<Metric>;
- store?: Store<State, any>;
history?: History;
appState?: AppState;
languages?: Languages;
currentUser = mockCurrentUser(),
navigateTo = indexPath,
metrics = DEFAULT_METRICS,
- store = getStore(),
appState = mockAppState(),
history = createMemoryHistory(),
languages = {}
<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>
+++ /dev/null
-/*
- * 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 globalMessagesReducer, { MessageLevel } from '../globalMessages';
-
-describe('globalMessagesReducer', () => {
- it('should handle ADD_GLOBAL_MESSAGE', () => {
- const actionAttributes = { id: 'id', message: 'There was an error', level: MessageLevel.Error };
-
- expect(
- globalMessagesReducer([], {
- type: 'ADD_GLOBAL_MESSAGE',
- ...actionAttributes
- })
- ).toEqual([actionAttributes]);
- });
-
- 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(globalMessagesReducer(state, { type: 'CLOSE_GLOBAL_MESSAGE', id: 'm2' })).toEqual([
- state[0]
- ]);
- });
-});
+++ /dev/null
-/*
- * 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;
-}
+++ /dev/null
-/*
- * 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 { combineReducers } from 'redux';
-import globalMessages, * as fromGlobalMessages from './globalMessages';
-
-export type Store = {
- globalMessages: fromGlobalMessages.State;
-};
-
-export default combineReducers<Store>({
- globalMessages
-});
-
-export function getGlobalMessages(state: Store) {
- return fromGlobalMessages.getGlobalMessages(state.globalMessages);
-}
+++ /dev/null
-/*
- * 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 { applyMiddleware, compose, createStore } from 'redux';
-import thunk, { ThunkMiddleware } from 'redux-thunk';
-
-type RootReducer = typeof import('../rootReducer').default;
-type State = import('../rootReducer').Store;
-
-const middlewares = [thunk as ThunkMiddleware<State, any>];
-const composed = [];
-
-if (process.env.NODE_ENV === 'development') {
- const { __REDUX_DEVTOOLS_EXTENSION__ } = window as any;
- composed.push(__REDUX_DEVTOOLS_EXTENSION__ ? __REDUX_DEVTOOLS_EXTENSION__() : (f: Function) => f);
-}
-
-const finalCreateStore = compose(applyMiddleware(...middlewares), ...composed)(createStore);
-
-export default function configureStore(rootReducer: RootReducer, initialState?: State) {
- return finalCreateStore(rootReducer, initialState);
-}
* 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';
export interface ExtensionStartMethodParameter {
appState: AppState;
- store: ReduxStore<Store, any>;
el: HTMLElement | undefined | null;
currentUser: CurrentUser;
intl: IntlShape;
--- /dev/null
+/*
+ * 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.
+ */
+export enum MessageLevel {
+ Error = 'ERROR',
+ Success = 'SUCCESS'
+}
+
+export interface Message {
+ id: string;
+ level: MessageLevel;
+ text: string;
+}
languageName: node
linkType: hard
-"@babel/runtime@npm:^7.1.2":
- version: 7.5.0
- resolution: "@babel/runtime@npm:7.5.0"
- dependencies:
- regenerator-runtime: ^0.13.2
- checksum: ec97ab3e35e93d65e3cadb558bca9adbc7fcc0656ee4ed7b83e72c40cab19e3a091fb2c631278e3eb7a60b2c030ce6d6ff5dec4a8154c372d950aaa6b9016156
- languageName: node
- linkType: hard
-
"@babel/runtime@npm:^7.10.2":
version: 7.12.5
resolution: "@babel/runtime@npm:7.12.5"
languageName: node
linkType: hard
-"@types/react-redux@npm:6.0.6":
- version: 6.0.6
- resolution: "@types/react-redux@npm:6.0.6"
- dependencies:
- "@types/react": "*"
- redux: ^4.0.0
- checksum: 2b56dcb652e412c82a112cc605b4eb2e463f1634ee7e8a2930539db72bc7f08b913ec5ce438f1989c5c9c0a5c903376bcb2a197464764f60330139d71b3900c2
- languageName: node
- linkType: hard
-
"@types/react-router@npm:3.0.20":
version: 3.0.20
resolution: "@types/react-router@npm:3.0.20"
"@types/react-dom": 16.8.4
"@types/react-helmet": 5.0.15
"@types/react-modal": 3.13.1
- "@types/react-redux": 6.0.6
"@types/react-router": 3.0.20
"@types/react-select": 4.0.16
"@types/react-virtualized": 9.21.20
react-helmet-async: 1.2.3
react-intl: 3.12.1
react-modal: 3.14.4
- react-redux: 5.1.1
react-router: 3.2.6
react-select: 4.3.1
react-select-event: 5.4.0
react-virtualized: 9.22.3
- redux: 4.1.2
- redux-thunk: 2.4.1
regenerator-runtime: 0.13.9
rehype-raw: 4.0.2
rehype-react: 5.0.0
languageName: node
linkType: hard
-"hoist-non-react-statics@npm:^3.1.0":
- version: 3.3.0
- resolution: "hoist-non-react-statics@npm:3.3.0"
- dependencies:
- react-is: ^16.7.0
- checksum: 78f77efc6dd4bfa194a96e8c97248ce59f9bf0e63686ee76cb9ab0183d8bd317fcb6bd25f442c0ef9c19d6db144de0df05b79895fd64cae331dbd6e2e573a565
- languageName: node
- linkType: hard
-
"hoist-non-react-statics@npm:^3.3.0, hoist-non-react-statics@npm:^3.3.1, hoist-non-react-statics@npm:^3.3.2":
version: 3.3.2
resolution: "hoist-non-react-statics@npm:3.3.2"
languageName: node
linkType: hard
-"react-is@npm:^16.6.0, react-is@npm:^16.7.0, react-is@npm:^16.8.1, react-is@npm:^16.8.6":
+"react-is@npm:^16.7.0, react-is@npm:^16.8.1, react-is@npm:^16.8.6":
version: 16.8.6
resolution: "react-is@npm:16.8.6"
checksum: 9dfcf465def71ba96e7d77d7e9c49a6cce7e9017dada5a13001bfe5a1b60f4bfb00a839a7847245ffcd4d1d6518b4b52787e6f2a4275f3c6bbc1243bd1dbeb9d
languageName: node
linkType: hard
-"react-redux@npm:5.1.1":
- version: 5.1.1
- resolution: "react-redux@npm:5.1.1"
- dependencies:
- "@babel/runtime": ^7.1.2
- hoist-non-react-statics: ^3.1.0
- invariant: ^2.2.4
- loose-envify: ^1.1.0
- prop-types: ^15.6.1
- react-is: ^16.6.0
- react-lifecycles-compat: ^3.0.0
- peerDependencies:
- react: ^0.14.0 || ^15.0.0-0 || ^16.0.0-0
- redux: ^2.0.0 || ^3.0.0 || ^4.0.0-0
- checksum: 6c79892e8dd40d33af056fa6064184287f7237891c6a858708b4decc9e1a456bab84b4f0a9ae14cb7b0bf520bbed044adae952c0d688d9a1c33072d3a058ad3c
- languageName: node
- linkType: hard
-
"react-router@npm:3.2.6":
version: 3.2.6
resolution: "react-router@npm:3.2.6"
languageName: node
linkType: hard
-"redux-thunk@npm:2.4.1":
- version: 2.4.1
- resolution: "redux-thunk@npm:2.4.1"
- peerDependencies:
- redux: ^4
- checksum: af5abb425fb9dccda02e5f387d6f3003997f62d906542a3d35fc9420088f550dc1a018bdc246c7d23ee852b4d4ab8b5c64c5be426e45a328d791c4586a3c6b6e
- languageName: node
- linkType: hard
-
-"redux@npm:4.1.2":
- version: 4.1.2
- resolution: "redux@npm:4.1.2"
- dependencies:
- "@babel/runtime": ^7.9.2
- checksum: 6a839cee5bd580c5298d968e9e2302150e961318253819bcd97f9d945a5a409559eacddf6026f4118bb68b681c593d90e8a2c5bbf278f014aff9bf0d2d8fa084
- languageName: node
- linkType: hard
-
-"redux@npm:^4.0.0":
- version: 4.0.1
- resolution: "redux@npm:4.0.1"
- dependencies:
- loose-envify: ^1.4.0
- symbol-observable: ^1.2.0
- checksum: f3a4e19b0413cc73ccdbe9f71977292dca9760606ab783aed516c90ca04e931fa1af573c6c55bc506580a2805d4ec0d50edde0b14d7d854f0a66da40f36184b2
- languageName: node
- linkType: hard
-
"reflect.ownkeys@npm:^0.2.0":
version: 0.2.0
resolution: "reflect.ownkeys@npm:0.2.0"
languageName: node
linkType: hard
-"symbol-observable@npm:^1.2.0":
- version: 1.2.0
- resolution: "symbol-observable@npm:1.2.0"
- checksum: 48ffbc22e3d75f9853b3ff2ae94a44d84f386415110aea5effc24d84c502e03a4a6b7a8f75ebaf7b585780bda34eb5d6da3121f826a6f93398429d30032971b6
- languageName: node
- linkType: hard
-
"symbol-tree@npm:^3.2.4":
version: 3.2.4
resolution: "symbol-tree@npm:3.2.4"
login.more_options=More options
login.unauthorized_access_alert=You are not authorized to access this page. Please log in with more privileges and try again.
login.with_x=With {0}
+login.authentication_failed=Authentication failed
+login.logout_failed=Logout failed
unauthorized.message=You're not authorized to access this page. Please contact the administrator.
unauthorized.reason=Reason:
+
+
#------------------------------------------------------------------------------
#
# USERS & GROUPS PAGE