Procházet zdrojové kódy

SONAR-13398 Display an alert with the indexation status

tags/8.4.0.35506
Philippe Perrin před 4 roky
rodič
revize
98cadf2880
20 změnil soubory, kde provedl 888 přidání a 18 odebrání
  1. 5
    0
      server/sonar-web/src/main/js/api/ce.ts
  2. 8
    3
      server/sonar-web/src/main/js/app/components/GlobalContainer.tsx
  3. 17
    14
      server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalContainer-test.tsx.snap
  4. 25
    0
      server/sonar-web/src/main/js/app/components/indexation/IndexationContext.ts
  5. 78
    0
      server/sonar-web/src/main/js/app/components/indexation/IndexationContextProvider.tsx
  6. 30
    0
      server/sonar-web/src/main/js/app/components/indexation/IndexationNotification.css
  7. 87
    0
      server/sonar-web/src/main/js/app/components/indexation/IndexationNotification.tsx
  8. 57
    0
      server/sonar-web/src/main/js/app/components/indexation/IndexationNotificationHelper.ts
  9. 65
    0
      server/sonar-web/src/main/js/app/components/indexation/IndexationNotificationRenderer.tsx
  10. 90
    0
      server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationContextProvider-test.tsx
  11. 87
    0
      server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationNotification-test.tsx
  12. 80
    0
      server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationNotificationHelper-test.tsx
  13. 57
    0
      server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationNotificationRenderer-test.tsx
  14. 57
    0
      server/sonar-web/src/main/js/app/components/indexation/__tests__/__snapshots__/IndexationNotificationRenderer-test.tsx.snap
  15. 2
    0
      server/sonar-web/src/main/js/app/theme.js
  16. 51
    0
      server/sonar-web/src/main/js/components/hoc/__tests__/withIndexationContext-test.tsx
  17. 54
    0
      server/sonar-web/src/main/js/components/hoc/withIndexationContext.tsx
  18. 28
    0
      server/sonar-web/src/main/js/types/indexation.ts
  19. 1
    0
      server/sonar-web/src/main/js/types/types.d.ts
  20. 9
    1
      sonar-core/src/main/resources/org/sonar/l10n/core.properties

+ 5
- 0
server/sonar-web/src/main/js/api/ce.ts Zobrazit soubor

@@ -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<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);
}

+ 8
- 3
server/sonar-web/src/main/js/app/components/GlobalContainer.tsx Zobrazit soubor

@@ -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) {
<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>

+ 17
- 14
server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalContainer-test.tsx.snap Zobrazit soubor

@@ -16,21 +16,24 @@ exports[`should render correctly 1`] = `
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>

+ 25
- 0
server/sonar-web/src/main/js/app/components/indexation/IndexationContext.ts Zobrazit soubor

@@ -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<IndexationContextInterface | null>(null);

+ 78
- 0
server/sonar-web/src/main/js/app/components/indexation/IndexationContextProvider.tsx Zobrazit soubor

@@ -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<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);

+ 30
- 0
server/sonar-web/src/main/js/app/components/indexation/IndexationNotification.css Zobrazit soubor

@@ -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;
}

+ 87
- 0
server/sonar-web/src/main/js/app/components/indexation/IndexationNotification.tsx Zobrazit soubor

@@ -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<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);

+ 57
- 0
server/sonar-web/src/main/js/app/components/indexation/IndexationNotificationHelper.ts Zobrazit soubor

@@ -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());
}
}

+ 65
- 0
server/sonar-web/src/main/js/app/components/indexation/IndexationNotificationRenderer.tsx Zobrazit soubor

@@ -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 (
<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>
);
}

+ 90
- 0
server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationContextProvider-test.tsx Zobrazit soubor

@@ -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<IndexationContextProvider>(
<IndexationContextProvider appState={{ needIssueSync: true }} {...props}>
<TestComponent />
</IndexationContextProvider>
);
}

class TestComponent extends React.PureComponent {
context!: IndexationStatus;
static contextType = IndexationContext;

render() {
return <h1>TestComponent</h1>;
}
}

+ 87
- 0
server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationNotification-test.tsx Zobrazit soubor

@@ -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<IndexationNotification['props']>) {
return shallow<IndexationNotification>(
<IndexationNotification indexationContext={{ status: { isCompleted: false } }} {...props} />
);
}

+ 80
- 0
server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationNotificationHelper-test.tsx Zobrazit soubor

@@ -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);
});

+ 57
- 0
server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationNotificationRenderer-test.tsx Zobrazit soubor

@@ -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<IndexationNotificationRendererProps> = {}) {
return shallow<IndexationNotificationRendererProps>(
<IndexationNotificationRenderer
progression={IndexationProgression.InProgress}
percentCompleted={25}
onDismissCompletedNotification={jest.fn()}
{...props}
/>
);
}

+ 57
- 0
server/sonar-web/src/main/js/app/components/indexation/__tests__/__snapshots__/IndexationNotificationRenderer-test.tsx.snap Zobrazit soubor

@@ -0,0 +1,57 @@
// 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>
`;

+ 2
- 0
server/sonar-web/src/main/js/app/theme.js Zobrazit soubor

@@ -193,6 +193,8 @@ module.exports = {
pageMainZIndex: '50',
pageSideZIndex: '51',

globalBannerZIndex: '60',

tooltipZIndex: '8000',

dropdownMenuZIndex: '7500',

+ 51
- 0
server/sonar-web/src/main/js/components/hoc/__tests__/withIndexationContext-test.tsx Zobrazit soubor

@@ -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<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);

+ 54
- 0
server/sonar-web/src/main/js/components/hoc/withIndexationContext.tsx Zobrazit soubor

@@ -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<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>
);
}
};
}

+ 28
- 0
server/sonar-web/src/main/js/types/indexation.ts Zobrazit soubor

@@ -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;
}

+ 1
- 0
server/sonar-web/src/main/js/types/types.d.ts Zobrazit soubor

@@ -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[];

+ 9
- 1
sonar-core/src/main/resources/org/sonar/l10n/core.properties Zobrazit soubor

@@ -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.

#------------------------------------------------------------------------------
#

Načítá se…
Zrušit
Uložit