From 98cadf2880787e8caaf73460d59a080a04a3f0b4 Mon Sep 17 00:00:00 2001 From: Philippe Perrin Date: Wed, 3 Jun 2020 19:23:52 +0200 Subject: [PATCH] SONAR-13398 Display an alert with the indexation status --- server/sonar-web/src/main/js/api/ce.ts | 5 ++ .../js/app/components/GlobalContainer.tsx | 11 ++- .../GlobalContainer-test.tsx.snap | 31 ++++--- .../indexation/IndexationContext.ts | 25 ++++++ .../indexation/IndexationContextProvider.tsx | 78 ++++++++++++++++ .../indexation/IndexationNotification.css | 30 +++++++ .../indexation/IndexationNotification.tsx | 87 ++++++++++++++++++ .../IndexationNotificationHelper.ts | 57 ++++++++++++ .../IndexationNotificationRenderer.tsx | 65 ++++++++++++++ .../IndexationContextProvider-test.tsx | 90 +++++++++++++++++++ .../__tests__/IndexationNotification-test.tsx | 87 ++++++++++++++++++ .../IndexationNotificationHelper-test.tsx | 80 +++++++++++++++++ .../IndexationNotificationRenderer-test.tsx | 57 ++++++++++++ ...dexationNotificationRenderer-test.tsx.snap | 57 ++++++++++++ server/sonar-web/src/main/js/app/theme.js | 2 + .../__tests__/withIndexationContext-test.tsx | 51 +++++++++++ .../components/hoc/withIndexationContext.tsx | 54 +++++++++++ .../sonar-web/src/main/js/types/indexation.ts | 28 ++++++ server/sonar-web/src/main/js/types/types.d.ts | 1 + .../resources/org/sonar/l10n/core.properties | 10 ++- 20 files changed, 888 insertions(+), 18 deletions(-) create mode 100644 server/sonar-web/src/main/js/app/components/indexation/IndexationContext.ts create mode 100644 server/sonar-web/src/main/js/app/components/indexation/IndexationContextProvider.tsx create mode 100644 server/sonar-web/src/main/js/app/components/indexation/IndexationNotification.css create mode 100644 server/sonar-web/src/main/js/app/components/indexation/IndexationNotification.tsx create mode 100644 server/sonar-web/src/main/js/app/components/indexation/IndexationNotificationHelper.ts create mode 100644 server/sonar-web/src/main/js/app/components/indexation/IndexationNotificationRenderer.tsx create mode 100644 server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationContextProvider-test.tsx create mode 100644 server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationNotification-test.tsx create mode 100644 server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationNotificationHelper-test.tsx create mode 100644 server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationNotificationRenderer-test.tsx create mode 100644 server/sonar-web/src/main/js/app/components/indexation/__tests__/__snapshots__/IndexationNotificationRenderer-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/components/hoc/__tests__/withIndexationContext-test.tsx create mode 100644 server/sonar-web/src/main/js/components/hoc/withIndexationContext.tsx create mode 100644 server/sonar-web/src/main/js/types/indexation.ts diff --git a/server/sonar-web/src/main/js/api/ce.ts b/server/sonar-web/src/main/js/api/ce.ts index 8b90d9642af..a4d1a03a5ca 100644 --- a/server/sonar-web/src/main/js/api/ce.ts +++ b/server/sonar-web/src/main/js/api/ce.ts @@ -19,6 +19,7 @@ */ import { getJSON, post, RequestData } from 'sonar-ui-common/helpers/request'; import throwGlobalError from '../app/utils/throwGlobalError'; +import { IndexationStatus } from '../types/indexation'; export function getAnalysisStatus(data: { component: string; @@ -83,3 +84,7 @@ export function getWorkers(): Promise<{ canSetWorkerCount: boolean; value: numbe export function setWorkerCount(count: number): Promise { return post('/api/ce/set_worker_count', { count }).catch(throwGlobalError); } + +export function getIndexationStatus(): Promise { + return getJSON('/api/ce/indexation_status').catch(throwGlobalError); +} diff --git a/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx b/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx index 4ea0a702a67..92e72874626 100644 --- a/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx +++ b/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx @@ -24,6 +24,8 @@ import A11ySkipLinks from './a11y/A11ySkipLinks'; import SuggestionsProvider from './embed-docs-modal/SuggestionsProvider'; import GlobalFooterContainer from './GlobalFooterContainer'; import GlobalMessagesContainer from './GlobalMessagesContainer'; +import IndexationContextProvider from './indexation/IndexationContextProvider'; +import IndexationNotification from './indexation/IndexationNotification'; import GlobalNav from './nav/global/GlobalNav'; import StartupModal from './StartupModal'; @@ -46,9 +48,12 @@ export default function GlobalContainer(props: Props) {
- - - {props.children} + + + + + {props.children} +
diff --git a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalContainer-test.tsx.snap b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalContainer-test.tsx.snap index 5d4c5d631f2..adf8cc40e47 100644 --- a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalContainer-test.tsx.snap +++ b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalContainer-test.tsx.snap @@ -16,21 +16,24 @@ exports[`should render correctly 1`] = ` className="page-container" > - + - - + /> + + + + diff --git a/server/sonar-web/src/main/js/app/components/indexation/IndexationContext.ts b/server/sonar-web/src/main/js/app/components/indexation/IndexationContext.ts new file mode 100644 index 00000000000..1e8793dba52 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/indexation/IndexationContext.ts @@ -0,0 +1,25 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 { createContext } from 'react'; +import { IndexationContextInterface } from '../../../types/indexation'; + +// eslint-disable-next-line import/prefer-default-export +export const IndexationContext = createContext(null); diff --git a/server/sonar-web/src/main/js/app/components/indexation/IndexationContextProvider.tsx b/server/sonar-web/src/main/js/app/components/indexation/IndexationContextProvider.tsx new file mode 100644 index 00000000000..e22fb67eeef --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/indexation/IndexationContextProvider.tsx @@ -0,0 +1,78 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 React from 'react'; +import { withAppState } from '../../../components/hoc/withAppState'; +import { IndexationContextInterface, IndexationStatus } from '../../../types/indexation'; +import { IndexationContext } from './IndexationContext'; +import IndexationNotificationHelper from './IndexationNotificationHelper'; + +interface Props { + appState: Pick; +} + +export class IndexationContextProvider extends React.PureComponent< + React.PropsWithChildren, + IndexationContextInterface +> { + mounted = false; + + constructor(props: React.PropsWithChildren) { + super(props); + + this.state = { + status: { isCompleted: !props.appState.needIssueSync } + }; + } + + componentDidMount() { + this.mounted = true; + + if (!this.state.status.isCompleted) { + IndexationNotificationHelper.startPolling(this.handleNewStatus); + } + } + + componentWillUnmount() { + this.mounted = false; + + IndexationNotificationHelper.stopPolling(); + } + + handleNewStatus = (newIndexationStatus: IndexationStatus) => { + if (newIndexationStatus.isCompleted) { + IndexationNotificationHelper.stopPolling(); + } + + if (this.mounted) { + this.setState({ status: newIndexationStatus }); + } + }; + + render() { + return ( + + {this.props.children} + + ); + } +} + +export default withAppState(IndexationContextProvider); diff --git a/server/sonar-web/src/main/js/app/components/indexation/IndexationNotification.css b/server/sonar-web/src/main/js/app/components/indexation/IndexationNotification.css new file mode 100644 index 00000000000..b69f709fa79 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/indexation/IndexationNotification.css @@ -0,0 +1,30 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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. + */ + +.indexation-notification-wrapper { + height: 34px; +} + +.indexation-notification-banner { + position: fixed; + width: 100%; + z-index: var(--globalBannerZIndex); + margin-bottom: 0 !important; +} diff --git a/server/sonar-web/src/main/js/app/components/indexation/IndexationNotification.tsx b/server/sonar-web/src/main/js/app/components/indexation/IndexationNotification.tsx new file mode 100644 index 00000000000..50c98188e1e --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/indexation/IndexationNotification.tsx @@ -0,0 +1,87 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 React from 'react'; +import withIndexationContext, { + WithIndexationContextProps +} from '../../../components/hoc/withIndexationContext'; +import './IndexationNotification.css'; +import IndexationNotificationHelper from './IndexationNotificationHelper'; +import IndexationNotificationRenderer from './IndexationNotificationRenderer'; + +interface State { + progression?: IndexationProgression; +} + +export enum IndexationProgression { + InProgress, + Completed +} + +export class IndexationNotification extends React.PureComponent { + state: State = { + progression: undefined + }; + + componentDidMount() { + this.refreshNotification(); + } + + componentDidUpdate() { + this.refreshNotification(); + } + + refreshNotification() { + if (!this.props.indexationContext.status.isCompleted) { + IndexationNotificationHelper.markInProgressNotificationAsDisplayed(); + this.setState({ progression: IndexationProgression.InProgress }); + } else if (IndexationNotificationHelper.shouldDisplayCompletedNotification()) { + this.setState({ progression: IndexationProgression.Completed }); + } + } + + handleDismissCompletedNotification = () => { + IndexationNotificationHelper.markCompletedNotificationAsDisplayed(); + this.setState({ progression: undefined }); + }; + + render() { + const { progression } = this.state; + const { + indexationContext: { + status: { percentCompleted } + } + } = this.props; + + if (progression === undefined) { + return null; + } + + return ( + + ); + } +} + +export default withIndexationContext(IndexationNotification); diff --git a/server/sonar-web/src/main/js/app/components/indexation/IndexationNotificationHelper.ts b/server/sonar-web/src/main/js/app/components/indexation/IndexationNotificationHelper.ts new file mode 100644 index 00000000000..18e71d12b6c --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/indexation/IndexationNotificationHelper.ts @@ -0,0 +1,57 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 { get, remove, save } from 'sonar-ui-common/helpers/storage'; +import { getIndexationStatus } from '../../../api/ce'; +import { IndexationStatus } from '../../../types/indexation'; + +const POLLING_INTERVAL_MS = 5000; +const LS_INDEXATION_PROGRESS_WAS_DISPLAYED = 'indexation.progress.was.displayed'; + +export default class IndexationNotificationHelper { + private static interval?: NodeJS.Timeout; + + static startPolling(onNewStatus: (status: IndexationStatus) => void) { + this.stopPolling(); + + this.interval = setInterval(async () => { + const status = await getIndexationStatus(); + onNewStatus(status); + }, POLLING_INTERVAL_MS); + } + + static stopPolling() { + if (this.interval) { + clearInterval(this.interval); + } + } + + static markInProgressNotificationAsDisplayed() { + save(LS_INDEXATION_PROGRESS_WAS_DISPLAYED, true.toString()); + } + + static markCompletedNotificationAsDisplayed() { + remove(LS_INDEXATION_PROGRESS_WAS_DISPLAYED); + } + + static shouldDisplayCompletedNotification() { + return JSON.parse(get(LS_INDEXATION_PROGRESS_WAS_DISPLAYED) || false.toString()); + } +} diff --git a/server/sonar-web/src/main/js/app/components/indexation/IndexationNotificationRenderer.tsx b/server/sonar-web/src/main/js/app/components/indexation/IndexationNotificationRenderer.tsx new file mode 100644 index 00000000000..9cb464cd6de --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/indexation/IndexationNotificationRenderer.tsx @@ -0,0 +1,65 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 React from 'react'; +import { ButtonLink } from 'sonar-ui-common/components/controls/buttons'; +import { Alert } from 'sonar-ui-common/components/ui/Alert'; +import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; +import { IndexationProgression } from './IndexationNotification'; + +export interface IndexationNotificationRendererProps { + progression: IndexationProgression; + percentCompleted: number; + onDismissCompletedNotification: VoidFunction; +} + +export default function IndexationNotificationRenderer(props: IndexationNotificationRendererProps) { + const { progression, percentCompleted } = props; + + const inProgress = progression === IndexationProgression.InProgress; + + return ( +
+ +
+ {inProgress ? ( + <> + {translate('indexation.in_progress')} + + + {translateWithParameters('indexation.in_progress.details', percentCompleted)} + + + ) : ( + <> + {translate('indexation.completed')} + + {translate('dismiss')} + + + )} +
+
+
+ ); +} diff --git a/server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationContextProvider-test.tsx b/server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationContextProvider-test.tsx new file mode 100644 index 00000000000..e0c8fb8130f --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationContextProvider-test.tsx @@ -0,0 +1,90 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 { mount } from 'enzyme'; +import * as React from 'react'; +import { IndexationStatus } from '../../../../types/indexation'; +import { IndexationContext } from '../IndexationContext'; +import { IndexationContextProvider } from '../IndexationContextProvider'; +import IndexationNotificationHelper from '../IndexationNotificationHelper'; + +beforeEach(() => jest.clearAllMocks()); + +jest.mock('../IndexationNotificationHelper'); + +it('should render correctly & start polling', () => { + const wrapper = mountRender(); + + expect(wrapper.state().status).toEqual({ isCompleted: false }); + + const child = wrapper.find(TestComponent); + expect(child.exists()).toBe(true); + expect(child.instance().context).toEqual(wrapper.state()); +}); + +it('should start polling if needed', () => { + mountRender(); + + expect(IndexationNotificationHelper.startPolling).toHaveBeenCalled(); +}); + +it('should not start polling if not needed', () => { + mountRender({ appState: { needIssueSync: false } }); + + expect(IndexationNotificationHelper.startPolling).not.toHaveBeenCalled(); +}); + +it('should update the state on new status & stop polling if indexation is complete', () => { + const wrapper = mountRender(); + + const triggerNewStatus = (IndexationNotificationHelper.startPolling as jest.Mock).mock + .calls[0][0] as (status: IndexationStatus) => void; + const newStatus = { isCompleted: true, percentCompleted: 100 }; + + triggerNewStatus(newStatus); + + expect(wrapper.state().status).toEqual(newStatus); + expect(IndexationNotificationHelper.stopPolling).toHaveBeenCalled(); +}); + +it('should stop polling when component is destroyed', () => { + const wrapper = mountRender(); + + wrapper.unmount(); + + expect(IndexationNotificationHelper.stopPolling).toHaveBeenCalled(); +}); + +function mountRender(props?: IndexationContextProvider['props']) { + return mount( + + + + ); +} + +class TestComponent extends React.PureComponent { + context!: IndexationStatus; + static contextType = IndexationContext; + + render() { + return

TestComponent

; + } +} diff --git a/server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationNotification-test.tsx b/server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationNotification-test.tsx new file mode 100644 index 00000000000..207db1ee713 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationNotification-test.tsx @@ -0,0 +1,87 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 * as React from 'react'; +import { IndexationNotification, IndexationProgression } from '../IndexationNotification'; +import IndexationNotificationHelper from '../IndexationNotificationHelper'; +import IndexationNotificationRenderer from '../IndexationNotificationRenderer'; + +beforeEach(() => jest.clearAllMocks()); + +jest.mock('../IndexationNotificationHelper'); + +it('should display the warning banner if indexation is in progress', () => { + const wrapper = shallowRender(); + + expect(IndexationNotificationHelper.markInProgressNotificationAsDisplayed).toHaveBeenCalled(); + expect(wrapper.state().progression).toBe(IndexationProgression.InProgress); +}); + +it('should display the success banner when indexation is complete', () => { + (IndexationNotificationHelper.shouldDisplayCompletedNotification as jest.Mock).mockReturnValueOnce( + true + ); + + const wrapper = shallowRender(); + + wrapper.setProps({ indexationContext: { status: { isCompleted: true } } }); + + expect(IndexationNotificationHelper.shouldDisplayCompletedNotification).toHaveBeenCalled(); + expect(wrapper.state().progression).toBe(IndexationProgression.Completed); +}); + +it('should render correctly completed notification at startup', () => { + (IndexationNotificationHelper.shouldDisplayCompletedNotification as jest.Mock).mockReturnValueOnce( + true + ); + + const wrapper = shallowRender({ + indexationContext: { status: { isCompleted: true } } + }); + + expect(IndexationNotificationHelper.markInProgressNotificationAsDisplayed).not.toHaveBeenCalled(); + expect(IndexationNotificationHelper.shouldDisplayCompletedNotification).toHaveBeenCalled(); + expect(wrapper.state().progression).toBe(IndexationProgression.Completed); +}); + +it('should hide the success banner on dismiss action', () => { + (IndexationNotificationHelper.shouldDisplayCompletedNotification as jest.Mock).mockReturnValueOnce( + true + ); + + const wrapper = shallowRender({ + indexationContext: { status: { isCompleted: true } } + }); + + wrapper + .find(IndexationNotificationRenderer) + .props() + .onDismissCompletedNotification(); + + expect(IndexationNotificationHelper.markCompletedNotificationAsDisplayed).toHaveBeenCalled(); + expect(wrapper.state().progression).toBeUndefined(); +}); + +function shallowRender(props?: Partial) { + return shallow( + + ); +} diff --git a/server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationNotificationHelper-test.tsx b/server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationNotificationHelper-test.tsx new file mode 100644 index 00000000000..6c3b7363e74 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationNotificationHelper-test.tsx @@ -0,0 +1,80 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 { get, remove, save } from 'sonar-ui-common/helpers/storage'; +import { getIndexationStatus } from '../../../../api/ce'; +import { IndexationStatus } from '../../../../types/indexation'; +import IndexationNotificationHelper from '../IndexationNotificationHelper'; + +beforeEach(() => { + jest.clearAllMocks(); + jest.useFakeTimers(); +}); + +jest.mock('../../../../api/ce', () => ({ + getIndexationStatus: jest.fn() +})); + +jest.mock('sonar-ui-common/helpers/storage', () => ({ + get: jest.fn(), + remove: jest.fn(), + save: jest.fn() +})); + +it('should properly start & stop polling for indexation status', async () => { + const onNewStatus = jest.fn(); + const newStatus: IndexationStatus = { isCompleted: true, percentCompleted: 87 }; + (getIndexationStatus as jest.Mock).mockResolvedValueOnce(newStatus); + + IndexationNotificationHelper.startPolling(onNewStatus); + + jest.runOnlyPendingTimers(); + expect(getIndexationStatus).toHaveBeenCalled(); + + await new Promise(setImmediate); + expect(onNewStatus).toHaveBeenCalledWith(newStatus); + + (getIndexationStatus as jest.Mock).mockClear(); + + IndexationNotificationHelper.stopPolling(); + jest.runAllTimers(); + + expect(getIndexationStatus).not.toHaveBeenCalled(); +}); + +it('should properly handle the flag to show the completed banner', () => { + IndexationNotificationHelper.markInProgressNotificationAsDisplayed(); + + expect(save).toHaveBeenCalledWith(expect.any(String), 'true'); + + (get as jest.Mock).mockReturnValueOnce('true'); + let shouldDisplay = IndexationNotificationHelper.shouldDisplayCompletedNotification(); + + expect(shouldDisplay).toBe(true); + expect(get).toHaveBeenCalled(); + + IndexationNotificationHelper.markCompletedNotificationAsDisplayed(); + + expect(remove).toHaveBeenCalled(); + + shouldDisplay = IndexationNotificationHelper.shouldDisplayCompletedNotification(); + + expect(shouldDisplay).toBe(false); +}); diff --git a/server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationNotificationRenderer-test.tsx b/server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationNotificationRenderer-test.tsx new file mode 100644 index 00000000000..3bafb5ebba8 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationNotificationRenderer-test.tsx @@ -0,0 +1,57 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 * as React from 'react'; +import { ButtonLink } from 'sonar-ui-common/components/controls/buttons'; +import { click } from 'sonar-ui-common/helpers/testUtils'; +import { IndexationProgression } from '../IndexationNotification'; +import IndexationNotificationRenderer, { + IndexationNotificationRendererProps +} from '../IndexationNotificationRenderer'; + +it('should render correctly', () => { + expect(shallowRender()).toMatchSnapshot('in-progress'); + expect(shallowRender({ progression: IndexationProgression.Completed })).toMatchSnapshot( + 'completed' + ); +}); + +it('should propagate the dismiss event', () => { + const onDismissCompletedNotification = jest.fn(); + const wrapper = shallowRender({ + progression: IndexationProgression.Completed, + onDismissCompletedNotification + }); + + click(wrapper.find(ButtonLink)); + expect(onDismissCompletedNotification).toHaveBeenCalled(); +}); + +function shallowRender(props: Partial = {}) { + return shallow( + + ); +} diff --git a/server/sonar-web/src/main/js/app/components/indexation/__tests__/__snapshots__/IndexationNotificationRenderer-test.tsx.snap b/server/sonar-web/src/main/js/app/components/indexation/__tests__/__snapshots__/IndexationNotificationRenderer-test.tsx.snap new file mode 100644 index 00000000000..c0cb064d7df --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/indexation/__tests__/__snapshots__/IndexationNotificationRenderer-test.tsx.snap @@ -0,0 +1,57 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly: completed 1`] = ` +
+ +
+ + indexation.completed + + + + dismiss + + +
+
+
+`; + +exports[`should render correctly: in-progress 1`] = ` +
+ +
+ + indexation.in_progress + + + + indexation.in_progress.details.25 + +
+
+
+`; diff --git a/server/sonar-web/src/main/js/app/theme.js b/server/sonar-web/src/main/js/app/theme.js index e5aa510ffc6..548cbc3a042 100644 --- a/server/sonar-web/src/main/js/app/theme.js +++ b/server/sonar-web/src/main/js/app/theme.js @@ -193,6 +193,8 @@ module.exports = { pageMainZIndex: '50', pageSideZIndex: '51', + globalBannerZIndex: '60', + tooltipZIndex: '8000', dropdownMenuZIndex: '7500', diff --git a/server/sonar-web/src/main/js/components/hoc/__tests__/withIndexationContext-test.tsx b/server/sonar-web/src/main/js/components/hoc/__tests__/withIndexationContext-test.tsx new file mode 100644 index 00000000000..d4ca9f9452f --- /dev/null +++ b/server/sonar-web/src/main/js/components/hoc/__tests__/withIndexationContext-test.tsx @@ -0,0 +1,51 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 { mount } from 'enzyme'; +import * as React from 'react'; +import { IndexationContext } from '../../../app/components/indexation/IndexationContext'; +import { IndexationContextInterface } from '../../../types/indexation'; +import withIndexationContext, { WithIndexationContextProps } from '../withIndexationContext'; + +it('should render correctly', () => { + const indexationContext: IndexationContextInterface = { + status: { isCompleted: true, percentCompleted: 87 } + }; + + const wrapper = mountRender(indexationContext); + + expect(wrapper.find(TestComponent).props().indexationContext).toEqual(indexationContext); +}); + +function mountRender(indexationContext?: Partial) { + return mount( + + + + ); +} + +class TestComponent extends React.PureComponent { + render() { + return

TestComponent

; + } +} + +const TestComponentWithIndexationContext = withIndexationContext(TestComponent); diff --git a/server/sonar-web/src/main/js/components/hoc/withIndexationContext.tsx b/server/sonar-web/src/main/js/components/hoc/withIndexationContext.tsx new file mode 100644 index 00000000000..a948a2685c1 --- /dev/null +++ b/server/sonar-web/src/main/js/components/hoc/withIndexationContext.tsx @@ -0,0 +1,54 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 React from 'react'; +import { IndexationContext } from '../../app/components/indexation/IndexationContext'; +import { IndexationContextInterface } from '../../types/indexation'; +import { getWrappedDisplayName } from './utils'; + +export interface WithIndexationContextProps { + indexationContext: IndexationContextInterface; +} + +export default function withIndexationContext

( + WrappedComponent: React.ComponentType

+) { + return class WithIndexationContext extends React.PureComponent< + Omit + > { + static displayName = getWrappedDisplayName(WrappedComponent, 'withIndexationContext'); + + render() { + return ( + + {indexationContext => { + if (indexationContext) { + return ( + + ); + } + + return null; + }} + + ); + } + }; +} diff --git a/server/sonar-web/src/main/js/types/indexation.ts b/server/sonar-web/src/main/js/types/indexation.ts new file mode 100644 index 00000000000..28f9b6eba53 --- /dev/null +++ b/server/sonar-web/src/main/js/types/indexation.ts @@ -0,0 +1,28 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 interface IndexationStatus { + isCompleted: boolean; + percentCompleted?: number; +} + +export interface IndexationContextInterface { + status: IndexationStatus; +} diff --git a/server/sonar-web/src/main/js/types/types.d.ts b/server/sonar-web/src/main/js/types/types.d.ts index df78c685b06..72b81c469c0 100644 --- a/server/sonar-web/src/main/js/types/types.d.ts +++ b/server/sonar-web/src/main/js/types/types.d.ts @@ -100,6 +100,7 @@ declare namespace T { edition: 'community' | 'developer' | 'enterprise' | 'datacenter' | undefined; globalPages?: Extension[]; multipleAlmEnabled?: boolean; + needIssueSync?: boolean; organizationsEnabled?: boolean; productionDatabase: boolean; qualifiers: string[]; diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index 2849bee64c6..4cf2149df4d 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -62,6 +62,7 @@ descending=Descending description=Description directories=Directories directory=Directory +dismiss=Dismiss display=Display download_verb=Download duplications=Duplications @@ -3556,7 +3557,14 @@ maintenance.all_systems_opetational=All systems operational. maintenance.is_offline={instance} is offline maintenance.sonarqube_is_offline.text=The connection to SonarQube is lost. Please contact your system administrator. - +#------------------------------------------------------------------------------ +# +# INDEXATION +# +#------------------------------------------------------------------------------ +indexation.in_progress=SonarQube is reloading project data. Some projects will be unavailable until this process is complete. +indexation.in_progress.details={0}% completed +indexation.completed=All project data has been reloaded. #------------------------------------------------------------------------------ # -- 2.39.5