Преглед изворни кода

SONAR-13398 Display an additional message if some tasks are in error

tags/8.4.0.35506
Philippe Perrin пре 4 година
родитељ
комит
23d876af1e
15 измењених фајлова са 543 додато и 175 уклоњено
  1. 4
    9
      server/sonar-web/src/main/js/app/components/indexation/IndexationContextProvider.tsx
  2. 29
    20
      server/sonar-web/src/main/js/app/components/indexation/IndexationNotification.tsx
  3. 11
    5
      server/sonar-web/src/main/js/app/components/indexation/IndexationNotificationHelper.ts
  4. 120
    45
      server/sonar-web/src/main/js/app/components/indexation/IndexationNotificationRenderer.tsx
  5. 16
    14
      server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationContextProvider-test.tsx
  6. 69
    36
      server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationNotification-test.tsx
  7. 9
    4
      server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationNotificationHelper-test.tsx
  8. 22
    13
      server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationNotificationRenderer-test.tsx
  9. 1
    1
      server/sonar-web/src/main/js/app/components/indexation/__tests__/PageUnavailableDueToIndexation-test.tsx
  10. 17
    0
      server/sonar-web/src/main/js/app/components/indexation/__tests__/__snapshots__/IndexationContextProvider-test.tsx.snap
  11. 216
    21
      server/sonar-web/src/main/js/app/components/indexation/__tests__/__snapshots__/IndexationNotificationRenderer-test.tsx.snap
  12. 6
    2
      server/sonar-web/src/main/js/components/hoc/__tests__/withIndexationContext-test.tsx
  13. 8
    2
      server/sonar-web/src/main/js/components/hoc/__tests__/withIndexationGuard-test.tsx
  14. 9
    1
      server/sonar-web/src/main/js/types/indexation.ts
  15. 6
    2
      sonar-core/src/main/resources/org/sonar/l10n/core.properties

+ 4
- 9
server/sonar-web/src/main/js/app/components/indexation/IndexationContextProvider.tsx Прегледај датотеку

@@ -18,6 +18,7 @@
* 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';
@@ -34,19 +35,13 @@ export class IndexationContextProvider extends React.PureComponent<
> {
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 } });
}
}


+ 29
- 20
server/sonar-web/src/main/js/app/components/indexation/IndexationNotification.tsx Прегледај датотеку

@@ -24,6 +24,7 @@ import withIndexationContext, {
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';
@@ -33,22 +34,16 @@ interface Props extends WithIndexationContextProps {
}

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');
}
@@ -57,42 +52,56 @@ export class IndexationNotification extends React.PureComponent<Props, State> {
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}
/>
);
}

+ 11
- 5
server/sonar-web/src/main/js/app/components/indexation/IndexationNotificationHelper.ts Прегледај датотеку

@@ -31,10 +31,10 @@ export default class IndexationNotificationHelper {
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() {
@@ -43,11 +43,17 @@ export default class IndexationNotificationHelper {
}
}

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


+ 120
- 45
server/sonar-web/src/main/js/app/components/indexation/IndexationNotificationRenderer.tsx Прегледај датотеку

@@ -21,68 +21,143 @@
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>
);
}

+ 16
- 14
server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationContextProvider-test.tsx Прегледај датотеку

@@ -29,26 +29,24 @@ beforeEach(() => jest.clearAllMocks());

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', () => {
@@ -56,7 +54,11 @@ it('should update the state on new status & stop polling if indexation is comple

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


+ 69
- 36
server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationNotification-test.tsx Прегледај датотеку

@@ -21,7 +21,8 @@
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';

@@ -29,63 +30,95 @@ beforeEach(() => jest.clearAllMocks());

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

+ 9
- 4
server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationNotificationHelper-test.tsx Прегледај датотеку

@@ -40,17 +40,22 @@ jest.mock('sonar-ui-common/helpers/storage', () => ({

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();
@@ -70,7 +75,7 @@ it('should properly handle the flag to show the completed banner', () => {
expect(shouldDisplay).toBe(true);
expect(get).toHaveBeenCalled();

IndexationNotificationHelper.markCompletedNotificationAsDisplayed();
IndexationNotificationHelper.markCompletedNotificationAsDismissed();

expect(remove).toHaveBeenCalled();


+ 22
- 13
server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationNotificationRenderer-test.tsx Прегледај датотеку

@@ -20,37 +20,46 @@

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}
/>

+ 1
- 1
server/sonar-web/src/main/js/app/components/indexation/__tests__/PageUnavailableDueToIndexation-test.tsx Прегледај датотеку

@@ -47,7 +47,7 @@ function shallowRender(props?: PageUnavailableDueToIndexation['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' }}

+ 17
- 0
server/sonar-web/src/main/js/app/components/indexation/__tests__/__snapshots__/IndexationContextProvider-test.tsx.snap Прегледај датотеку

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

+ 216
- 21
server/sonar-web/src/main/js/app/components/indexation/__tests__/__snapshots__/IndexationNotificationRenderer-test.tsx.snap Прегледај датотеку

@@ -1,6 +1,6 @@
// 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"
>
@@ -12,23 +12,123 @@ exports[`should render correctly: completed 1`] = `
<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"
>
@@ -40,23 +140,25 @@ exports[`should render correctly: in-progress 1`] = `
<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"
>
@@ -68,23 +170,25 @@ exports[`should render correctly: in-progress-admin 1`] = `
<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
@@ -94,6 +198,7 @@ exports[`should render correctly: in-progress-admin 1`] = `
Object {
"pathname": "/admin/background_tasks",
"query": Object {
"status": undefined,
"taskType": "ISSUE_SYNC",
},
}
@@ -109,3 +214,93 @@ exports[`should render correctly: in-progress-admin 1`] = `
</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>
`;

+ 6
- 2
server/sonar-web/src/main/js/components/hoc/__tests__/withIndexationContext-test.tsx Прегледај датотеку

@@ -26,7 +26,7 @@ import withIndexationContext, { WithIndexationContextProps } from '../withIndexa

it('should render correctly', () => {
const indexationContext: IndexationContextInterface = {
status: { isCompleted: true, percentCompleted: 87 }
status: { isCompleted: true, percentCompleted: 87, hasFailures: false }
};

const wrapper = mountRender(indexationContext);
@@ -36,7 +36,11 @@ it('should render correctly', () => {

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

+ 8
- 2
server/sonar-web/src/main/js/components/hoc/__tests__/withIndexationGuard-test.tsx Прегледај датотеку

@@ -29,13 +29,19 @@ it('should render correctly', () => {
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>
);

+ 9
- 1
server/sonar-web/src/main/js/types/indexation.ts Прегледај датотеку

@@ -20,9 +20,17 @@

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'
}

+ 6
- 2
sonar-core/src/main/resources/org/sonar/l10n/core.properties Прегледај датотеку

@@ -3563,9 +3563,13 @@ maintenance.sonarqube_is_offline.text=The connection to SonarQube is lost. Pleas
#
#------------------------------------------------------------------------------
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

Loading…
Откажи
Сачувај