]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9732 Automatically update project page when analysis is finished
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>
Mon, 11 Jun 2018 09:04:44 +0000 (11:04 +0200)
committerSonarTech <sonartech@sonarsource.com>
Tue, 12 Jun 2018 18:20:57 +0000 (20:20 +0200)
server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx

index 11d254b4ada6eaaaf46a7dbd2dcd019136db612b..20bd441df410941c46834acce73d9293d0814fcc 100644 (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>
index eda3927faf18ff2787eac94b33f34675065cdf83..9052c611e682844acc4683cced398929ce0e98b3 100644 (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);
 });