* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+/* eslint-disable react/no-unused-state */
import * as React from 'react';
import { withAppState } from '../../../components/hoc/withAppState';
import { IndexationContextInterface, IndexationStatus } from '../../../types/indexation';
> {
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) {
+ if (this.props.appState.needIssueSync) {
IndexationNotificationHelper.startPolling(this.handleNewStatus);
+ } else {
+ this.setState({ status: { isCompleted: true, percentCompleted: 100, hasFailures: false } });
}
}
WithIndexationContextProps
} from '../../../components/hoc/withIndexationContext';
import { hasGlobalPermission, isLoggedIn } from '../../../helpers/users';
+import { IndexationNotificationType } from '../../../types/indexation';
import './IndexationNotification.css';
import IndexationNotificationHelper from './IndexationNotificationHelper';
import IndexationNotificationRenderer from './IndexationNotificationRenderer';
}
interface State {
- progression?: IndexationProgression;
-}
-
-export enum IndexationProgression {
- InProgress,
- Completed
+ notificationType?: IndexationNotificationType;
}
export class IndexationNotification extends React.PureComponent<Props, State> {
- state: State;
+ state: State = {};
isSystemAdmin = false;
constructor(props: Props) {
super(props);
- this.state = { progression: undefined };
this.isSystemAdmin =
isLoggedIn(this.props.currentUser) && hasGlobalPermission(this.props.currentUser, 'admin');
}
this.refreshNotification();
}
- componentDidUpdate() {
- this.refreshNotification();
+ componentDidUpdate(prevProps: Props) {
+ if (prevProps.indexationContext.status !== this.props.indexationContext.status) {
+ this.refreshNotification();
+ }
}
refreshNotification() {
- if (!this.props.indexationContext.status.isCompleted) {
+ const { isCompleted, hasFailures } = this.props.indexationContext.status;
+
+ if (!isCompleted) {
IndexationNotificationHelper.markInProgressNotificationAsDisplayed();
- this.setState({ progression: IndexationProgression.InProgress });
+ this.setState({
+ notificationType: hasFailures
+ ? IndexationNotificationType.InProgressWithFailure
+ : IndexationNotificationType.InProgress
+ });
+ } else if (hasFailures) {
+ this.setState({ notificationType: IndexationNotificationType.CompletedWithFailure });
} else if (IndexationNotificationHelper.shouldDisplayCompletedNotification()) {
- this.setState({ progression: IndexationProgression.Completed });
+ this.setState({
+ notificationType: IndexationNotificationType.Completed
+ });
+ } else {
+ this.setState({ notificationType: undefined });
}
}
handleDismissCompletedNotification = () => {
- IndexationNotificationHelper.markCompletedNotificationAsDisplayed();
- this.setState({ progression: undefined });
+ IndexationNotificationHelper.markCompletedNotificationAsDismissed();
+ this.refreshNotification();
};
render() {
- const { progression } = this.state;
+ const { notificationType } = this.state;
const {
indexationContext: {
status: { percentCompleted }
}
} = this.props;
- if (progression === undefined) {
+ if (notificationType === undefined) {
return null;
}
return (
<IndexationNotificationRenderer
- progression={progression}
- percentCompleted={percentCompleted ?? 0}
+ type={notificationType}
+ percentCompleted={percentCompleted}
+ isSystemAdmin={this.isSystemAdmin}
onDismissCompletedNotification={this.handleDismissCompletedNotification}
- displayBackgroundTaskLink={this.isSystemAdmin}
/>
);
}
static startPolling(onNewStatus: (status: IndexationStatus) => void) {
this.stopPolling();
- this.interval = setInterval(async () => {
- const status = await getIndexationStatus();
- onNewStatus(status);
- }, POLLING_INTERVAL_MS);
+ // eslint-disable-next-line promise/catch-or-return
+ this.poll(onNewStatus).finally(() => {
+ this.interval = setInterval(() => this.poll(onNewStatus), POLLING_INTERVAL_MS);
+ });
}
static stopPolling() {
}
}
+ static async poll(onNewStatus: (status: IndexationStatus) => void) {
+ const status = await getIndexationStatus();
+
+ onNewStatus(status);
+ }
+
static markInProgressNotificationAsDisplayed() {
save(LS_INDEXATION_PROGRESS_WAS_DISPLAYED, true.toString());
}
- static markCompletedNotificationAsDisplayed() {
+ static markCompletedNotificationAsDismissed() {
remove(LS_INDEXATION_PROGRESS_WAS_DISPLAYED);
}
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router';
-import { ButtonLink } from 'sonar-ui-common/components/controls/buttons';
-import { Alert } from 'sonar-ui-common/components/ui/Alert';
+import { ClearButton } from 'sonar-ui-common/components/controls/buttons';
+import { Alert, AlertProps } from 'sonar-ui-common/components/ui/Alert';
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n';
-import { BackgroundTaskTypes } from '../../../apps/background-tasks/constants';
-import { IndexationProgression } from './IndexationNotification';
+import { BackgroundTaskTypes, STATUSES } from '../../../apps/background-tasks/constants';
+import { IndexationNotificationType } from '../../../types/indexation';
export interface IndexationNotificationRendererProps {
- progression: IndexationProgression;
+ type: IndexationNotificationType;
percentCompleted: number;
+ isSystemAdmin: boolean;
onDismissCompletedNotification: VoidFunction;
- displayBackgroundTaskLink?: boolean;
}
-export default function IndexationNotificationRenderer(props: IndexationNotificationRendererProps) {
- const { progression, percentCompleted, displayBackgroundTaskLink } = props;
+const NOTIFICATION_VARIANTS: { [key in IndexationNotificationType]: AlertProps['variant'] } = {
+ [IndexationNotificationType.InProgress]: 'warning',
+ [IndexationNotificationType.InProgressWithFailure]: 'error',
+ [IndexationNotificationType.Completed]: 'success',
+ [IndexationNotificationType.CompletedWithFailure]: 'error'
+};
- const inProgress = progression === IndexationProgression.InProgress;
+export default function IndexationNotificationRenderer(props: IndexationNotificationRendererProps) {
+ const { type } = props;
return (
<div className="indexation-notification-wrapper">
<Alert
className="indexation-notification-banner"
display="banner"
- variant={inProgress ? 'warning' : 'success'}>
+ variant={NOTIFICATION_VARIANTS[type]}>
<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>
- {displayBackgroundTaskLink && (
- <span className="spacer-left">
- <FormattedMessage
- id="indexation.in_progress.admin_details"
- defaultMessage={translate('indexation.in_progress.admin_details')}
- values={{
- link: (
- <Link
- to={{
- pathname: '/admin/background_tasks',
- query: { taskType: BackgroundTaskTypes.IssueSync }
- }}>
- {translate('background_tasks.page')}
- </Link>
- )
- }}
- />
- </span>
- )}
- </>
- ) : (
- <>
- <span>{translate('indexation.completed')}</span>
- <ButtonLink className="spacer-left" onClick={props.onDismissCompletedNotification}>
- <strong>{translate('dismiss')}</strong>
- </ButtonLink>
- </>
- )}
+ {type === IndexationNotificationType.Completed && renderCompletedBanner(props)}
+ {type === IndexationNotificationType.CompletedWithFailure &&
+ renderCompletedWithFailureBanner(props)}
+ {type === IndexationNotificationType.InProgress && renderInProgressBanner(props)}
+ {type === IndexationNotificationType.InProgressWithFailure &&
+ renderInProgressWithFailureBanner(props)}
</div>
</Alert>
</div>
);
}
+
+function renderCompletedBanner(props: IndexationNotificationRendererProps) {
+ return (
+ <>
+ <span className="spacer-right">{translate('indexation.completed')}</span>
+ <ClearButton
+ className="button-tiny"
+ title={translate('dismiss')}
+ onClick={props.onDismissCompletedNotification}
+ />
+ </>
+ );
+}
+
+function renderCompletedWithFailureBanner(props: IndexationNotificationRendererProps) {
+ const { isSystemAdmin } = props;
+
+ return (
+ <span className="spacer-right">
+ <FormattedMessage
+ id="indexation.completed_with_error"
+ defaultMessage={translate('indexation.completed_with_error')}
+ values={{
+ link: isSystemAdmin
+ ? renderBackgroundTasksPageLink(true, translate('indexation.completed_with_error.link'))
+ : translate('indexation.completed_with_error.link')
+ }}
+ />
+ </span>
+ );
+}
+
+function renderInProgressBanner(props: IndexationNotificationRendererProps) {
+ const { percentCompleted, isSystemAdmin } = props;
+
+ return (
+ <>
+ <span className="spacer-right">{translate('indexation.in_progress')}</span>
+ <i className="spinner spacer-right" />
+ <span className="spacer-right">
+ {translateWithParameters('indexation.progression', percentCompleted)}
+ </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;
+
+ return (
+ <>
+ <span className="spacer-right">{translate('indexation.in_progress')}</span>
+ <i className="spinner spacer-right" />
+ <span className="spacer-right">
+ <FormattedMessage
+ id="indexation.progression_with_error"
+ defaultMessage={translateWithParameters(
+ 'indexation.progression_with_error',
+ percentCompleted
+ )}
+ values={{
+ link: isSystemAdmin
+ ? renderBackgroundTasksPageLink(
+ true,
+ translate('indexation.progression_with_error.link')
+ )
+ : translate('indexation.progression_with_error.link')
+ }}
+ />
+ </span>
+ </>
+ );
+}
+
+function renderBackgroundTasksPageLink(hasError: boolean, text: string) {
+ return (
+ <Link
+ to={{
+ pathname: '/admin/background_tasks',
+ query: {
+ taskType: BackgroundTaskTypes.IssueSync,
+ status: hasError ? STATUSES.FAILED : undefined
+ }
+ }}>
+ {text}
+ </Link>
+ );
+}
jest.mock('../IndexationNotificationHelper');
-it('should render correctly & start polling', () => {
+it('should render correctly and start polling if issue sync is needed', () => {
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(wrapper).toMatchSnapshot();
expect(IndexationNotificationHelper.startPolling).toHaveBeenCalled();
});
-it('should not start polling if not needed', () => {
- mountRender({ appState: { needIssueSync: false } });
+it('should not start polling if no issue sync is needed', () => {
+ const wrapper = mountRender({ appState: { needIssueSync: false } });
expect(IndexationNotificationHelper.startPolling).not.toHaveBeenCalled();
+
+ const expectedStatus: IndexationStatus = {
+ isCompleted: true,
+ percentCompleted: 100,
+ hasFailures: false
+ };
+ expect(wrapper.state().status).toEqual(expectedStatus);
});
it('should update the state on new status & stop polling if indexation is complete', () => {
const triggerNewStatus = (IndexationNotificationHelper.startPolling as jest.Mock).mock
.calls[0][0] as (status: IndexationStatus) => void;
- const newStatus = { isCompleted: true, percentCompleted: 100 };
+ const newStatus: IndexationStatus = {
+ isCompleted: true,
+ percentCompleted: 100,
+ hasFailures: false
+ };
triggerNewStatus(newStatus);
import { shallow } from 'enzyme';
import * as React from 'react';
import { mockCurrentUser } from '../../../../helpers/testMocks';
-import { IndexationNotification, IndexationProgression } from '../IndexationNotification';
+import { IndexationNotificationType } from '../../../../types/indexation';
+import { IndexationNotification } from '../IndexationNotification';
import IndexationNotificationHelper from '../IndexationNotificationHelper';
import IndexationNotificationRenderer from '../IndexationNotificationRenderer';
jest.mock('../IndexationNotificationHelper');
-it('should display the warning banner if indexation is in progress', () => {
- const wrapper = shallowRender();
+describe('Completed banner', () => {
+ it('should be displayed', () => {
+ (IndexationNotificationHelper.shouldDisplayCompletedNotification as jest.Mock).mockReturnValueOnce(
+ true
+ );
- expect(IndexationNotificationHelper.markInProgressNotificationAsDisplayed).toHaveBeenCalled();
- expect(wrapper.state().progression).toBe(IndexationProgression.InProgress);
-});
+ const wrapper = shallowRender();
-it('should display the success banner when indexation is complete', () => {
- (IndexationNotificationHelper.shouldDisplayCompletedNotification as jest.Mock).mockReturnValueOnce(
- true
- );
+ wrapper.setProps({
+ indexationContext: {
+ status: { isCompleted: true, percentCompleted: 100, hasFailures: false }
+ }
+ });
- const wrapper = shallowRender();
+ expect(IndexationNotificationHelper.shouldDisplayCompletedNotification).toHaveBeenCalled();
+ expect(wrapper.state().notificationType).toBe(IndexationNotificationType.Completed);
+ });
- wrapper.setProps({ indexationContext: { status: { isCompleted: true } } });
+ it('should be displayed at startup', () => {
+ (IndexationNotificationHelper.shouldDisplayCompletedNotification as jest.Mock).mockReturnValueOnce(
+ true
+ );
- expect(IndexationNotificationHelper.shouldDisplayCompletedNotification).toHaveBeenCalled();
- expect(wrapper.state().progression).toBe(IndexationProgression.Completed);
-});
+ const wrapper = shallowRender({
+ indexationContext: {
+ status: { isCompleted: true, percentCompleted: 100, hasFailures: false }
+ }
+ });
-it('should render correctly completed notification at startup', () => {
- (IndexationNotificationHelper.shouldDisplayCompletedNotification as jest.Mock).mockReturnValueOnce(
- true
- );
+ expect(IndexationNotificationHelper.shouldDisplayCompletedNotification).toHaveBeenCalled();
+ expect(wrapper.state().notificationType).toBe(IndexationNotificationType.Completed);
+ });
+
+ it('should be hidden on dismiss action', () => {
+ (IndexationNotificationHelper.shouldDisplayCompletedNotification as jest.Mock).mockReturnValueOnce(
+ true
+ );
+
+ const wrapper = shallowRender({
+ indexationContext: {
+ status: { isCompleted: true, percentCompleted: 100, hasFailures: false }
+ }
+ });
+
+ expect(wrapper.state().notificationType).toBe(IndexationNotificationType.Completed);
+
+ wrapper
+ .find(IndexationNotificationRenderer)
+ .props()
+ .onDismissCompletedNotification();
+ expect(IndexationNotificationHelper.markCompletedNotificationAsDismissed).toHaveBeenCalled();
+ expect(wrapper.state().notificationType).toBeUndefined();
+ });
+});
+
+it('should display the completed-with-failure banner', () => {
const wrapper = shallowRender({
- indexationContext: { status: { isCompleted: true } }
+ indexationContext: { status: { isCompleted: true, percentCompleted: 100, hasFailures: true } }
});
- expect(IndexationNotificationHelper.markInProgressNotificationAsDisplayed).not.toHaveBeenCalled();
- expect(IndexationNotificationHelper.shouldDisplayCompletedNotification).toHaveBeenCalled();
- expect(wrapper.state().progression).toBe(IndexationProgression.Completed);
+ expect(wrapper.state().notificationType).toBe(IndexationNotificationType.CompletedWithFailure);
});
-it('should hide the success banner on dismiss action', () => {
- (IndexationNotificationHelper.shouldDisplayCompletedNotification as jest.Mock).mockReturnValueOnce(
- true
- );
-
+it('should display the progress banner', () => {
const wrapper = shallowRender({
- indexationContext: { status: { isCompleted: true } }
+ indexationContext: { status: { isCompleted: false, percentCompleted: 23, hasFailures: false } }
});
- wrapper
- .find(IndexationNotificationRenderer)
- .props()
- .onDismissCompletedNotification();
+ expect(IndexationNotificationHelper.markInProgressNotificationAsDisplayed).toHaveBeenCalled();
+ expect(wrapper.state().notificationType).toBe(IndexationNotificationType.InProgress);
+});
- expect(IndexationNotificationHelper.markCompletedNotificationAsDisplayed).toHaveBeenCalled();
- expect(wrapper.state().progression).toBeUndefined();
+it('should display the progress-with-failure banner', () => {
+ const wrapper = shallowRender({
+ indexationContext: { status: { isCompleted: false, percentCompleted: 23, hasFailures: true } }
+ });
+
+ expect(IndexationNotificationHelper.markInProgressNotificationAsDisplayed).toHaveBeenCalled();
+ expect(wrapper.state().notificationType).toBe(IndexationNotificationType.InProgressWithFailure);
});
function shallowRender(props?: Partial<IndexationNotification['props']>) {
return shallow<IndexationNotification>(
<IndexationNotification
currentUser={mockCurrentUser()}
- indexationContext={{ status: { isCompleted: false } }}
+ indexationContext={{
+ status: { isCompleted: false, percentCompleted: 23, hasFailures: false }
+ }}
{...props}
/>
);
it('should properly start & stop polling for indexation status', async () => {
const onNewStatus = jest.fn();
- const newStatus: IndexationStatus = { isCompleted: true, percentCompleted: 87 };
+ const newStatus: IndexationStatus = {
+ isCompleted: true,
+ percentCompleted: 100,
+ hasFailures: false
+ };
(getIndexationStatus as jest.Mock).mockResolvedValueOnce(newStatus);
IndexationNotificationHelper.startPolling(onNewStatus);
-
- jest.runOnlyPendingTimers();
expect(getIndexationStatus).toHaveBeenCalled();
await new Promise(setImmediate);
expect(onNewStatus).toHaveBeenCalledWith(newStatus);
+ jest.runOnlyPendingTimers();
+ expect(getIndexationStatus).toHaveBeenCalledTimes(2);
+
(getIndexationStatus as jest.Mock).mockClear();
IndexationNotificationHelper.stopPolling();
expect(shouldDisplay).toBe(true);
expect(get).toHaveBeenCalled();
- IndexationNotificationHelper.markCompletedNotificationAsDisplayed();
+ IndexationNotificationHelper.markCompletedNotificationAsDismissed();
expect(remove).toHaveBeenCalled();
import { shallow } from 'enzyme';
import * as React from 'react';
-import { ButtonLink } from 'sonar-ui-common/components/controls/buttons';
+import { ClearButton } from 'sonar-ui-common/components/controls/buttons';
import { click } from 'sonar-ui-common/helpers/testUtils';
-import { IndexationProgression } from '../IndexationNotification';
+import { IndexationNotificationType } from '../../../../types/indexation';
import IndexationNotificationRenderer, {
IndexationNotificationRendererProps
} from '../IndexationNotificationRenderer';
-it('should render correctly', () => {
- expect(shallowRender()).toMatchSnapshot('in-progress');
- expect(shallowRender({ displayBackgroundTaskLink: true })).toMatchSnapshot('in-progress-admin');
- expect(shallowRender({ progression: IndexationProgression.Completed })).toMatchSnapshot(
- 'completed'
- );
-});
+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();
+ }
+);
-it('should propagate the dismiss event', () => {
+it('should propagate the dismiss event from completed notification', () => {
const onDismissCompletedNotification = jest.fn();
const wrapper = shallowRender({
- progression: IndexationProgression.Completed,
+ type: IndexationNotificationType.Completed,
onDismissCompletedNotification
});
- click(wrapper.find(ButtonLink));
+ click(wrapper.find(ClearButton));
expect(onDismissCompletedNotification).toHaveBeenCalled();
});
function shallowRender(props: Partial<IndexationNotificationRendererProps> = {}) {
return shallow<IndexationNotificationRendererProps>(
<IndexationNotificationRenderer
- progression={IndexationProgression.InProgress}
+ type={IndexationNotificationType.InProgress}
percentCompleted={25}
+ isSystemAdmin={false}
onDismissCompletedNotification={jest.fn()}
{...props}
/>
return shallow(
<PageUnavailableDueToIndexation
indexationContext={{
- status: { isCompleted: false }
+ status: { isCompleted: false, percentCompleted: 23, hasFailures: false }
}}
pageContext={PageContext.Issues}
component={{ qualifier: ComponentQualifier.Portfolio, name: 'test-portfolio' }}
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly and start polling if issue sync is needed 1`] = `
+<IndexationContextProvider
+ appState={
+ Object {
+ "needIssueSync": true,
+ }
+ }
+>
+ <TestComponent>
+ <h1>
+ TestComponent
+ </h1>
+ </TestComponent>
+</IndexationContextProvider>
+`;
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`should render correctly: completed 1`] = `
+exports[`should render correctly for type="Completed" & isSystemAdmin=false 1`] = `
<div
className="indexation-notification-wrapper"
>
<div
className="display-flex-center"
>
- <span>
+ <span
+ className="spacer-right"
+ >
indexation.completed
</span>
- <ButtonLink
- className="spacer-left"
+ <ClearButton
+ className="button-tiny"
onClick={[MockFunction]}
+ title="dismiss"
+ />
+ </div>
+ </Alert>
+</div>
+`;
+
+exports[`should render correctly for type="Completed" & isSystemAdmin=true 1`] = `
+<div
+ className="indexation-notification-wrapper"
+>
+ <Alert
+ className="indexation-notification-banner"
+ display="banner"
+ variant="success"
+ >
+ <div
+ className="display-flex-center"
+ >
+ <span
+ className="spacer-right"
+ >
+ indexation.completed
+ </span>
+ <ClearButton
+ className="button-tiny"
+ onClick={[MockFunction]}
+ title="dismiss"
+ />
+ </div>
+ </Alert>
+</div>
+`;
+
+exports[`should render correctly for type="CompletedWithFailure" & isSystemAdmin=false 1`] = `
+<div
+ className="indexation-notification-wrapper"
+>
+ <Alert
+ className="indexation-notification-banner"
+ display="banner"
+ variant="error"
+ >
+ <div
+ className="display-flex-center"
+ >
+ <span
+ className="spacer-right"
>
- <strong>
- dismiss
- </strong>
- </ButtonLink>
+ <FormattedMessage
+ defaultMessage="indexation.completed_with_error"
+ id="indexation.completed_with_error"
+ values={
+ Object {
+ "link": "indexation.completed_with_error.link",
+ }
+ }
+ />
+ </span>
+ </div>
+ </Alert>
+</div>
+`;
+
+exports[`should render correctly for type="CompletedWithFailure" & isSystemAdmin=true 1`] = `
+<div
+ className="indexation-notification-wrapper"
+>
+ <Alert
+ 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={
+ Object {
+ "link": <Link
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/admin/background_tasks",
+ "query": Object {
+ "status": "FAILED",
+ "taskType": "ISSUE_SYNC",
+ },
+ }
+ }
+ >
+ indexation.completed_with_error.link
+ </Link>,
+ }
+ }
+ />
+ </span>
</div>
</Alert>
</div>
`;
-exports[`should render correctly: in-progress 1`] = `
+exports[`should render correctly for type="InProgress" & isSystemAdmin=false 1`] = `
<div
className="indexation-notification-wrapper"
>
<div
className="display-flex-center"
>
- <span>
+ <span
+ className="spacer-right"
+ >
indexation.in_progress
</span>
<i
- className="spinner spacer-left"
+ className="spinner spacer-right"
/>
<span
- className="spacer-left"
+ className="spacer-right"
>
- indexation.in_progress.details.25
+ indexation.progression.25
</span>
</div>
</Alert>
</div>
`;
-exports[`should render correctly: in-progress-admin 1`] = `
+exports[`should render correctly for type="InProgress" & isSystemAdmin=true 1`] = `
<div
className="indexation-notification-wrapper"
>
<div
className="display-flex-center"
>
- <span>
+ <span
+ className="spacer-right"
+ >
indexation.in_progress
</span>
<i
- className="spinner spacer-left"
+ className="spinner spacer-right"
/>
<span
- className="spacer-left"
+ className="spacer-right"
>
- indexation.in_progress.details.25
+ indexation.progression.25
</span>
<span
- className="spacer-left"
+ className="spacer-right"
>
<FormattedMessage
- defaultMessage="indexation.in_progress.admin_details"
- id="indexation.in_progress.admin_details"
+ defaultMessage="indexation.admin_link"
+ id="indexation.admin_link"
values={
Object {
"link": <Link
Object {
"pathname": "/admin/background_tasks",
"query": Object {
+ "status": undefined,
"taskType": "ISSUE_SYNC",
},
}
</Alert>
</div>
`;
+
+exports[`should render correctly for type="InProgressWithFailure" & isSystemAdmin=false 1`] = `
+<div
+ className="indexation-notification-wrapper"
+>
+ <Alert
+ className="indexation-notification-banner"
+ display="banner"
+ variant="error"
+ >
+ <div
+ className="display-flex-center"
+ >
+ <span
+ className="spacer-right"
+ >
+ indexation.in_progress
+ </span>
+ <i
+ className="spinner spacer-right"
+ />
+ <span
+ className="spacer-right"
+ >
+ <FormattedMessage
+ defaultMessage="indexation.progression_with_error.25"
+ id="indexation.progression_with_error"
+ values={
+ Object {
+ "link": "indexation.progression_with_error.link",
+ }
+ }
+ />
+ </span>
+ </div>
+ </Alert>
+</div>
+`;
+
+exports[`should render correctly for type="InProgressWithFailure" & isSystemAdmin=true 1`] = `
+<div
+ className="indexation-notification-wrapper"
+>
+ <Alert
+ className="indexation-notification-banner"
+ display="banner"
+ variant="error"
+ >
+ <div
+ className="display-flex-center"
+ >
+ <span
+ className="spacer-right"
+ >
+ indexation.in_progress
+ </span>
+ <i
+ className="spinner spacer-right"
+ />
+ <span
+ className="spacer-right"
+ >
+ <FormattedMessage
+ defaultMessage="indexation.progression_with_error.25"
+ id="indexation.progression_with_error"
+ values={
+ Object {
+ "link": <Link
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/admin/background_tasks",
+ "query": Object {
+ "status": "FAILED",
+ "taskType": "ISSUE_SYNC",
+ },
+ }
+ }
+ >
+ indexation.progression_with_error.link
+ </Link>,
+ }
+ }
+ />
+ </span>
+ </div>
+ </Alert>
+</div>
+`;
it('should render correctly', () => {
const indexationContext: IndexationContextInterface = {
- status: { isCompleted: true, percentCompleted: 87 }
+ status: { isCompleted: true, percentCompleted: 87, hasFailures: false }
};
const wrapper = mountRender(indexationContext);
function mountRender(indexationContext?: Partial<IndexationContextInterface>) {
return mount(
- <IndexationContext.Provider value={{ status: { isCompleted: false }, ...indexationContext }}>
+ <IndexationContext.Provider
+ value={{
+ status: { isCompleted: false, percentCompleted: 23, hasFailures: false },
+ ...indexationContext
+ }}>
<TestComponentWithIndexationContext />
</IndexationContext.Provider>
);
let wrapper = mountRender();
expect(wrapper.find(TestComponent).exists()).toBe(false);
- wrapper = mountRender({ status: { isCompleted: true } });
+ wrapper = mountRender({
+ status: { isCompleted: true, percentCompleted: 100, hasFailures: false }
+ });
expect(wrapper.find(TestComponent).exists()).toBe(true);
});
function mountRender(context?: Partial<IndexationContextInterface>) {
return mount(
- <IndexationContext.Provider value={{ status: { isCompleted: false }, ...context }}>
+ <IndexationContext.Provider
+ value={{
+ status: { isCompleted: false, percentCompleted: 23, hasFailures: false },
+ ...context
+ }}>
<TestComponentWithGuard />
</IndexationContext.Provider>
);
export interface IndexationStatus {
isCompleted: boolean;
- percentCompleted?: number;
+ percentCompleted: number;
+ hasFailures: boolean;
}
export interface IndexationContextInterface {
status: IndexationStatus;
}
+
+export enum IndexationNotificationType {
+ InProgress = 'InProgress',
+ InProgressWithFailure = 'InProgressWithFailure',
+ Completed = 'Completed',
+ CompletedWithFailure = 'CompletedWithFailure'
+}
#
#------------------------------------------------------------------------------
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.in_progress.admin_details=See {link}.
+indexation.progression={0}% complete.
+indexation.progression_with_error={0}% complete 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.
+indexation.completed_with_error.link=tasks failed
+indexation.admin_link=See {link} for more information.
indexation.page_unavailable.title.issues=Issues page is temporarily unavailable
indexation.page_unavailable.title.portfolios=Portfolios page is temporarily unavailable
indexation.page_unavailable.title={componentQualifier} {componentName} is temporarily unavailable