* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
/* eslint-disable react/no-unused-state */
+
import * as React from 'react';
import { AppState } from '../../../types/appstate';
import { IndexationContextInterface, IndexationStatus } from '../../../types/indexation';
if (this.props.appState.needIssueSync) {
IndexationNotificationHelper.startPolling(this.handleNewStatus);
} else {
- this.setState({ status: { isCompleted: true, percentCompleted: 100, hasFailures: false } });
+ this.setState({
+ status: { isCompleted: true, hasFailures: false },
+ });
}
}
* 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,
if (!isCompleted) {
IndexationNotificationHelper.markCompletedNotificationAsToDisplay();
+
this.setState({
notificationType: hasFailures
? IndexationNotificationType.InProgressWithFailure
this.setState({
notificationType: IndexationNotificationType.Completed,
});
+
IndexationNotificationHelper.markCompletedNotificationAsDisplayed();
// Hide after some time
render() {
const { notificationType } = this.state;
+
const {
indexationContext: {
- status: { percentCompleted },
+ status: { completedCount, total },
},
} = this.props;
return !this.isSystemAdmin ? null : (
<IndexationNotificationRenderer
+ completedCount={completedCount}
+ total={total}
type={notificationType}
- percentCompleted={percentCompleted}
- isSystemAdmin={this.isSystemAdmin}
/>
);
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
/* eslint-disable react/no-unused-prop-types */
import classNames from 'classnames';
import { TaskStatuses, TaskTypes } from '../../../types/tasks';
export interface IndexationNotificationRendererProps {
+ completedCount?: number;
+ total?: number;
type?: IndexationNotificationType;
- percentCompleted: number;
- isSystemAdmin: boolean;
}
const NOTIFICATION_VARIANTS: { [key in IndexationNotificationType]: AlertProps['variant'] } = {
};
export default function IndexationNotificationRenderer(props: IndexationNotificationRendererProps) {
- const { type } = props;
+ const { completedCount, total, type } = props;
return (
<div className={classNames({ 'indexation-notification-wrapper': type })}>
>
{type !== undefined && (
<div className="display-flex-center">
- {type === IndexationNotificationType.Completed && renderCompletedBanner(props)}
+ {type === IndexationNotificationType.Completed && renderCompletedBanner()}
+
{type === IndexationNotificationType.CompletedWithFailure &&
- renderCompletedWithFailureBanner(props)}
- {type === IndexationNotificationType.InProgress && renderInProgressBanner(props)}
+ renderCompletedWithFailureBanner()}
+
+ {type === IndexationNotificationType.InProgress &&
+ renderInProgressBanner(completedCount as number, total as number)}
+
{type === IndexationNotificationType.InProgressWithFailure &&
- renderInProgressWithFailureBanner(props)}
+ renderInProgressWithFailureBanner(completedCount as number, total as number)}
</div>
)}
</Alert>
);
}
-function renderCompletedBanner(_props: IndexationNotificationRendererProps) {
+function renderCompletedBanner() {
return <span className="spacer-right">{translate('indexation.completed')}</span>;
}
-function renderCompletedWithFailureBanner(props: IndexationNotificationRendererProps) {
- const { isSystemAdmin } = props;
-
+function renderCompletedWithFailureBanner() {
return (
<span className="spacer-right">
<FormattedMessage
- id="indexation.completed_with_error"
defaultMessage={translate('indexation.completed_with_error')}
+ id="indexation.completed_with_error"
values={{
- link: isSystemAdmin
- ? renderBackgroundTasksPageLink(true, translate('indexation.completed_with_error.link'))
- : translate('indexation.completed_with_error.link'),
+ link: renderBackgroundTasksPageLink(
+ true,
+ translate('indexation.completed_with_error.link')
+ ),
}}
/>
</span>
);
}
-function renderInProgressBanner(props: IndexationNotificationRendererProps) {
- const { percentCompleted, isSystemAdmin } = props;
-
+function renderInProgressBanner(completedCount: number, total: number) {
return (
<>
<span className="spacer-right">{`${translate('indexation.in_progress')} ${translate(
'indexation.projects_unavailable'
)}`}</span>
<i className="spinner spacer-right" />
+
<span className="spacer-right">
- {translateWithParameters('indexation.progression', percentCompleted)}
+ {translateWithParameters(
+ 'indexation.progression',
+ completedCount.toString(),
+ total.toString()
+ )}
+ </span>
+
+ <span className="spacer-right">
+ <FormattedMessage
+ id="indexation.admin_link"
+ defaultMessage={translate('indexation.admin_link')}
+ values={{
+ link: renderBackgroundTasksPageLink(false, translate('background_tasks.page')),
+ }}
+ />
</span>
- {isSystemAdmin && (
- <span className="spacer-right">
- <FormattedMessage
- id="indexation.admin_link"
- defaultMessage={translate('indexation.admin_link')}
- values={{
- link: renderBackgroundTasksPageLink(false, translate('background_tasks.page')),
- }}
- />
- </span>
- )}
</>
);
}
-function renderInProgressWithFailureBanner(props: IndexationNotificationRendererProps) {
- const { percentCompleted, isSystemAdmin } = props;
-
+function renderInProgressWithFailureBanner(completedCount: number, total: number) {
return (
<>
<span className="spacer-right">{`${translate('indexation.in_progress')} ${translate(
'indexation.projects_unavailable'
)}`}</span>
<i className="spinner spacer-right" />
+
<span className="spacer-right">
<FormattedMessage
id="indexation.progression_with_error"
defaultMessage={translateWithParameters(
'indexation.progression_with_error',
- percentCompleted
+ completedCount.toString(),
+ total.toString()
)}
values={{
- link: isSystemAdmin
- ? renderBackgroundTasksPageLink(
- true,
- translate('indexation.progression_with_error.link')
- )
- : translate('indexation.progression_with_error.link'),
+ link: renderBackgroundTasksPageLink(
+ true,
+ translate('indexation.progression_with_error.link')
+ ),
}}
/>
</span>
* 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 { mockAppState } from '../../../../helpers/testMocks';
import { IndexationStatus } from '../../../../types/indexation';
-import { IndexationContext } from '../IndexationContext';
import {
IndexationContextProvider,
IndexationContextProviderProps,
expect(IndexationNotificationHelper.startPolling).not.toHaveBeenCalled();
const expectedStatus: IndexationStatus = {
- isCompleted: true,
- percentCompleted: 100,
hasFailures: false,
+ isCompleted: true,
};
+
expect(wrapper.state().status).toEqual(expectedStatus);
});
it('should update the state on new status', () => {
const wrapper = mountRender();
- const triggerNewStatus = (IndexationNotificationHelper.startPolling as jest.Mock).mock
+ const triggerNewStatus = jest.mocked(IndexationNotificationHelper.startPolling).mock
.calls[0][0] as (status: IndexationStatus) => void;
+
const newStatus: IndexationStatus = {
- isCompleted: true,
- percentCompleted: 100,
hasFailures: false,
+ isCompleted: true,
};
triggerNewStatus(newStatus);
);
}
-class TestComponent extends React.PureComponent {
- context: IndexationStatus = { hasFailures: false, isCompleted: false, percentCompleted: 0 };
- static contextType = IndexationContext;
-
- render() {
- return <h1>TestComponent</h1>;
- }
+function TestComponent() {
+ return <h1>TestComponent</h1>;
}
* 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 { mockCurrentUser } from '../../../../helpers/testMocks';
describe('Completed banner', () => {
it('should be displayed', () => {
- (
- IndexationNotificationHelper.shouldDisplayCompletedNotification as jest.Mock
- ).mockReturnValueOnce(true);
+ jest
+ .mocked(IndexationNotificationHelper.shouldDisplayCompletedNotification)
+ .mockReturnValueOnce(true);
const wrapper = shallowRender();
wrapper.setProps({
indexationContext: {
- status: { isCompleted: true, percentCompleted: 100, hasFailures: false },
+ status: { hasFailures: false, isCompleted: true },
},
});
});
it('should be displayed at startup', () => {
- (
- IndexationNotificationHelper.shouldDisplayCompletedNotification as jest.Mock
- ).mockReturnValueOnce(true);
+ jest
+ .mocked(IndexationNotificationHelper.shouldDisplayCompletedNotification)
+ .mockReturnValueOnce(true);
const wrapper = shallowRender({
indexationContext: {
- status: { isCompleted: true, percentCompleted: 100, hasFailures: false },
+ status: { hasFailures: false, isCompleted: true },
},
});
expect(wrapper.state().notificationType).toBe(IndexationNotificationType.Completed);
});
- it('should be hidden once displayed', () => {
+ it('should be hidden once completed without failure', () => {
jest.useFakeTimers();
- (
- IndexationNotificationHelper.shouldDisplayCompletedNotification as jest.Mock
- ).mockReturnValueOnce(true);
+
+ jest
+ .mocked(IndexationNotificationHelper.shouldDisplayCompletedNotification)
+ .mockReturnValueOnce(true);
const wrapper = shallowRender({
indexationContext: {
- status: { isCompleted: true, percentCompleted: 100, hasFailures: false },
+ status: { hasFailures: false, isCompleted: true },
},
});
expect(IndexationNotificationHelper.markCompletedNotificationAsDisplayed).toHaveBeenCalled();
jest.runAllTimers();
+
expect(wrapper.state().notificationType).toBeUndefined();
jest.useRealTimers();
it('should display the completed-with-failure banner', () => {
const wrapper = shallowRender({
- indexationContext: { status: { isCompleted: true, percentCompleted: 100, hasFailures: true } },
+ indexationContext: {
+ status: { hasFailures: true, isCompleted: true },
+ },
});
expect(wrapper.state().notificationType).toBe(IndexationNotificationType.CompletedWithFailure);
it('should display the progress banner', () => {
const wrapper = shallowRender({
- indexationContext: { status: { isCompleted: false, percentCompleted: 23, hasFailures: false } },
+ indexationContext: {
+ status: { completedCount: 23, hasFailures: false, isCompleted: false, total: 42 },
+ },
});
expect(IndexationNotificationHelper.markCompletedNotificationAsToDisplay).toHaveBeenCalled();
it('should display the progress-with-failure banner', () => {
const wrapper = shallowRender({
- indexationContext: { status: { isCompleted: false, percentCompleted: 23, hasFailures: true } },
+ indexationContext: {
+ status: { completedCount: 23, hasFailures: true, isCompleted: false, total: 42 },
+ },
});
expect(IndexationNotificationHelper.markCompletedNotificationAsToDisplay).toHaveBeenCalled();
<IndexationNotification
currentUser={mockCurrentUser()}
indexationContext={{
- status: { isCompleted: false, percentCompleted: 23, hasFailures: false },
+ status: { completedCount: 23, hasFailures: false, isCompleted: false, total: 42 },
}}
{...props}
/>
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
import { setImmediate } from 'timers';
import { getIndexationStatus } from '../../../../api/ce';
import { get, remove, save } from '../../../../helpers/storage';
it('should properly start & stop polling for indexation status', async () => {
const onNewStatus = jest.fn();
+
const newStatus: IndexationStatus = {
- isCompleted: false,
- percentCompleted: 100,
+ completedCount: 23,
hasFailures: false,
+ isCompleted: false,
+ total: 42,
};
- (getIndexationStatus as jest.Mock).mockResolvedValueOnce(newStatus);
+
+ jest.mocked(getIndexationStatus).mockResolvedValueOnce(newStatus);
IndexationNotificationHelper.startPolling(onNewStatus);
expect(getIndexationStatus).toHaveBeenCalled();
jest.runOnlyPendingTimers();
expect(getIndexationStatus).toHaveBeenCalledTimes(2);
- (getIndexationStatus as jest.Mock).mockClear();
+ jest.mocked(getIndexationStatus).mockClear();
IndexationNotificationHelper.stopPolling();
jest.runAllTimers();
expect(save).toHaveBeenCalledWith(expect.any(String), 'true');
- (get as jest.Mock).mockReturnValueOnce('true');
+ jest.mocked(get).mockReturnValueOnce('true');
let shouldDisplay = IndexationNotificationHelper.shouldDisplayCompletedNotification();
expect(shouldDisplay).toBe(true);
* 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 { IndexationNotificationType } from '../../../../types/indexation';
} from '../IndexationNotificationRenderer';
it.each([
- [IndexationNotificationType.InProgress, false],
- [IndexationNotificationType.InProgress, true],
- [IndexationNotificationType.InProgressWithFailure, false],
- [IndexationNotificationType.InProgressWithFailure, true],
- [IndexationNotificationType.Completed, false],
- [IndexationNotificationType.Completed, true],
- [IndexationNotificationType.CompletedWithFailure, false],
- [IndexationNotificationType.CompletedWithFailure, true],
-])(
- 'should render correctly for type=%p & isSystemAdmin=%p',
- (type: IndexationNotificationType, isSystemAdmin: boolean) => {
- expect(shallowRender({ type, isSystemAdmin })).toMatchSnapshot();
- }
-);
+ [IndexationNotificationType.InProgress],
+ [IndexationNotificationType.InProgressWithFailure],
+ [IndexationNotificationType.Completed],
+ [IndexationNotificationType.CompletedWithFailure],
+])('should render correctly for type=%p', (type: IndexationNotificationType) => {
+ expect(shallowRender({ type })).toMatchSnapshot();
+});
function shallowRender(props: Partial<IndexationNotificationRendererProps> = {}) {
return shallow<IndexationNotificationRendererProps>(
<IndexationNotificationRenderer
+ completedCount={23}
+ total={42}
type={IndexationNotificationType.InProgress}
- percentCompleted={25}
- isSystemAdmin={false}
{...props}
/>
);
expect(reload).not.toHaveBeenCalled();
wrapper.setProps({
- indexationContext: { status: { isCompleted: true, percentCompleted: 100, hasFailures: true } },
+ indexationContext: {
+ status: { hasFailures: true, isCompleted: true },
+ },
});
wrapper.update();
expect(reload).not.toHaveBeenCalled();
wrapper.setProps({
- indexationContext: { status: { isCompleted: true, percentCompleted: 100, hasFailures: false } },
+ indexationContext: {
+ status: { hasFailures: false, isCompleted: true },
+ },
});
wrapper.update();
return shallow<PageUnavailableDueToIndexation>(
<PageUnavailableDueToIndexation
indexationContext={{
- status: { isCompleted: false, percentCompleted: 23, hasFailures: false },
+ status: { completedCount: 23, hasFailures: false, isCompleted: false, total: 42 },
}}
/>
);
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`should render correctly for type="Completed" & isSystemAdmin=false 1`] = `
+exports[`should render correctly for type="Completed" 1`] = `
<div
className="indexation-notification-wrapper"
>
</div>
`;
-exports[`should render correctly for type="Completed" & isSystemAdmin=true 1`] = `
-<div
- className="indexation-notification-wrapper"
->
- <Alert
- aria-live="assertive"
- className="indexation-notification-banner"
- display="banner"
- variant="success"
- >
- <div
- className="display-flex-center"
- >
- <span
- className="spacer-right"
- >
- indexation.completed
- </span>
- </div>
- </Alert>
-</div>
-`;
-
-exports[`should render correctly for type="CompletedWithFailure" & isSystemAdmin=false 1`] = `
-<div
- className="indexation-notification-wrapper"
->
- <Alert
- aria-live="assertive"
- className="indexation-notification-banner"
- display="banner"
- variant="error"
- >
- <div
- className="display-flex-center"
- >
- <span
- className="spacer-right"
- >
- <FormattedMessage
- defaultMessage="indexation.completed_with_error"
- id="indexation.completed_with_error"
- values={
- {
- "link": "indexation.completed_with_error.link",
- }
- }
- />
- </span>
- </div>
- </Alert>
-</div>
-`;
-
-exports[`should render correctly for type="CompletedWithFailure" & isSystemAdmin=true 1`] = `
+exports[`should render correctly for type="CompletedWithFailure" 1`] = `
<div
className="indexation-notification-wrapper"
>
</div>
`;
-exports[`should render correctly for type="InProgress" & isSystemAdmin=false 1`] = `
-<div
- className="indexation-notification-wrapper"
->
- <Alert
- aria-live="assertive"
- className="indexation-notification-banner"
- display="banner"
- variant="warning"
- >
- <div
- className="display-flex-center"
- >
- <span
- className="spacer-right"
- >
- indexation.in_progress indexation.projects_unavailable
- </span>
- <i
- className="spinner spacer-right"
- />
- <span
- className="spacer-right"
- >
- indexation.progression.25
- </span>
- </div>
- </Alert>
-</div>
-`;
-
-exports[`should render correctly for type="InProgress" & isSystemAdmin=true 1`] = `
+exports[`should render correctly for type="InProgress" 1`] = `
<div
className="indexation-notification-wrapper"
>
<span
className="spacer-right"
>
- indexation.progression.25
+ indexation.progression.23.42
</span>
<span
className="spacer-right"
</div>
`;
-exports[`should render correctly for type="InProgressWithFailure" & isSystemAdmin=false 1`] = `
-<div
- className="indexation-notification-wrapper"
->
- <Alert
- aria-live="assertive"
- className="indexation-notification-banner"
- display="banner"
- variant="error"
- >
- <div
- className="display-flex-center"
- >
- <span
- className="spacer-right"
- >
- indexation.in_progress indexation.projects_unavailable
- </span>
- <i
- className="spinner spacer-right"
- />
- <span
- className="spacer-right"
- >
- <FormattedMessage
- defaultMessage="indexation.progression_with_error.25"
- id="indexation.progression_with_error"
- values={
- {
- "link": "indexation.progression_with_error.link",
- }
- }
- />
- </span>
- </div>
- </Alert>
-</div>
-`;
-
-exports[`should render correctly for type="InProgressWithFailure" & isSystemAdmin=true 1`] = `
+exports[`should render correctly for type="InProgressWithFailure" 1`] = `
<div
className="indexation-notification-wrapper"
>
className="spacer-right"
>
<FormattedMessage
- defaultMessage="indexation.progression_with_error.25"
+ defaultMessage="indexation.progression_with_error.23.42"
id="indexation.progression_with_error"
values={
{
* 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';
it('should render correctly', () => {
const indexationContext: IndexationContextInterface = {
- status: { isCompleted: true, percentCompleted: 87, hasFailures: false },
+ status: { hasFailures: false, isCompleted: true },
};
const wrapper = mountRender(indexationContext);
return mount(
<IndexationContext.Provider
value={{
- status: { isCompleted: false, percentCompleted: 23, hasFailures: false },
+ status: { completedCount: 23, hasFailures: false, isCompleted: false, total: 42 },
...indexationContext,
}}
>
);
}
-class TestComponent extends React.PureComponent<WithIndexationContextProps> {
- render() {
- return <h1>TestComponent</h1>;
- }
+function TestComponent(_props: WithIndexationContextProps) {
+ return <h1>TestComponent</h1>;
}
const TestComponentWithIndexationContext = withIndexationContext(TestComponent);
return render(
<IndexationContext.Provider
value={{
- status: { isCompleted: false, percentCompleted: 23, hasFailures: false },
+ status: { completedCount: 23, isCompleted: false, hasFailures: false, total: 42 },
}}
>
<TestComponentWithGuard />
* 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;
- hasFailures: boolean;
+
+interface IndexationStatusInProgress {
+ completedCount: number;
+ isCompleted: false;
+ total: number;
+}
+
+interface IndexationStatusCompleted {
+ completedCount?: number;
+ isCompleted: true;
+ total?: number;
}
+export type IndexationStatus = {
+ hasFailures: boolean;
+} & (IndexationStatusInProgress | IndexationStatusCompleted);
+
export interface IndexationContextInterface {
status: IndexationStatus;
}
indexation.details_unavailable=Details are unavailable until this process is complete.
indexation.link_unavailable=The link to these results is unavailable until this process is complete.
indexation.projects_unavailable=Some projects will be unavailable until this process is complete.
-indexation.progression={0}% complete.
-indexation.progression_with_error={0}% complete with some {link}.
+indexation.progression={0} out of {1} projects reindexed.
+indexation.progression_with_error={0} out of {1} projects reindexed with some {link}.
indexation.progression_with_error.link=tasks failing
indexation.completed=All project data has been reloaded.
indexation.completed_with_error=SonarQube completed the reload of project data. Some {link} causing some projects to remain unavailable.