@@ -32,12 +32,9 @@ | |||
"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", | |||
@@ -74,7 +71,6 @@ | |||
"@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", |
@@ -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} |
@@ -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)}; | |||
} | |||
`; |
@@ -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; | |||
`; |
@@ -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> |
@@ -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')} |
@@ -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() |
@@ -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!'); | |||
}); |
@@ -33,7 +33,6 @@ exports[`should render correctly 1`] = ` | |||
} | |||
} | |||
/> | |||
<Connect(GlobalMessages) /> | |||
<withCurrentUserContext(withIndexationContext(IndexationNotification)) /> | |||
<withCurrentUserContext(withAppStateContext(UpdateNotification)) | |||
dismissable={true} |
@@ -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" | |||
> |
@@ -7,7 +7,6 @@ exports[`should render correctly 1`] = ` | |||
<div | |||
className="page-simple" | |||
> | |||
<Connect(GlobalMessages) /> | |||
<h1 | |||
className="text-center huge" | |||
> |
@@ -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)))); |
@@ -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); |
@@ -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} | |||
/> |
@@ -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'; | |||
@@ -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], |
@@ -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", |
@@ -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", |
@@ -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'; | |||
@@ -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 () => { |
@@ -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 }) | |||
); | |||
}); |
@@ -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)); | |||
} |
@@ -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()); |
@@ -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); | |||
} |
@@ -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 | |||
); |
@@ -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> |
@@ -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) | |||
})); |
@@ -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" | |||
> |
@@ -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(); | |||
}); |
@@ -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'; |
@@ -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'; |
@@ -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()); | |||
@@ -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'; |
@@ -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()); | |||
@@ -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'; |
@@ -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'; |
@@ -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'; |
@@ -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(); |
@@ -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> | |||
); | |||
} |
@@ -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(); | |||
}); | |||
}; |
@@ -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; |
@@ -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; |
@@ -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> | |||
`; |
@@ -4,7 +4,6 @@ exports[`should not redirect if logout fails 1`] = ` | |||
<div | |||
className="page page-limited" | |||
> | |||
<Connect(GlobalMessages) /> | |||
<div | |||
className="text-center" | |||
> |
@@ -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'; |
@@ -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'; |
@@ -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); | |||
@@ -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} | |||
/> | |||
); | |||
} |
@@ -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> | |||
`; |
@@ -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', () => { |
@@ -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)); | |||
} | |||
@@ -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, |
@@ -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> |
@@ -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; | |||
} |
@@ -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; |
@@ -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; | |||
} |
@@ -573,15 +573,6 @@ __metadata: | |||
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" | |||
@@ -2023,16 +2014,6 @@ __metadata: | |||
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" | |||
@@ -2328,7 +2309,6 @@ __metadata: | |||
"@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 | |||
@@ -2388,13 +2368,10 @@ __metadata: | |||
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 | |||
@@ -5893,15 +5870,6 @@ __metadata: | |||
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" | |||
@@ -9151,7 +9119,7 @@ __metadata: | |||
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 | |||
@@ -9187,24 +9155,6 @@ __metadata: | |||
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" | |||
@@ -9352,34 +9302,6 @@ __metadata: | |||
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" | |||
@@ -10521,13 +10443,6 @@ resolve@^1.3.2: | |||
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" |
@@ -1946,10 +1946,14 @@ login.login_with_x=Log in with {0} | |||
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 |