*/
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;
export function setWorkerCount(count: number): Promise<void | Response> {
return post('/api/ce/set_worker_count', { count }).catch(throwGlobalError);
}
+
+export function getIndexationStatus(): Promise<IndexationStatus> {
+ return getJSON('/api/ce/indexation_status').catch(throwGlobalError);
+}
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';
<div className="page-wrapper" id="container">
<div className="page-container">
<Workspace>
- <GlobalNav location={props.location} />
- <GlobalMessagesContainer />
- {props.children}
+ <IndexationContextProvider>
+ <GlobalNav location={props.location} />
+ <GlobalMessagesContainer />
+ <IndexationNotification />
+ {props.children}
+ </IndexationContextProvider>
</Workspace>
</div>
</div>
className="page-container"
>
<Workspace>
- <Connect(GlobalNav)
- location={
- Object {
- "action": "PUSH",
- "hash": "",
- "key": "key",
- "pathname": "/path",
- "query": Object {},
- "search": "",
- "state": Object {},
+ <Connect(withAppState(IndexationContextProvider))>
+ <Connect(GlobalNav)
+ location={
+ Object {
+ "action": "PUSH",
+ "hash": "",
+ "key": "key",
+ "pathname": "/path",
+ "query": Object {},
+ "search": "",
+ "state": Object {},
+ }
}
- }
- />
- <Connect(GlobalMessages) />
- <ChildComponent />
+ />
+ <Connect(GlobalMessages) />
+ <withIndexationContext(IndexationNotification) />
+ <ChildComponent />
+ </Connect(withAppState(IndexationContextProvider))>
</Workspace>
</div>
</div>
--- /dev/null
+/*
+ * 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<IndexationContextInterface | null>(null);
--- /dev/null
+/*
+ * 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<T.AppState, 'needIssueSync'>;
+}
+
+export class IndexationContextProvider extends React.PureComponent<
+ React.PropsWithChildren<Props>,
+ IndexationContextInterface
+> {
+ mounted = false;
+
+ constructor(props: React.PropsWithChildren<Props>) {
+ 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 (
+ <IndexationContext.Provider value={this.state}>
+ {this.props.children}
+ </IndexationContext.Provider>
+ );
+ }
+}
+
+export default withAppState(IndexationContextProvider);
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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<WithIndexationContextProps, State> {
+ 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 (
+ <IndexationNotificationRenderer
+ progression={progression}
+ percentCompleted={percentCompleted ?? 0}
+ onDismissCompletedNotification={this.handleDismissCompletedNotification}
+ />
+ );
+ }
+}
+
+export default withIndexationContext(IndexationNotification);
--- /dev/null
+/*
+ * 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());
+ }
+}
--- /dev/null
+/*
+ * 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 (
+ <div className="indexation-notification-wrapper">
+ <Alert
+ className="indexation-notification-banner"
+ display="banner"
+ variant={inProgress ? 'warning' : 'success'}>
+ <div className="display-flex-center">
+ {inProgress ? (
+ <>
+ <span>{translate('indexation.in_progress')}</span>
+ <i className="spinner spacer-left" />
+ <span className="spacer-left">
+ {translateWithParameters('indexation.in_progress.details', percentCompleted)}
+ </span>
+ </>
+ ) : (
+ <>
+ <span>{translate('indexation.completed')}</span>
+ <ButtonLink className="spacer-left" onClick={props.onDismissCompletedNotification}>
+ <strong>{translate('dismiss')}</strong>
+ </ButtonLink>
+ </>
+ )}
+ </div>
+ </Alert>
+ </div>
+ );
+}
--- /dev/null
+/*
+ * 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<IndexationContextProvider>(
+ <IndexationContextProvider appState={{ needIssueSync: true }} {...props}>
+ <TestComponent />
+ </IndexationContextProvider>
+ );
+}
+
+class TestComponent extends React.PureComponent {
+ context!: IndexationStatus;
+ static contextType = IndexationContext;
+
+ render() {
+ return <h1>TestComponent</h1>;
+ }
+}
--- /dev/null
+/*
+ * 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<IndexationNotification['props']>) {
+ return shallow<IndexationNotification>(
+ <IndexationNotification indexationContext={{ status: { isCompleted: false } }} {...props} />
+ );
+}
--- /dev/null
+/*
+ * 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);
+});
--- /dev/null
+/*
+ * 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<IndexationNotificationRendererProps> = {}) {
+ return shallow<IndexationNotificationRendererProps>(
+ <IndexationNotificationRenderer
+ progression={IndexationProgression.InProgress}
+ percentCompleted={25}
+ onDismissCompletedNotification={jest.fn()}
+ {...props}
+ />
+ );
+}
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly: completed 1`] = `
+<div
+ className="indexation-notification-wrapper"
+>
+ <Alert
+ className="indexation-notification-banner"
+ display="banner"
+ variant="success"
+ >
+ <div
+ className="display-flex-center"
+ >
+ <span>
+ indexation.completed
+ </span>
+ <ButtonLink
+ className="spacer-left"
+ onClick={[MockFunction]}
+ >
+ <strong>
+ dismiss
+ </strong>
+ </ButtonLink>
+ </div>
+ </Alert>
+</div>
+`;
+
+exports[`should render correctly: in-progress 1`] = `
+<div
+ className="indexation-notification-wrapper"
+>
+ <Alert
+ className="indexation-notification-banner"
+ display="banner"
+ variant="warning"
+ >
+ <div
+ className="display-flex-center"
+ >
+ <span>
+ indexation.in_progress
+ </span>
+ <i
+ className="spinner spacer-left"
+ />
+ <span
+ className="spacer-left"
+ >
+ indexation.in_progress.details.25
+ </span>
+ </div>
+ </Alert>
+</div>
+`;
pageMainZIndex: '50',
pageSideZIndex: '51',
+ globalBannerZIndex: '60',
+
tooltipZIndex: '8000',
dropdownMenuZIndex: '7500',
--- /dev/null
+/*
+ * 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<IndexationContextInterface>) {
+ return mount(
+ <IndexationContext.Provider value={{ status: { isCompleted: false }, ...indexationContext }}>
+ <TestComponentWithIndexationContext />
+ </IndexationContext.Provider>
+ );
+}
+
+class TestComponent extends React.PureComponent<WithIndexationContextProps> {
+ render() {
+ return <h1>TestComponent</h1>;
+ }
+}
+
+const TestComponentWithIndexationContext = withIndexationContext(TestComponent);
--- /dev/null
+/*
+ * 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<P>(
+ WrappedComponent: React.ComponentType<P & WithIndexationContextProps>
+) {
+ return class WithIndexationContext extends React.PureComponent<
+ Omit<P, keyof WithIndexationContextProps>
+ > {
+ static displayName = getWrappedDisplayName(WrappedComponent, 'withIndexationContext');
+
+ render() {
+ return (
+ <IndexationContext.Consumer>
+ {indexationContext => {
+ if (indexationContext) {
+ return (
+ <WrappedComponent indexationContext={indexationContext} {...(this.props as P)} />
+ );
+ }
+
+ return null;
+ }}
+ </IndexationContext.Consumer>
+ );
+ }
+ };
+}
--- /dev/null
+/*
+ * 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;
+}
edition: 'community' | 'developer' | 'enterprise' | 'datacenter' | undefined;
globalPages?: Extension[];
multipleAlmEnabled?: boolean;
+ needIssueSync?: boolean;
organizationsEnabled?: boolean;
productionDatabase: boolean;
qualifiers: string[];
description=Description
directories=Directories
directory=Directory
+dismiss=Dismiss
display=Display
download_verb=Download
duplications=Duplications
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.
#------------------------------------------------------------------------------
#