Browse Source

SONAR-9732 Automatically update project page when analysis is finished

tags/7.5
Grégoire Aubert 6 years ago
parent
commit
0a34e84cd5

+ 79
- 31
server/sonar-web/src/main/js/app/components/ComponentContainer.tsx View File

@@ -20,6 +20,7 @@
import * as React from 'react';
import * as PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { differenceBy } from 'lodash';
import ComponentContainerNotFound from './ComponentContainerNotFound';
import ComponentNav from './nav/component/ComponentNav';
import { Component, BranchLike } from '../types';
@@ -47,14 +48,19 @@ interface Props {
}

interface State {
branchLike?: BranchLike;
branchLikes: BranchLike[];
component?: Component;
currentTask?: Task;
isPending: boolean;
loading: boolean;
pendingTasks?: PendingTask[];
tasksInProgress?: PendingTask[];
}

const FETCH_STATUS_WAIT_TIME = 3000;

export class ComponentContainer extends React.PureComponent<Props, State> {
watchStatusTimer?: number;
mounted = false;

static contextTypes = {
@@ -63,12 +69,12 @@ export class ComponentContainer extends React.PureComponent<Props, State> {

constructor(props: Props) {
super(props);
this.state = { branchLikes: [], loading: true };
this.state = { branchLikes: [], isPending: false, loading: true };
}

componentDidMount() {
this.mounted = true;
this.fetchComponent(this.props);
this.fetchComponent();
}

componentWillReceiveProps(nextProps: Props) {
@@ -90,7 +96,7 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
qualifier: component.breadcrumbs[component.breadcrumbs.length - 1].qualifier
});

fetchComponent(props: Props) {
fetchComponent(props = this.props) {
const { branch, id: key, pullRequest } = props.location.query;
this.setState({ loading: true });

@@ -114,49 +120,98 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
this.props.fetchOrganizations([component.organization]);
}

this.fetchBranches(component).then(branchLikes => {
this.fetchBranches(component).then(({ branchLike, branchLikes }) => {
if (this.mounted) {
this.setState({ loading: false, branchLikes, component });
this.setState({ branchLike, branchLikes, component, loading: false });
this.fetchStatus(component);
}
}, onError);

this.fetchStatus(component);
}, onError);
}

fetchBranches = (component: Component): Promise<BranchLike[]> => {
fetchBranches = (
component: Component
): Promise<{ branchLike?: BranchLike; branchLikes: BranchLike[] }> => {
const project = component.breadcrumbs.find(({ qualifier }) => qualifier === 'TRK');
return project
? Promise.all([getBranches(project.key), getPullRequests(project.key)]).then(
([branches, pullRequests]) => [...branches, ...pullRequests]
([branches, pullRequests]) => {
const branchLikes = [...branches, ...pullRequests];
return {
branchLike: this.getCurrentBranchLike(branchLikes),
branchLikes
};
}
)
: Promise.resolve([]);
: Promise.resolve({ branchLikes: [] });
};

fetchStatus = (component: Component) => {
getTasksForComponent(component.key).then(
({ current, queue }) => {
if (this.mounted) {
this.setState({ currentTask: current, pendingTasks: queue });
let shouldFetchComponent = false;
this.setState(
({ branchLike, component, currentTask, tasksInProgress }) => {
const newCurrentTask = this.getCurrentTask(current, branchLike);
const pendingTasks = this.getPendingTasks(queue, branchLike);
const newTasksInProgress = pendingTasks.filter(
task => task.status === STATUSES.IN_PROGRESS
);

const currentTaskChanged =
currentTask && newCurrentTask && currentTask.id !== newCurrentTask.id;
const progressChanged =
tasksInProgress &&
(newTasksInProgress.length !== tasksInProgress.length ||
differenceBy(newTasksInProgress, tasksInProgress, 'id').length > 0);

shouldFetchComponent = Boolean(currentTaskChanged || progressChanged);
if (!shouldFetchComponent && component && newTasksInProgress.length > 0) {
window.clearTimeout(this.watchStatusTimer);
this.watchStatusTimer = window.setTimeout(
() => this.fetchStatus(component),
FETCH_STATUS_WAIT_TIME
);
}

const isPending = pendingTasks.some(task => task.status === STATUSES.PENDING);
return {
currentTask: newCurrentTask,
isPending,
tasksInProgress: newTasksInProgress
};
},
() => {
if (shouldFetchComponent) {
this.fetchComponent();
}
}
);
}
},
() => {}
);
};

getCurrentTask = (branchLike?: BranchLike) => {
const { currentTask } = this.state;
if (!currentTask) {
getCurrentBranchLike = (branchLikes: BranchLike[]) => {
const { query } = this.props.location;
return query.pullRequest
? branchLikes.find(b => isPullRequest(b) && b.key === query.pullRequest)
: branchLikes.find(b => isBranch(b) && (query.branch ? b.name === query.branch : b.isMain));
};

getCurrentTask = (current: Task, branchLike?: BranchLike) => {
if (!current) {
return undefined;
}

return currentTask.status === STATUSES.FAILED || this.isSameBranch(currentTask, branchLike)
? currentTask
return current.status === STATUSES.FAILED || this.isSameBranch(current, branchLike)
? current
: undefined;
};

getPendingTasks = (branchLike?: BranchLike) => {
const { pendingTasks = [] } = this.state;
getPendingTasks = (pendingTasks: PendingTask[], branchLike?: BranchLike) => {
return pendingTasks.filter(task => this.isSameBranch(task, branchLike));
};

@@ -184,9 +239,9 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
handleBranchesChange = () => {
if (this.mounted && this.state.component) {
this.fetchBranches(this.state.component).then(
branchLikes => {
({ branchLike, branchLikes }) => {
if (this.mounted) {
this.setState({ branchLikes });
this.setState({ branchLike, branchLikes });
}
},
() => {}
@@ -195,21 +250,14 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
};

render() {
const { query } = this.props.location;
const { branchLikes, component, loading } = this.state;
const { component, loading } = this.state;

if (!loading && !component) {
return <ComponentContainerNotFound />;
}

const branchLike = query.pullRequest
? branchLikes.find(b => isPullRequest(b) && b.key === query.pullRequest)
: branchLikes.find(b => isBranch(b) && (query.branch ? b.name === query.branch : b.isMain));

const currentTask = this.getCurrentTask(branchLike);
const pendingTasks = this.getPendingTasks(branchLike);
const isInProgress = pendingTasks.some(task => task.status === STATUSES.IN_PROGRESS);
const isPending = pendingTasks.some(task => task.status === STATUSES.PENDING);
const { branchLike, branchLikes, currentTask, isPending, tasksInProgress } = this.state;
const isInProgress = tasksInProgress && tasksInProgress.length > 0;

return (
<div>

+ 32
- 9
server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx View File

@@ -32,6 +32,7 @@ import {
BranchType
} from '../../types';
import { STATUSES } from '../../../apps/background-tasks/constants';
import { waitAndUpdate } from '../../../helpers/testUtils';

jest.mock('../../../api/branches', () => ({
getBranches: jest.fn(() => Promise.resolve([])),
@@ -229,13 +230,35 @@ it('filters correctly the pending tasks for a main branch', () => {
{ branch: 'feature', branchType: 'SHORT' } as Task,
{} as Task
];
expect(component.getCurrentTask(undefined)).toBe(undefined);
component.setState({ currentTask: failedTask });
expect(component.getCurrentTask(mainBranch)).toBe(failedTask);
component.setState({ currentTask });
expect(component.getCurrentTask(mainBranch)).toBe(undefined);
expect(component.getCurrentTask(pullRequest)).toMatchObject(currentTask);
component.setState({ pendingTasks });
expect(component.getPendingTasks(mainBranch)).toMatchObject([{}]);
expect(component.getPendingTasks(pullRequest)).toMatchObject([currentTask]);
expect(component.getCurrentTask(currentTask, undefined)).toBe(undefined);
expect(component.getCurrentTask(failedTask, mainBranch)).toBe(failedTask);
expect(component.getCurrentTask(currentTask, mainBranch)).toBe(undefined);
expect(component.getCurrentTask(currentTask, pullRequest)).toMatchObject(currentTask);
expect(component.getPendingTasks(pendingTasks, mainBranch)).toMatchObject([{}]);
expect(component.getPendingTasks(pendingTasks, pullRequest)).toMatchObject([currentTask]);
});

it('reload component after task progress finished', async () => {
jest.useFakeTimers();
const inProgressTask = { id: 'foo', status: STATUSES.IN_PROGRESS } as Task;
(getTasksForComponent as jest.Mock<any>).mockResolvedValueOnce({ queue: [inProgressTask] });
const wrapper = shallow(
<ComponentContainer fetchOrganizations={jest.fn()} location={{ query: { id: 'foo' } }}>
<Inner />
</ComponentContainer>
);
await waitAndUpdate(wrapper);
expect(getComponentNavigation).toHaveBeenCalledTimes(1);
expect(getTasksForComponent).toHaveBeenCalledTimes(1);

jest.runAllTimers();
expect(getTasksForComponent).toHaveBeenCalledTimes(2);
await waitAndUpdate(wrapper);
expect(getComponentNavigation).toHaveBeenCalledTimes(2);
expect(getTasksForComponent).toHaveBeenCalledTimes(3);

jest.runAllTimers();
await waitAndUpdate(wrapper);
expect(getComponentNavigation).toHaveBeenCalledTimes(2);
expect(getTasksForComponent).toHaveBeenCalledTimes(3);
});

Loading…
Cancel
Save