From: Grégoire Aubert Date: Mon, 11 Jun 2018 09:04:44 +0000 (+0200) Subject: SONAR-9732 Automatically update project page when analysis is finished X-Git-Tag: 7.5~1048 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=0a34e84cd5ccdbaff1f0b96262ccbe3345adf26f;p=sonarqube.git SONAR-9732 Automatically update project page when analysis is finished --- diff --git a/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx b/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx index 11d254b4ada..20bd441df41 100644 --- a/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx +++ b/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx @@ -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 { + watchStatusTimer?: number; mounted = false; static contextTypes = { @@ -63,12 +69,12 @@ export class ComponentContainer extends React.PureComponent { 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 { 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 { 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 => { + 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 { 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 { }; render() { - const { query } = this.props.location; - const { branchLikes, component, loading } = this.state; + const { component, loading } = this.state; if (!loading && !component) { return ; } - 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 (
diff --git a/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx b/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx index eda3927faf1..9052c611e68 100644 --- a/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx +++ b/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx @@ -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).mockResolvedValueOnce({ queue: [inProgressTask] }); + const wrapper = shallow( + + + + ); + 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); });