]> source.dussan.org Git - sonarqube.git/commitdiff
Fix project history data loading bug when a graph is saved in localstorage
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>
Wed, 28 Jun 2017 08:48:55 +0000 (10:48 +0200)
committerGrégoire Aubert <gregoire.aubert@sonarsource.com>
Tue, 4 Jul 2017 12:15:34 +0000 (14:15 +0200)
server/sonar-web/src/main/js/apps/overview/components/OverviewApp.js
server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysesList.js
server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js
server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.js
server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityApp-test.js
server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityApp-test.js.snap
server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityGraphs-test.js.snap
server/sonar-web/src/main/js/apps/projectActivity/utils.js
server/sonar-web/src/main/js/components/icons-components/ProjectEventIcon.js
server/sonar-web/src/main/less/components/graphics.less

index b9071b92d7431008d037c1a9cf41ff43bc8da18e..2be253ef9f6997fcff4309e404ba098f932313f7 100644 (file)
  */
 // @flow
 import React from 'react';
+import { uniq } from 'lodash';
 import moment from 'moment';
 import QualityGate from '../qualityGate/QualityGate';
 import BugsAndVulnerabilities from '../main/BugsAndVulnerabilities';
 import CodeSmells from '../main/CodeSmells';
 import Coverage from '../main/Coverage';
 import Duplications from '../main/Duplications';
-import Meta from './../meta/Meta';
+import Meta from '../meta/Meta';
+import throwGlobalError from '../../../app/utils/throwGlobalError';
 import { getMeasuresAndMeta } from '../../../api/measures';
 import { getAllTimeMachineData } from '../../../api/time-machine';
 import { enhanceMeasuresWithMetrics } from '../../../helpers/measures';
@@ -95,11 +97,11 @@ export default class OverviewApp extends React.PureComponent {
           periods: r.periods
         });
       }
-    });
+    }, throwGlobalError);
   }
 
   loadHistory(component: Component) {
-    const metrics = HISTORY_METRICS_LIST.concat(GRAPHS_METRICS[getGraph()]);
+    const metrics = uniq(HISTORY_METRICS_LIST.concat(GRAPHS_METRICS[getGraph()]));
     return getAllTimeMachineData(component.key, metrics).then(r => {
       if (this.mounted) {
         const history: History = {};
@@ -113,7 +115,7 @@ export default class OverviewApp extends React.PureComponent {
         const historyStartDate = history[HISTORY_METRICS_LIST[0]][0].date;
         this.setState({ history, historyStartDate });
       }
-    });
+    }, throwGlobalError);
   }
 
   renderLoading() {
index d0655faec68089bbdf24fc035baa7e7664c20130..368ecf5abe9bfaf1b565306529ed5a965f30976e 100644 (file)
@@ -21,6 +21,7 @@
 import React from 'react';
 import classNames from 'classnames';
 import moment from 'moment';
+import { throttle } from 'lodash';
 import ProjectActivityAnalysis from './ProjectActivityAnalysis';
 import FormattedDate from '../../../components/ui/FormattedDate';
 import { translate } from '../../../helpers/l10n';
@@ -45,6 +46,11 @@ export default class ProjectActivityAnalysesList extends React.PureComponent {
   badges: HTMLCollection<HTMLElement>;
   props: Props;
 
+  constructor(props: Props) {
+    super(props);
+    this.handleScroll = throttle(this.handleScroll, 20);
+  }
+
   componentDidMount() {
     this.badges = document.getElementsByClassName('project-activity-version-badge');
   }
@@ -107,7 +113,7 @@ export default class ProjectActivityAnalysesList extends React.PureComponent {
         onScroll={this.handleScroll}
         ref={element => (this.scrollContainer = element)}>
         {byVersionByDay.map((version, idx) => (
-          <li key={idx + version.version}>
+          <li key={version.key || 'noversion'}>
             {version.version &&
               <div className={classNames('project-activity-version-badge', { first: idx === 0 })}>
                 <span className="badge">
index 1904f8d975d6a93b88de831eef805be1d2d66275..9b4f2a595dd41a8287d0c8767ccbe08d14f6762e 100644 (file)
@@ -37,6 +37,7 @@ type Props = {
   changeEvent: (event: string, name: string) => Promise<*>,
   deleteAnalysis: (analysis: string) => Promise<*>,
   deleteEvent: (analysis: string, event: string) => Promise<*>,
+  graphLoading: boolean,
   loading: boolean,
   project: { configuration?: { showHistory: boolean }, key: string, leakPeriodDate: string },
   metrics: Array<Metric>,
@@ -87,7 +88,7 @@ export default class ProjectActivityApp extends React.PureComponent {
   };
 
   render() {
-    const { loading, measuresHistory, query } = this.props;
+    const { measuresHistory, query } = this.props;
     const { filteredAnalyses } = this.state;
     const { configuration } = this.props.project;
     const canAdmin = configuration ? configuration.showHistory : false;
@@ -109,14 +110,14 @@ export default class ProjectActivityApp extends React.PureComponent {
               changeEvent={this.props.changeEvent}
               deleteAnalysis={this.props.deleteAnalysis}
               deleteEvent={this.props.deleteEvent}
-              loading={loading}
+              loading={this.props.loading}
             />
           </div>
           <div className="project-activity-layout-page-main">
             <ProjectActivityGraphs
               analyses={filteredAnalyses}
               leakPeriodDate={moment(this.props.project.leakPeriodDate).toDate()}
-              loading={loading}
+              loading={this.props.graphLoading}
               measuresHistory={measuresHistory}
               metricsType={this.getMetricType()}
               project={this.props.project.key}
index e7a4e2505ef7e7ab249878d06f40155394d778be..774f1165eae94ad47397464f333b1877f4e8ed94 100644 (file)
@@ -86,9 +86,9 @@ class ProjectActivityAppContainer extends React.PureComponent {
     elem && elem.classList.add('dashboard-page');
   }
 
-  componentDidUpdate(prevProps: Props) {
-    if (prevProps.location.query !== this.props.location.query) {
-      const query = parseQuery(this.props.location.query);
+  componentWillReceiveProps(nextProps: Props) {
+    if (nextProps.location.query !== this.props.location.query) {
+      const query = parseQuery(nextProps.location.query);
       if (query.graph !== this.state.query.graph) {
         this.updateGraphData(query.graph);
       }
@@ -157,8 +157,8 @@ class ProjectActivityAppContainer extends React.PureComponent {
     );
   };
 
-  fetchMeasuresHistory = (metrics: Array<string>): Promise<Array<MeasureHistory>> =>
-    getAllTimeMachineData(this.props.project.key, metrics).then(
+  fetchMeasuresHistory = (metrics: Array<string>): Promise<Array<MeasureHistory>> => {
+    return getAllTimeMachineData(this.props.project.key, metrics).then(
       ({ measures }) =>
         measures.map(measure => ({
           metric: measure.metric,
@@ -169,6 +169,7 @@ class ProjectActivityAppContainer extends React.PureComponent {
         })),
       throwGlobalError
     );
+  };
 
   fetchMetrics = (): Promise<Array<Metric>> => getMetrics().catch(throwGlobalError);
 
@@ -197,31 +198,41 @@ class ProjectActivityAppContainer extends React.PureComponent {
   firstLoadData() {
     const { query } = this.state;
     const graphMetrics = GRAPHS_METRICS[query.graph];
+    const ignoreHistory = this.shouldRedirect();
     Promise.all([
       this.fetchActivity(query.project, 1, 100, serializeQuery(query)),
       this.fetchMetrics(),
-      this.fetchMeasuresHistory(graphMetrics)
+      ignoreHistory ? Promise.resolve() : this.fetchMeasuresHistory(graphMetrics)
     ]).then(response => {
       if (this.mounted) {
-        this.setState({
-          analyses: response[0].analyses,
-          analysesLoading: true,
-          graphLoading: false,
-          loading: false,
-          metrics: response[1],
-          measuresHistory: response[2],
-          paging: response[0].paging
-        });
-
-        this.loadAllActivities(query.project).then(({ analyses, paging }) => {
-          if (this.mounted) {
+        setTimeout(() => {
+          const newState = {
+            analyses: response[0].analyses,
+            analysesLoading: true,
+            loading: false,
+            metrics: response[1],
+            paging: response[0].paging
+          };
+          if (ignoreHistory) {
+            this.setState(newState);
+          } else {
             this.setState({
-              analyses,
-              analysesLoading: false,
-              paging
+              ...newState,
+              graphLoading: false,
+              measuresHistory: response[2]
             });
           }
-        });
+
+          this.loadAllActivities(query.project).then(({ analyses, paging }) => {
+            if (this.mounted) {
+              this.setState({
+                analyses,
+                analysesLoading: false,
+                paging
+              });
+            }
+          });
+        }, 1000);
       }
     });
   }
@@ -273,7 +284,7 @@ class ProjectActivityAppContainer extends React.PureComponent {
         changeEvent={this.changeEvent}
         deleteAnalysis={this.deleteAnalysis}
         deleteEvent={this.deleteEvent}
-        graphLoading={this.state.graphLoading}
+        graphLoading={this.state.loading || this.state.graphLoading}
         loading={this.state.loading}
         metrics={this.state.metrics}
         measuresHistory={this.state.measuresHistory}
index e6122763349ab39bd49235a14b82a258319e6c79..22f85b2a28f8b3c32d5559773aadadb3db54bd8b 100644 (file)
@@ -60,9 +60,11 @@ const DEFAULT_PROPS = {
   addCustomEvent: () => {},
   addVersion: () => {},
   analyses: ANALYSES,
+  analysesLoading: false,
   changeEvent: () => {},
   deleteAnalysis: () => {},
   deleteEvent: () => {},
+  graphLoading: false,
   loading: false,
   project: {
     key: 'org.sonarsource.sonarqube:sonarqube',
index c34ab47e81c3e90c69e8d0f83e72c5b1fcf7dc8c..0f5a433fbfed7ed9ecb448558289316aa136c695 100644 (file)
@@ -103,6 +103,7 @@ exports[`should render correctly 1`] = `
             },
           ]
         }
+        analysesLoading={false}
         canAdmin={false}
         changeEvent={[Function]}
         className="boxed-group-inner"
index 67899574868e231fe2b93d7509e7611b857d3d46..920f463b1eaa424516e43a5506bd2c0a82b4d24d 100644 (file)
@@ -46,8 +46,8 @@ exports[`should render correctly the graph and legends 1`] = `
       ]
     }
     eventFilter=""
-    graphEndDate={null}
-    graphStartDate={null}
+    graphEndDate={2016-10-27T14:33:50.000Z}
+    graphStartDate={2016-10-26T10:17:29.000Z}
     leakPeriodDate="2017-05-16T13:50:02+0200"
     loading={false}
     metricsType="INT"
@@ -79,8 +79,8 @@ exports[`should render correctly the graph and legends 1`] = `
     updateGraphZoom={[Function]}
   />
   <GraphsZoom
-    graphEndDate={null}
-    graphStartDate={null}
+    graphEndDate={2016-10-27T14:33:50.000Z}
+    graphStartDate={2016-10-26T10:17:29.000Z}
     leakPeriodDate="2017-05-16T13:50:02+0200"
     loading={false}
     metricsType="INT"
@@ -115,8 +115,6 @@ exports[`should render correctly the graph and legends 1`] = `
 
 exports[`should render correctly with filter history on dates 1`] = `
 Object {
-  "graphEndDate": null,
-  "graphStartDate": "2016-10-27T12:21:15+0200",
   "series": Array [
     Object {
       "data": Array [
index 2c0c3c818bcdd90fea06e33604e6a54e723e993a..7f5389028d1873b71e6aa8304bb805e871633240 100644 (file)
@@ -99,11 +99,12 @@ export const getAnalysesByVersionByDay = (
   analyses: Array<Analysis>
 ): Array<{
   version: ?string,
+  key: ?string,
   byDay: { [string]: Array<Analysis> }
 }> =>
   analyses.reduce((acc, analysis) => {
     if (acc.length === 0) {
-      acc.push({ version: undefined, byDay: {} });
+      acc.push({ version: undefined, key: undefined, byDay: {} });
     }
     const currentVersion = acc[acc.length - 1];
     const day = moment(analysis.date).startOf('day').valueOf().toString();
@@ -122,7 +123,8 @@ export const getAnalysesByVersionByDay = (
     const lastEvent = sortedEvents[sortedEvents.length - 1];
     if (lastEvent && lastEvent.category === 'VERSION') {
       currentVersion.version = lastEvent.name;
-      acc.push({ version: undefined, byDay: {} });
+      currentVersion.key = lastEvent.key;
+      acc.push({ version: undefined, key: undefined, byDay: {} });
     }
     return acc;
   }, []);
index 985d68baae30f91865c247f1230f306c0d6455bd..747cc2bfbced00b6fe7bb37a9d0452fd63036c0c 100644 (file)
@@ -32,7 +32,7 @@ export default function ProjectEventIcon({ className, size = 14 }: Props) {
       width={size}
       height={size}>
       <path
-        style={{ fill: '#fff', stroke: 'currentColor', strokeWidth: '3px' }}
+        style={{ fill: '#fff', stroke: 'currentColor', strokeWidth: '2px' }}
         d="M8 2 L14 8 L8 14 L2 8 L8 2 L14 8"
       />
     </svg>
index 916fea38a744ffda9038b5d43a6570712715492e..07e7b5857e33af75fe302baf1dd71d896d6a3399 100644 (file)
   .line-chart-path {
     clip-path: url(#chart-clip);
   }
+
+  .leak-chart-rect {
+    clip-path: url(#chart-clip);
+  }
 }
 
 .chart-zoom-tick {