]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-11479 Display the measures of the currently selected directory on the Measures...
authorStas Vilchik <stas.vilchik@sonarsource.com>
Mon, 3 Dec 2018 12:50:17 +0000 (13:50 +0100)
committersonartech <sonartech@sonarsource.com>
Wed, 16 Jan 2019 08:43:04 +0000 (09:43 +0100)
28 files changed:
server/sonar-web/src/main/js/api/components.ts
server/sonar-web/src/main/js/api/measures.ts
server/sonar-web/src/main/js/apps/code/utils.ts
server/sonar-web/src/main/js/apps/component-measures/components/App.tsx
server/sonar-web/src/main/js/apps/component-measures/components/AppContainer.tsx [deleted file]
server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumbs.tsx
server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.tsx
server/sonar-web/src/main/js/apps/component-measures/components/MeasureContentContainer.tsx [deleted file]
server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.tsx
server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverview.tsx
server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverviewContainer.tsx
server/sonar-web/src/main/js/apps/component-measures/components/MetricNotFound.tsx [deleted file]
server/sonar-web/src/main/js/apps/component-measures/components/__tests__/App-test.tsx
server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureHeader-test.tsx
server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/App-test.tsx.snap
server/sonar-web/src/main/js/apps/component-measures/drilldown/CodeView.tsx
server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentCell.tsx
server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsList.tsx
server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsListRow.tsx
server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.tsx
server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/ComponentList-test.tsx
server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/FilesView-test.tsx
server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/__snapshots__/ComponentList-test.tsx.snap
server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/__snapshots__/FilesView-test.tsx.snap
server/sonar-web/src/main/js/apps/component-measures/routes.ts
server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.tsx
server/sonar-web/src/main/js/apps/component-measures/utils.ts
server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx

index 60317a41e3937b2995b3647e46b142f30520048e..7b97f66111c4457f3b1bd398eca3e56fc9c10ce2 100644 (file)
@@ -94,6 +94,7 @@ export function getComponentTree(
   metrics: string[] = [],
   additional: RequestData = {}
 ): Promise<{
+  baseComponent: T.ComponentMeasure;
   components: T.ComponentMeasure[];
   metrics: T.Metric[];
   paging: T.Paging;
index 324ca966a543bb9e822996e00f8a2f10a13ff13f..5ebda3a570bde6128ac6397312776d103cf3e044 100644 (file)
@@ -31,8 +31,11 @@ export function getMeasuresAndMeta(
   metrics: string[],
   additional: RequestData = {}
 ): Promise<{ component: T.ComponentMeasure; metrics?: T.Metric[]; periods?: T.Period[] }> {
-  const data = { ...additional, component, metricKeys: metrics.join(',') };
-  return getJSON('/api/measures/component', data);
+  return getJSON('/api/measures/component', {
+    ...additional,
+    component,
+    metricKeys: metrics.join(',')
+  }).catch(throwGlobalError);
 }
 
 interface MeasuresForProjects {
index baee517fccb27ba019c90778810fe000e7d7de2f..86f015f0e514c4af151f028dfabddc7ecd842918 100644 (file)
@@ -17,7 +17,6 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { without } from 'lodash';
 import {
   addComponent,
   getComponent as getComponentFromBucket,
@@ -59,61 +58,12 @@ const LEAK_METRICS = [
 
 const PAGE_SIZE = 100;
 
-function requestChildren(
-  componentKey: string,
-  metrics: string[],
-  page: number,
-  branchLike?: T.BranchLike
-): Promise<T.ComponentMeasure[]> {
-  return getChildren(componentKey, metrics, {
-    p: page,
-    ps: PAGE_SIZE,
-    ...getBranchLikeQuery(branchLike)
-  }).then(r => {
-    if (r.paging.total > r.paging.pageSize * r.paging.pageIndex) {
-      return requestChildren(componentKey, metrics, page + 1, branchLike).then(moreComponents => {
-        return [...r.components, ...moreComponents];
-      });
-    }
-    return r.components;
-  });
-}
-
-function requestAllChildren(
-  componentKey: string,
-  metrics: string[],
-  branchLike?: T.BranchLike
-): Promise<T.ComponentMeasure[]> {
-  return requestChildren(componentKey, metrics, 1, branchLike);
-}
-
 interface Children {
   components: T.ComponentMeasure[];
   page: number;
   total: number;
 }
 
-interface ExpandRootDirFunc {
-  (children: Children): Promise<Children>;
-}
-
-function expandRootDir(metrics: string[], branchLike?: T.BranchLike): ExpandRootDirFunc {
-  return function({ components, total, ...other }) {
-    const rootDir = components.find(
-      (component: T.ComponentMeasure) => component.qualifier === 'DIR' && component.name === '/'
-    );
-    if (rootDir) {
-      return requestAllChildren(rootDir.key, metrics, branchLike).then(rootDirComponents => {
-        const nextComponents = without([...rootDirComponents, ...components], rootDir);
-        const nextTotal = total + rootDirComponents.length - /* root dir */ 1;
-        return { components: nextComponents, total: nextTotal, ...other };
-      });
-    } else {
-      return Promise.resolve({ components, total, ...other });
-    }
-  };
-}
-
 function prepareChildren(r: any): Children {
   return {
     components: r.components,
@@ -202,7 +152,6 @@ export function retrieveComponentChildren(
     ...getBranchLikeQuery(branchLike)
   })
     .then(prepareChildren)
-    .then(expandRootDir(metrics, branchLike))
     .then(r => {
       addComponentChildren(componentKey, r.components, r.total, r.page);
       storeChildrenBase(r.components);
@@ -268,7 +217,6 @@ export function loadMoreChildren(
     ...getBranchLikeQuery(branchLike)
   })
     .then(prepareChildren)
-    .then(expandRootDir(metrics, branchLike))
     .then(r => {
       addComponentChildren(componentKey, r.components, r.total, r.page);
       storeChildrenBase(r.components);
index 51de9019d2fb80060d9bd89951024209d4351031..551d4d9f80d6c4d7fbead001970e74e24d3c0a3c 100644 (file)
  */
 import * as React from 'react';
 import * as key from 'keymaster';
-import { InjectedRouter } from 'react-router';
+import { withRouter, WithRouterProps } from 'react-router';
 import Helmet from 'react-helmet';
-import MeasureContentContainer from './MeasureContentContainer';
+import { keyBy } from 'lodash';
+import MeasureContent from './MeasureContent';
 import MeasuresEmpty from './MeasuresEmpty';
 import MeasureOverviewContainer from './MeasureOverviewContainer';
 import Sidebar from '../sidebar/Sidebar';
@@ -35,7 +36,9 @@ import {
   hasFullMeasures,
   getMeasuresPageMetricKeys,
   groupByDomains,
-  sortMeasures
+  sortMeasures,
+  hasTreemap,
+  hasTree
 } from '../utils';
 import {
   isSameBranchLike,
@@ -55,62 +58,59 @@ import {
   removeSideBarClass,
   removeWhitePageClass
 } from '../../../helpers/pages';
-import { RawQuery } from '../../../helpers/query';
 import '../../../components/search-navigator.css';
 import '../style.css';
+import { getAllMetrics } from '../../../api/metrics';
+import { getMeasuresAndMeta } from '../../../api/measures';
+import { enhanceMeasure } from '../../../components/measure/utils';
+import { getLeakPeriod } from '../../../helpers/periods';
 
-interface Props {
+interface Props extends WithRouterProps {
   branchLike?: T.BranchLike;
   component: T.ComponentMeasure;
-  location: { pathname: string; query: RawQuery };
-  fetchMeasures: (
-    component: string,
-    metricsKey: string[],
-    branchLike?: T.BranchLike
-  ) => Promise<{
-    component: T.ComponentMeasure;
-    measures: T.MeasureEnhanced[];
-    leakPeriod?: T.Period;
-  }>;
-  fetchMetrics: () => void;
-  metrics: { [metric: string]: T.Metric };
-  metricsKey: string[];
-  router: InjectedRouter;
 }
 
 interface State {
+  leakPeriod?: T.Period;
   loading: boolean;
   measures: T.MeasureEnhanced[];
-  leakPeriod?: T.Period;
+  metrics: { [metric: string]: T.Metric };
 }
 
-export default class App extends React.PureComponent<Props, State> {
+export class App extends React.PureComponent<Props, State> {
   mounted = false;
-
-  constructor(props: Props) {
-    super(props);
-    this.state = { loading: true, measures: [] };
-  }
+  state: State = {
+    loading: true,
+    measures: [],
+    metrics: {}
+  };
 
   componentDidMount() {
     this.mounted = true;
 
     key.setScope('measures-files');
-    this.props.fetchMetrics();
-    this.fetchMeasures(this.props);
+    getAllMetrics().then(
+      metrics => {
+        const byKey = keyBy(metrics, 'key');
+        this.setState({ metrics: byKey });
+        this.fetchMeasures(byKey);
+      },
+      () => {}
+    );
   }
 
-  componentWillReceiveProps(nextProps: Props) {
+  componentDidUpdate(prevProps: Props, prevState: State) {
+    const prevQuery = parseQuery(prevProps.location.query);
+    const query = parseQuery(this.props.location.query);
+
     if (
-      !isSameBranchLike(nextProps.branchLike, this.props.branchLike) ||
-      nextProps.component.key !== this.props.component.key ||
-      nextProps.metrics !== this.props.metrics
+      !isSameBranchLike(prevProps.branchLike, this.props.branchLike) ||
+      prevProps.component.key !== this.props.component.key ||
+      prevQuery.selected !== query.selected
     ) {
-      this.fetchMeasures(nextProps);
+      this.fetchMeasures(this.state.metrics);
     }
-  }
 
-  componentDidUpdate(_prevProps: Props, prevState: State) {
     if (prevState.measures.length === 0 && this.state.measures.length > 0) {
       addWhitePageClass();
       addSideBarClass();
@@ -124,13 +124,40 @@ export default class App extends React.PureComponent<Props, State> {
     key.deleteScope('measures-files');
   }
 
-  fetchMeasures = ({ branchLike, component, fetchMeasures, metrics }: Props) => {
-    this.setState({ loading: true });
+  fetchMeasures(metrics: State['metrics']) {
+    const { branchLike } = this.props;
+    const query = parseQuery(this.props.location.query);
+    const componentKey = query.selected || this.props.component.key;
 
     const filteredKeys = getMeasuresPageMetricKeys(metrics, branchLike);
-    fetchMeasures(component.key, filteredKeys, branchLike).then(
-      ({ measures, leakPeriod }) => {
+
+    const banQualityGate = ({ measures = [], qualifier }: T.ComponentMeasure) => {
+      const bannedMetrics: string[] = [];
+      if (!['VW', 'SVW'].includes(qualifier)) {
+        bannedMetrics.push('alert_status');
+      }
+      if (qualifier === 'APP') {
+        bannedMetrics.push('releasability_rating', 'releasability_effort');
+      }
+      return measures.filter(measure => !bannedMetrics.includes(measure.metric));
+    };
+
+    getMeasuresAndMeta(componentKey, filteredKeys, {
+      additionalFields: 'periods',
+      ...getBranchLikeQuery(branchLike)
+    }).then(
+      ({ component, periods }) => {
         if (this.mounted) {
+          const measures = banQualityGate(component).map(measure =>
+            enhanceMeasure(measure, metrics)
+          );
+
+          const newBugs = measures.find(measure => measure.metric.key === 'new_bugs');
+          const applicationPeriods = newBugs ? [{ index: 1 } as T.Period] : [];
+          const leakPeriod = getLeakPeriod(
+            component.qualifier === 'APP' ? applicationPeriods : periods
+          );
+
           this.setState({
             loading: false,
             leakPeriod,
@@ -146,7 +173,7 @@ export default class App extends React.PureComponent<Props, State> {
         }
       }
     );
-  };
+  }
 
   getHelmetTitle = (query: Query, displayOverview: boolean, metric?: T.Metric) => {
     if (displayOverview && query.metric) {
@@ -164,7 +191,7 @@ export default class App extends React.PureComponent<Props, State> {
     if (displayOverview) {
       return undefined;
     }
-    const metric = this.props.metrics[query.metric];
+    const metric = this.state.metrics[query.metric];
     if (!metric) {
       const domainMeasures = groupByDomains(this.state.measures);
       const firstMeasure =
@@ -177,14 +204,21 @@ export default class App extends React.PureComponent<Props, State> {
   };
 
   updateQuery = (newQuery: Partial<Query>) => {
-    const query = serializeQuery({
-      ...parseQuery(this.props.location.query),
-      ...newQuery
-    });
+    const query: Query = { ...parseQuery(this.props.location.query), ...newQuery };
+
+    const metric = this.getSelectedMetric(query, false);
+    if (metric) {
+      if (query.view === 'treemap' && !hasTreemap(metric.key, metric.type)) {
+        query.view = 'tree';
+      } else if (query.view === 'tree' && !hasTree(metric.key)) {
+        query.view = 'list';
+      }
+    }
+
     this.props.router.push({
       pathname: this.props.location.pathname,
       query: {
-        ...query,
+        ...serializeQuery(query),
         ...getBranchLikeQuery(this.props.branchLike),
         id: this.props.component.key
       }
@@ -192,7 +226,7 @@ export default class App extends React.PureComponent<Props, State> {
   };
 
   renderContent = (displayOverview: boolean, query: Query, metric?: T.Metric) => {
-    const { branchLike, component, fetchMeasures, metrics } = this.props;
+    const { branchLike, component } = this.props;
     const { leakPeriod } = this.state;
     if (displayOverview) {
       return (
@@ -201,7 +235,7 @@ export default class App extends React.PureComponent<Props, State> {
           className="layout-page-main"
           domain={query.metric}
           leakPeriod={leakPeriod}
-          metrics={metrics}
+          metrics={this.state.metrics}
           rootComponent={component}
           router={this.props.router}
           selected={query.selected}
@@ -229,13 +263,11 @@ export default class App extends React.PureComponent<Props, State> {
     }
 
     return (
-      <MeasureContentContainer
+      <MeasureContent
         branchLike={branchLike}
-        className="layout-page-main"
-        fetchMeasures={fetchMeasures}
         leakPeriod={leakPeriod}
-        metric={metric}
-        metrics={metrics}
+        metrics={this.state.metrics}
+        requestedMetric={metric}
         rootComponent={component}
         router={this.props.router}
         selected={query.selected}
@@ -246,16 +278,17 @@ export default class App extends React.PureComponent<Props, State> {
   };
 
   render() {
-    const isLoading = this.state.loading || this.props.metricsKey.length <= 0;
-    if (isLoading) {
+    if (this.state.loading) {
       return <i className="spinner spinner-margin" />;
     }
+
     const { branchLike } = this.props;
     const { measures } = this.state;
     const query = parseQuery(this.props.location.query);
     const hasOverview = hasFullMeasures(branchLike);
     const displayOverview = hasOverview && hasBubbleChart(query.metric);
     const metric = this.getSelectedMetric(query, displayOverview);
+
     return (
       <div id="component-measures">
         <Suggestions suggestions="component_measures" />
@@ -288,3 +321,5 @@ export default class App extends React.PureComponent<Props, State> {
     );
   }
 }
+
+export default withRouter(App);
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/AppContainer.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/AppContainer.tsx
deleted file mode 100644 (file)
index e7ea87f..0000000
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-import { Dispatch } from 'redux';
-import { connect } from 'react-redux';
-import { withRouter, WithRouterProps } from 'react-router';
-import App from './App';
-import throwGlobalError from '../../../app/utils/throwGlobalError';
-import { getMetrics, getMetricsKey } from '../../../store/rootReducer';
-import { fetchMetrics } from '../../../store/rootActions';
-import { getMeasuresAndMeta } from '../../../api/measures';
-import { getLeakPeriod } from '../../../helpers/periods';
-import { enhanceMeasure } from '../../../components/measure/utils';
-import { getBranchLikeQuery } from '../../../helpers/branches';
-
-interface StateToProps {
-  metrics: { [metric: string]: T.Metric };
-  metricsKey: string[];
-}
-
-interface DispatchToProps {
-  fetchMeasures: (
-    component: string,
-    metricsKey: string[],
-    branchLike?: T.BranchLike
-  ) => Promise<{
-    component: T.ComponentMeasure;
-    measures: T.MeasureEnhanced[];
-    leakPeriod?: T.Period;
-  }>;
-  fetchMetrics: () => void;
-}
-
-interface OwnProps {
-  branchLike?: T.BranchLike;
-  component: T.ComponentMeasure;
-}
-
-const mapStateToProps = (state: any): StateToProps => ({
-  metrics: getMetrics(state),
-  metricsKey: getMetricsKey(state)
-});
-
-function banQualityGate({ measures = [], qualifier }: T.ComponentMeasure): T.Measure[] {
-  const bannedMetrics: string[] = [];
-  if (!['VW', 'SVW'].includes(qualifier)) {
-    bannedMetrics.push('alert_status');
-  }
-  if (qualifier === 'APP') {
-    bannedMetrics.push('releasability_rating', 'releasability_effort');
-  }
-  return measures.filter(measure => !bannedMetrics.includes(measure.metric));
-}
-
-const fetchMeasures = (component: string, metricsKey: string[], branchLike?: T.BranchLike) => (
-  _dispatch: Dispatch,
-  getState: () => any
-) => {
-  if (metricsKey.length <= 0) {
-    return Promise.resolve({ component: {}, measures: [], leakPeriod: null });
-  }
-
-  return getMeasuresAndMeta(component, metricsKey, {
-    additionalFields: 'periods',
-    ...getBranchLikeQuery(branchLike)
-  }).then(({ component, periods }) => {
-    const measures = banQualityGate(component).map(measure =>
-      enhanceMeasure(measure, getMetrics(getState()))
-    );
-
-    const newBugs = measures.find(measure => measure.metric.key === 'new_bugs');
-    const applicationPeriods = newBugs ? [{ index: 1 } as T.Period] : [];
-    const leakPeriod = getLeakPeriod(component.qualifier === 'APP' ? applicationPeriods : periods);
-    return { component, measures, leakPeriod };
-  }, throwGlobalError);
-};
-
-const mapDispatchToProps: DispatchToProps = { fetchMeasures: fetchMeasures as any, fetchMetrics };
-
-export default withRouter<OwnProps>(
-  connect<StateToProps, DispatchToProps, OwnProps & WithRouterProps>(
-    mapStateToProps,
-    mapDispatchToProps
-  )(App)
-);
index d771d65dc59337e2898ef4d838922ba0dbdd7aa7..e7202a2a15c850b594e2db62654b9b5923e2e63d 100644 (file)
@@ -21,7 +21,7 @@ import * as React from 'react';
 import * as key from 'keymaster';
 import Breadcrumb from './Breadcrumb';
 import { getBreadcrumbs } from '../../../api/components';
-import { getBranchLikeQuery } from '../../../helpers/branches';
+import { getBranchLikeQuery, isSameBranchLike } from '../../../helpers/branches';
 
 interface Props {
   backToFirst: boolean;
@@ -42,13 +42,16 @@ export default class Breadcrumbs extends React.PureComponent<Props, State> {
 
   componentDidMount() {
     this.mounted = true;
-    this.fetchBreadcrumbs(this.props);
+    this.fetchBreadcrumbs();
     this.attachShortcuts();
   }
 
-  componentWillReceiveProps(nextProps: Props) {
-    if (this.props.component !== nextProps.component) {
-      this.fetchBreadcrumbs(nextProps);
+  componentDidUpdate(prevProps: Props) {
+    if (
+      this.props.component !== prevProps.component ||
+      !isSameBranchLike(prevProps.branchLike, this.props.branchLike)
+    ) {
+      this.fetchBreadcrumbs();
     }
   }
 
@@ -72,7 +75,8 @@ export default class Breadcrumbs extends React.PureComponent<Props, State> {
     key.unbind('left', 'measures-files');
   }
 
-  fetchBreadcrumbs = ({ branchLike, component, rootComponent }: Props) => {
+  fetchBreadcrumbs = () => {
+    const { branchLike, component, rootComponent } = this.props;
     const isRoot = component.key === rootComponent.key;
     if (isRoot) {
       if (this.mounted) {
index 86982a68e0a8cbff44498d7740acde78ad838b50..4b545348cddd6524ebc119479822b19633b67fe4 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
-import * as classNames from 'classnames';
 import { InjectedRouter } from 'react-router';
 import Breadcrumbs from './Breadcrumbs';
 import MeasureContentHeader from './MeasureContentHeader';
 import MeasureHeader from './MeasureHeader';
 import MeasureViewSelect from './MeasureViewSelect';
-import MetricNotFound from './MetricNotFound';
 import PageActions from './PageActions';
-import FilesView from '../drilldown/FilesView';
+import { complementary } from '../config/complementary';
 import CodeView from '../drilldown/CodeView';
+import FilesView from '../drilldown/FilesView';
 import TreeMapView from '../drilldown/TreeMapView';
+import { Query, View, isFileType, enhanceComponent, isViewType } from '../utils';
 import { getComponentTree } from '../../../api/components';
-import { complementary } from '../config/complementary';
-import { enhanceComponent, isFileType, isViewType, View } from '../utils';
-import { getProjectUrl } from '../../../helpers/urls';
-import { isDiffMetric } from '../../../helpers/measures';
 import { isSameBranchLike, getBranchLikeQuery } from '../../../helpers/branches';
-import DeferredSpinner from '../../../components/common/DeferredSpinner';
+import { isDiffMetric, getPeriodValue } from '../../../helpers/measures';
 import { RequestData } from '../../../helpers/request';
+import { getProjectUrl } from '../../../helpers/urls';
+import { getMeasures } from '../../../api/measures';
 
 interface Props {
   branchLike?: T.BranchLike;
-  className?: string;
-  component: T.ComponentMeasure;
-  loading: boolean;
-  loadingMore: boolean;
   leakPeriod?: T.Period;
-  measure?: T.MeasureEnhanced;
-  metric: T.Metric;
+  requestedMetric: Pick<T.Metric, 'key' | 'direction'>;
   metrics: { [metric: string]: T.Metric };
   rootComponent: T.ComponentMeasure;
   router: InjectedRouter;
-  secondaryMeasure?: T.MeasureEnhanced;
-  updateLoading: (param: { [key: string]: boolean }) => void;
-  updateSelected: (component: string) => void;
-  updateView: (view: View) => void;
+  selected?: string;
+  updateQuery: (query: Partial<Query>) => void;
   view: View;
 }
 
 interface State {
+  baseComponent?: T.ComponentMeasure;
   components: T.ComponentMeasureEnhanced[];
+  loading: boolean;
+  loadingMoreComponents: boolean;
+  measure?: T.Measure;
   metric?: T.Metric;
   paging?: T.Paging;
+  secondaryMeasure?: T.Measure;
   selected?: string;
 }
 
 export default class MeasureContent extends React.PureComponent<Props, State> {
   container?: HTMLElement | null;
   mounted = false;
-  state: State = { components: [] };
+  state: State = {
+    components: [],
+    loading: true,
+    loadingMoreComponents: false
+  };
 
   componentDidMount() {
     this.mounted = true;
-    this.fetchComponents(this.props);
+    this.fetchComponentTree();
   }
 
-  componentWillReceiveProps(nextProps: Props) {
+  componentDidUpdate(prevProps: Props) {
+    const prevComponentKey = prevProps.selected || prevProps.rootComponent.key;
+    const componentKey = this.props.selected || this.props.rootComponent.key;
     if (
-      !isSameBranchLike(nextProps.branchLike, this.props.branchLike) ||
-      nextProps.component !== this.props.component ||
-      nextProps.metric !== this.props.metric
+      prevComponentKey !== componentKey ||
+      !isSameBranchLike(prevProps.branchLike, this.props.branchLike) ||
+      prevProps.requestedMetric !== this.props.requestedMetric ||
+      prevProps.view !== this.props.view
     ) {
-      this.fetchComponents(nextProps);
+      this.fetchComponentTree();
     }
   }
 
@@ -88,15 +91,95 @@ export default class MeasureContent extends React.PureComponent<Props, State> {
     this.mounted = false;
   }
 
-  getSelectedIndex = () => {
-    const componentKey = isFileType(this.props.component)
-      ? this.props.component.key
-      : this.state.selected;
-    const index = this.state.components.findIndex(component => component.key === componentKey);
-    return index !== -1 ? index : undefined;
+  fetchComponentTree = () => {
+    this.setState({ loading: true });
+    const { metricKeys, opts, strategy } = this.getComponentRequestParams(
+      this.props.view,
+      this.props.requestedMetric
+    );
+    const componentKey = this.props.selected || this.props.rootComponent.key;
+    const baseComponentMetrics = [this.props.requestedMetric.key];
+    if (this.props.requestedMetric.key === 'ncloc') {
+      baseComponentMetrics.push('ncloc_language_distribution');
+    }
+    Promise.all([
+      getComponentTree(strategy, componentKey, metricKeys, opts),
+      getMeasures({ componentKey, metricKeys: baseComponentMetrics.join() })
+    ]).then(
+      ([tree, measures]) => {
+        if (this.mounted) {
+          const metric = tree.metrics.find(m => m.key === this.props.requestedMetric.key);
+          const components = tree.components.map(component =>
+            enhanceComponent(component, metric, this.props.metrics)
+          );
+
+          const measure = measures.find(
+            measure => measure.metric === this.props.requestedMetric.key
+          );
+          const secondaryMeasure = measures.find(
+            measure => measure.metric !== this.props.requestedMetric.key
+          );
+
+          this.setState(({ selected }) => ({
+            baseComponent: tree.baseComponent,
+            components,
+            measure,
+            metric,
+            paging: tree.paging,
+            secondaryMeasure,
+            selected:
+              components.length > 0 && components.find(c => c.key === selected)
+                ? selected
+                : undefined
+          }));
+        }
+      },
+      () => {
+        if (this.mounted) {
+          this.setState({ loading: false });
+        }
+      }
+    );
   };
 
-  getComponentRequestParams = (view: View, metric: T.Metric, options: Object = {}) => {
+  fetchMoreComponents = () => {
+    const { metrics, view } = this.props;
+    const { baseComponent, metric, paging } = this.state;
+    if (!baseComponent || !paging || !metric) {
+      return;
+    }
+    const { metricKeys, opts, strategy } = this.getComponentRequestParams(view, metric, {
+      p: paging.pageIndex + 1
+    });
+    this.setState({ loadingMoreComponents: true });
+    getComponentTree(strategy, baseComponent.key, metricKeys, opts).then(
+      r => {
+        if (metric === this.props.requestedMetric) {
+          if (this.mounted) {
+            this.setState(state => ({
+              components: [
+                ...state.components,
+                ...r.components.map(component => enhanceComponent(component, metric, metrics))
+              ],
+              paging: r.paging
+            }));
+          }
+          this.setState({ loadingMoreComponents: false });
+        }
+      },
+      () => {
+        if (this.mounted) {
+          this.setState({ loadingMoreComponents: false });
+        }
+      }
+    );
+  };
+
+  getComponentRequestParams(
+    view: View,
+    metric: Pick<T.Metric, 'key' | 'direction'>,
+    options: Object = {}
+  ) {
     const strategy = view === 'list' ? 'leaves' : 'children';
     const metricKeys = [metric.key];
     const opts: RequestData = {
@@ -133,68 +216,16 @@ export default class MeasureContent extends React.PureComponent<Props, State> {
     }
 
     return { metricKeys, opts: { ...opts, ...options }, strategy };
-  };
-
-  fetchComponents = ({ component, metric, metrics, view }: Props) => {
-    if (isFileType(component)) {
-      return;
-    }
+  }
 
-    const { metricKeys, opts, strategy } = this.getComponentRequestParams(view, metric);
-    this.props.updateLoading({ components: true });
-    getComponentTree(strategy, component.key, metricKeys, opts).then(
-      r => {
-        if (metric === this.props.metric) {
-          if (this.mounted) {
-            this.setState(({ selected }: State) => ({
-              components: r.components.map(component =>
-                enhanceComponent(component, metric, metrics)
-              ),
-              metric: { ...metric, ...r.metrics.find(m => m.key === metric.key) },
-              paging: r.paging,
-              selected:
-                r.components.length > 0 && r.components.find(c => c.key === selected)
-                  ? selected
-                  : undefined,
-              view
-            }));
-          }
-          this.props.updateLoading({ components: false });
-        }
-      },
-      () => this.props.updateLoading({ components: false })
-    );
+  updateSelected = (component: string) => {
+    this.props.updateQuery({
+      selected: component !== this.props.rootComponent.key ? component : undefined
+    });
   };
 
-  fetchMoreComponents = () => {
-    const { component, metric, metrics, view } = this.props;
-    const { paging } = this.state;
-    if (!paging) {
-      return;
-    }
-    const { metricKeys, opts, strategy } = this.getComponentRequestParams(view, metric, {
-      p: paging.pageIndex + 1
-    });
-    this.props.updateLoading({ moreComponents: true });
-    getComponentTree(strategy, component.key, metricKeys, opts).then(
-      r => {
-        if (metric === this.props.metric) {
-          if (this.mounted) {
-            this.setState(state => ({
-              components: [
-                ...state.components,
-                ...r.components.map(component => enhanceComponent(component, metric, metrics))
-              ],
-              // merge to get the metric best value
-              metric: { ...metric, ...r.metrics.find(m => m.key === metric.key) },
-              paging: r.paging
-            }));
-          }
-          this.props.updateLoading({ moreComponents: false });
-        }
-      },
-      () => this.props.updateLoading({ moreComponents: false })
-    );
+  updateView = (view: View) => {
+    this.props.updateQuery({ view });
   };
 
   onOpenComponent = (componentKey: string) => {
@@ -209,26 +240,35 @@ export default class MeasureContent extends React.PureComponent<Props, State> {
         return;
       }
     }
-    this.setState({ selected: this.props.component.key });
-    this.props.updateSelected(componentKey);
+    this.setState(state => ({ selected: state.baseComponent!.key }));
+    this.updateSelected(componentKey);
     if (this.container) {
       this.container.focus();
     }
   };
 
-  onSelectComponent = (componentKey: string) => this.setState({ selected: componentKey });
+  onSelectComponent = (componentKey: string) => {
+    this.setState({ selected: componentKey });
+  };
+
+  getSelectedIndex = () => {
+    const componentKey = isFileType(this.state.baseComponent!)
+      ? this.state.baseComponent!.key
+      : this.state.selected;
+    const index = this.state.components.findIndex(component => component.key === componentKey);
+    return index !== -1 ? index : undefined;
+  };
 
   renderCode() {
     return (
       <div className="measure-details-viewer">
         <CodeView
           branchLike={this.props.branchLike}
-          component={this.props.component}
+          component={this.state.baseComponent!}
           components={this.state.components}
           leakPeriod={this.props.leakPeriod}
-          metric={this.props.metric}
           selectedIdx={this.getSelectedIndex()}
-          updateSelected={this.props.updateSelected}
+          updateSelected={this.updateSelected}
         />
       </div>
     );
@@ -250,13 +290,14 @@ export default class MeasureContent extends React.PureComponent<Props, State> {
           fetchMore={this.fetchMoreComponents}
           handleOpen={this.onOpenComponent}
           handleSelect={this.onSelectComponent}
-          loadingMore={this.props.loadingMore}
+          loadingMore={this.state.loadingMoreComponents}
           metric={metric}
           metrics={this.props.metrics}
           paging={this.state.paging}
           rootComponent={this.props.rootComponent}
           selectedIdx={selectedIdx}
           selectedKey={selectedIdx !== undefined ? this.state.selected : undefined}
+          view={view}
         />
       );
     } else {
@@ -272,13 +313,20 @@ export default class MeasureContent extends React.PureComponent<Props, State> {
   }
 
   render() {
-    const { branchLike, component, measure, metric, rootComponent, view } = this.props;
-    const isFile = isFileType(component);
+    const { branchLike, rootComponent, view } = this.props;
+    const { baseComponent, measure, metric, secondaryMeasure } = this.state;
+
+    if (!baseComponent || !metric) {
+      return null;
+    }
+
+    const measureValue =
+      measure && (isDiffMetric(measure.metric) ? getPeriodValue(measure, 1) : measure.value);
+    const isFile = isFileType(baseComponent);
     const selectedIdx = this.getSelectedIndex();
+
     return (
-      <div
-        className={classNames('no-outline', this.props.className)}
-        ref={container => (this.container = container)}>
+      <div className="layout-page-main no-outline" ref={container => (this.container = container)}>
         <div className="layout-page-header-panel layout-page-main-header">
           <div className="layout-page-header-panel-inner layout-page-main-header-inner">
             <div className="layout-page-main-inner">
@@ -288,21 +336,22 @@ export default class MeasureContent extends React.PureComponent<Props, State> {
                     backToFirst={view === 'list'}
                     branchLike={branchLike}
                     className="text-ellipsis flex-1"
-                    component={component}
+                    component={baseComponent}
                     handleSelect={this.onOpenComponent}
                     rootComponent={rootComponent}
                   />
                 }
                 right={
                   <div className="display-flex-center">
-                    {!isFile && (
-                      <MeasureViewSelect
-                        className="measure-view-select big-spacer-right"
-                        handleViewChange={this.props.updateView}
-                        metric={metric}
-                        view={view}
-                      />
-                    )}
+                    {!isFile &&
+                      metric && (
+                        <MeasureViewSelect
+                          className="measure-view-select big-spacer-right"
+                          handleViewChange={this.updateView}
+                          metric={metric}
+                          view={view}
+                        />
+                      )}
                     <PageActions
                       current={
                         selectedIdx !== undefined && view !== 'treemap'
@@ -320,22 +369,18 @@ export default class MeasureContent extends React.PureComponent<Props, State> {
             </div>
           </div>
         </div>
-        {!metric && <MetricNotFound className="layout-page-main-inner measure-details-content" />}
-        {metric && (
-          <div className="layout-page-main-inner measure-details-content">
-            <MeasureHeader
-              branchLike={branchLike}
-              component={component}
-              leakPeriod={this.props.leakPeriod}
-              measure={measure}
-              metric={metric}
-              secondaryMeasure={this.props.secondaryMeasure}
-            />
-            <DeferredSpinner loading={this.props.loading}>
-              {isFileType(component) ? this.renderCode() : this.renderMeasure()}
-            </DeferredSpinner>
-          </div>
-        )}
+
+        <div className="layout-page-main-inner measure-details-content">
+          <MeasureHeader
+            branchLike={branchLike}
+            component={baseComponent}
+            leakPeriod={this.props.leakPeriod}
+            measureValue={measureValue}
+            metric={metric}
+            secondaryMeasure={secondaryMeasure}
+          />
+          {isFile ? this.renderCode() : this.renderMeasure()}
+        </div>
       </div>
     );
   }
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContentContainer.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContentContainer.tsx
deleted file mode 100644 (file)
index cd6edf1..0000000
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-import * as React from 'react';
-import { InjectedRouter } from 'react-router';
-import MeasureContent from './MeasureContent';
-import { Query, View } from '../utils';
-
-interface Props {
-  branchLike?: T.BranchLike;
-  className?: string;
-  rootComponent: T.ComponentMeasure;
-  fetchMeasures: (
-    component: string,
-    metricsKey: string[],
-    branchLike?: T.BranchLike
-  ) => Promise<{ component: T.ComponentMeasure; measures: T.MeasureEnhanced[] }>;
-  leakPeriod?: T.Period;
-  metric: T.Metric;
-  metrics: { [metric: string]: T.Metric };
-  router: InjectedRouter;
-  selected?: string;
-  updateQuery: (query: Partial<Query>) => void;
-  view: View;
-}
-
-interface LoadingState {
-  measure: boolean;
-  components: boolean;
-  moreComponents: boolean;
-}
-
-interface State {
-  component?: T.ComponentMeasure;
-  loading: LoadingState;
-  measure?: T.MeasureEnhanced;
-  secondaryMeasure?: T.MeasureEnhanced;
-}
-
-export default class MeasureContentContainer extends React.PureComponent<Props, State> {
-  mounted = false;
-  state: State = { loading: { measure: false, components: false, moreComponents: false } };
-
-  componentDidMount() {
-    this.mounted = true;
-    this.fetchMeasure(this.props);
-  }
-
-  componentWillReceiveProps(nextProps: Props) {
-    const { component } = this.state;
-    const componentChanged =
-      !component ||
-      nextProps.rootComponent.key !== component.key ||
-      nextProps.selected !== component.key;
-    if (componentChanged || nextProps.metric !== this.props.metric) {
-      this.fetchMeasure(nextProps);
-    }
-  }
-
-  componentWillUnmount() {
-    this.mounted = false;
-  }
-
-  fetchMeasure = ({ branchLike, rootComponent, fetchMeasures, metric, selected }: Props) => {
-    this.updateLoading({ measure: true });
-
-    const metricKeys = [metric.key];
-    if (metric.key === 'ncloc') {
-      metricKeys.push('ncloc_language_distribution');
-    }
-
-    fetchMeasures(selected || rootComponent.key, metricKeys, branchLike).then(
-      ({ component, measures }) => {
-        if (this.mounted) {
-          const measure = measures.find(measure => measure.metric.key === metric.key);
-          const secondaryMeasure = measures.find(measure => measure.metric.key !== metric.key);
-          this.setState({ component, measure, secondaryMeasure });
-          this.updateLoading({ measure: false });
-        }
-      },
-      () => this.updateLoading({ measure: false })
-    );
-  };
-
-  updateLoading = (loading: Partial<LoadingState>) => {
-    if (this.mounted) {
-      this.setState(state => ({ loading: { ...state.loading, ...loading } }));
-    }
-  };
-
-  updateSelected = (component: string) => {
-    this.props.updateQuery({
-      selected: component !== this.props.rootComponent.key ? component : undefined
-    });
-  };
-
-  updateView = (view: View) => {
-    this.props.updateQuery({ view });
-  };
-
-  render() {
-    if (!this.state.component) {
-      return null;
-    }
-
-    return (
-      <MeasureContent
-        branchLike={this.props.branchLike}
-        className={this.props.className}
-        component={this.state.component}
-        leakPeriod={this.props.leakPeriod}
-        loading={this.state.loading.measure || this.state.loading.components}
-        loadingMore={this.state.loading.moreComponents}
-        measure={this.state.measure}
-        metric={this.props.metric}
-        metrics={this.props.metrics}
-        rootComponent={this.props.rootComponent}
-        router={this.props.router}
-        secondaryMeasure={this.state.secondaryMeasure}
-        updateLoading={this.updateLoading}
-        updateSelected={this.updateSelected}
-        updateView={this.updateView}
-        view={this.props.view}
-      />
-    );
-  }
-}
index 2f261e241984766e9b5e4434007b3bfdf0f33d63..9c1b1898cfa7d3df77b2a29879a46ffa24091d4d 100644 (file)
@@ -34,13 +34,13 @@ interface Props {
   branchLike?: T.BranchLike;
   component: T.ComponentMeasure;
   leakPeriod?: T.Period;
-  measure?: T.MeasureEnhanced;
+  measureValue?: string;
   metric: T.Metric;
-  secondaryMeasure?: T.MeasureEnhanced;
+  secondaryMeasure?: T.Measure;
 }
 
 export default function MeasureHeader(props: Props) {
-  const { branchLike, component, leakPeriod, measure, metric, secondaryMeasure } = props;
+  const { branchLike, component, leakPeriod, measureValue, metric, secondaryMeasure } = props;
   const isDiff = isDiffMetric(metric.key);
   const hasHistory =
     component.qualifier !== 'FIL' && component.qualifier !== 'UTS' && hasFullMeasures(branchLike);
@@ -53,20 +53,12 @@ export default function MeasureHeader(props: Props) {
           {getLocalizedMetricName(metric)}
           <span className="measure-details-value spacer-left">
             <strong>
-              {isDiff ? (
-                <Measure
-                  className="leak-box"
-                  metricKey={metric.key}
-                  metricType={metric.type}
-                  value={measure && measure.leak}
-                />
-              ) : (
-                <Measure
-                  metricKey={metric.key}
-                  metricType={metric.type}
-                  value={measure && measure.value}
-                />
-              )}
+              <Measure
+                className={isDiff ? 'leak-box' : undefined}
+                metricKey={metric.key}
+                metricType={metric.type}
+                value={measureValue}
+              />
             </strong>
           </span>
           {!isDiff &&
@@ -88,7 +80,7 @@ export default function MeasureHeader(props: Props) {
         </div>
       </div>
       {secondaryMeasure &&
-        secondaryMeasure.metric.key === 'ncloc_language_distribution' &&
+        secondaryMeasure.metric === 'ncloc_language_distribution' &&
         secondaryMeasure.value !== undefined && (
           <div className="measure-details-secondary">
             <LanguageDistributionContainer
index e18c68211080a9a6963281616c24b7c4f9ddf262..cc871ea922fa98d69b638ba0d2da4de0cfe5cfb9 100644 (file)
@@ -26,7 +26,7 @@ import BubbleChart from '../drilldown/BubbleChart';
 import SourceViewer from '../../../components/SourceViewer/SourceViewer';
 import { getComponentLeaves } from '../../../api/components';
 import { enhanceComponent, getBubbleMetrics, isFileType } from '../utils';
-import { getBranchLikeQuery } from '../../../helpers/branches';
+import { getBranchLikeQuery, isSameBranchLike } from '../../../helpers/branches';
 import DeferredSpinner from '../../../components/common/DeferredSpinner';
 
 interface Props {
@@ -55,16 +55,17 @@ export default class MeasureOverview extends React.PureComponent<Props, State> {
 
   componentDidMount() {
     this.mounted = true;
-    this.fetchComponents(this.props);
+    this.fetchComponents();
   }
 
-  componentWillReceiveProps(nextProps: Props) {
+  componentDidUpdate(prevProps: Props) {
     if (
-      nextProps.component !== this.props.component ||
-      nextProps.metrics !== this.props.metrics ||
-      nextProps.domain !== this.props.domain
+      prevProps.component !== this.props.component ||
+      !isSameBranchLike(prevProps.branchLike, this.props.branchLike) ||
+      prevProps.metrics !== this.props.metrics ||
+      prevProps.domain !== this.props.domain
     ) {
-      this.fetchComponents(nextProps);
+      this.fetchComponents();
     }
   }
 
@@ -72,8 +73,8 @@ export default class MeasureOverview extends React.PureComponent<Props, State> {
     this.mounted = false;
   }
 
-  fetchComponents = (props: Props) => {
-    const { branchLike, component, domain, metrics } = props;
+  fetchComponents = () => {
+    const { branchLike, component, domain, metrics } = this.props;
     if (isFileType(component)) {
       this.setState({ components: [], paging: undefined });
       return;
index 200fce078a2a0c20742738ef35ad755d0a281025..af26325e68a4c20b7ef5a5ed628879cfcd6b3433 100644 (file)
@@ -23,7 +23,7 @@ import MeasureOverview from './MeasureOverview';
 import { getComponentShow } from '../../../api/components';
 import { getProjectUrl } from '../../../helpers/urls';
 import { isViewType, Query } from '../utils';
-import { getBranchLikeQuery } from '../../../helpers/branches';
+import { getBranchLikeQuery, isSameBranchLike } from '../../../helpers/branches';
 
 interface Props {
   branchLike?: T.BranchLike;
@@ -56,17 +56,18 @@ export default class MeasureOverviewContainer extends React.PureComponent<Props,
 
   componentDidMount() {
     this.mounted = true;
-    this.fetchComponent(this.props);
+    this.fetchComponent();
   }
 
-  componentWillReceiveProps(nextProps: Props) {
-    const { component } = this.state;
-    const componentChanged =
-      !component ||
-      nextProps.rootComponent.key !== component.key ||
-      nextProps.selected !== component.key;
-    if (componentChanged || nextProps.domain !== this.props.domain) {
-      this.fetchComponent(nextProps);
+  componentDidUpdate(prevProps: Props) {
+    const prevComponentKey = prevProps.selected || prevProps.rootComponent.key;
+    const componentKey = this.props.selected || this.props.rootComponent.key;
+    if (
+      prevComponentKey !== componentKey ||
+      !isSameBranchLike(prevProps.branchLike, this.props.branchLike) ||
+      prevProps.domain !== this.props.domain
+    ) {
+      this.fetchComponent();
     }
   }
 
@@ -74,7 +75,8 @@ export default class MeasureOverviewContainer extends React.PureComponent<Props,
     this.mounted = false;
   }
 
-  fetchComponent = ({ branchLike, rootComponent, selected }: Props) => {
+  fetchComponent = () => {
+    const { branchLike, rootComponent, selected } = this.props;
     if (!selected || rootComponent.key === selected) {
       this.setState({ component: rootComponent });
       this.updateLoading({ component: false });
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MetricNotFound.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/MetricNotFound.tsx
deleted file mode 100644 (file)
index 6985137..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-import * as React from 'react';
-import { translate } from '../../../helpers/l10n';
-import { Alert } from '../../../components/ui/Alert';
-
-// TODO seems like this component is used by never rendered in real life
-export default function MetricNotFound({ className }: { className?: string }) {
-  return (
-    <div className={className}>
-      <Alert variant="error">{translate('component_measures.not_found')}</Alert>
-    </div>
-  );
-}
index de3394ecc88051bee3b336dd79cb5677cbd10833..ad3c05ee775810e3475ebdf337514d2db9143735 100644 (file)
  */
 import * as React from 'react';
 import { shallow } from 'enzyme';
-import App from '../App';
+import { Location } from 'history';
+import { App } from '../App';
 import { waitAndUpdate } from '../../../../helpers/testUtils';
+import { getMeasuresAndMeta } from '../../../../api/measures';
 
-const COMPONENT = { key: 'foo', name: 'Foo', qualifier: 'TRK' };
+jest.mock('../../../../api/metrics', () => ({
+  getAllMetrics: jest.fn().mockResolvedValue([
+    {
+      id: '1',
+      key: 'lines_to_cover',
+      type: 'INT',
+      name: 'Lines to Cover',
+      domain: 'Coverage'
+    },
+    {
+      id: '2',
+      key: 'coverage',
+      type: 'PERCENT',
+      name: 'Coverage',
+      domain: 'Coverage'
+    },
+    {
+      id: '3',
+      key: 'duplicated_lines_density',
+      type: 'PERCENT',
+      name: 'Duplicated Lines (%)',
+      domain: 'Duplications'
+    },
+    {
+      id: '4',
+      key: 'new_bugs',
+      type: 'INT',
+      name: 'New Bugs',
+      domain: 'Reliability'
+    }
+  ])
+}));
 
-const METRICS = {
-  lines_to_cover: {
-    id: '1',
-    key: 'lines_to_cover',
-    type: 'INT',
-    name: 'Lines to Cover',
-    domain: 'Coverage'
-  },
-  coverage: { id: '2', key: 'coverage', type: 'PERCENT', name: 'Coverage', domain: 'Coverage' },
-  duplicated_lines_density: {
-    id: '3',
-    key: 'duplicated_lines_density',
-    type: 'PERCENT',
-    name: 'Duplicated Lines (%)',
-    domain: 'Duplications'
-  },
-  new_bugs: { id: '4', key: 'new_bugs', type: 'INT', name: 'New Bugs', domain: 'Reliability' }
-};
+jest.mock('../../../../api/measures', () => ({
+  getMeasuresAndMeta: jest.fn()
+}));
+
+const COMPONENT = { key: 'foo', name: 'Foo', qualifier: 'TRK' };
 
 const PROPS: App['props'] = {
   branchLike: { isMain: true, name: 'master' },
   component: COMPONENT,
-  location: { pathname: '/component_measures', query: { metric: 'coverage' } },
-  fetchMeasures: jest.fn().mockResolvedValue({
-    component: COMPONENT,
-    measures: [{ metric: 'coverage', value: '80.0' }]
-  }),
-  fetchMetrics: jest.fn(),
-  metrics: METRICS,
-  metricsKey: ['lines_to_cover', 'coverage', 'duplicated_lines_density', 'new_bugs'],
-  router: { push: jest.fn() } as any
+  location: { pathname: '/component_measures', query: { metric: 'coverage' } } as Location,
+  params: {},
+  router: { push: jest.fn() } as any,
+  routes: []
 };
 
+beforeEach(() => {
+  (getMeasuresAndMeta as jest.Mock).mockResolvedValue({
+    component: { measures: [{ metric: 'coverage', value: '80.0' }] },
+    periods: [{ index: '1' }]
+  });
+});
+
 it('should render correctly', async () => {
   const wrapper = shallow(<App {...PROPS} />);
   expect(wrapper.find('.spinner')).toHaveLength(1);
@@ -68,7 +90,7 @@ it('should render a measure overview', async () => {
   const wrapper = shallow(
     <App
       {...PROPS}
-      location={{ pathname: '/component_measures', query: { metric: 'Reliability' } }}
+      location={{ pathname: '/component_measures', query: { metric: 'Reliability' } } as Location}
     />
   );
   expect(wrapper.find('.spinner')).toHaveLength(1);
@@ -77,8 +99,11 @@ it('should render a measure overview', async () => {
 });
 
 it('should render a message when there are no measures', async () => {
-  const fetchMeasures = jest.fn().mockResolvedValue({ component: COMPONENT, measures: [] });
-  const wrapper = shallow(<App {...PROPS} fetchMeasures={fetchMeasures} />);
+  (getMeasuresAndMeta as jest.Mock).mockResolvedValue({
+    component: { measures: [] },
+    periods: [{ index: '1' }]
+  });
+  const wrapper = shallow(<App {...PROPS} />);
   await waitAndUpdate(wrapper);
   expect(wrapper).toMatchSnapshot();
 });
index e00c0b579120650d2c5da306f5700005f59365a5..4fdc255b5a330ccdaa0cf59c74599526317b2055 100644 (file)
@@ -28,13 +28,6 @@ const METRIC = {
   name: 'Reliability Rating'
 };
 
-const MEASURE = {
-  value: '3.0',
-  periods: [{ index: 1, value: '0.0' }],
-  metric: METRIC,
-  leak: '0.0'
-};
-
 const LEAK_METRIC = {
   id: '2',
   key: 'new_reliability_rating',
@@ -42,20 +35,11 @@ const LEAK_METRIC = {
   name: 'Reliability Rating on New Code'
 };
 
-const LEAK_MEASURE = {
-  periods: [{ index: 1, value: '3.0' }],
-  metric: LEAK_METRIC,
-  leak: '3.0'
-};
+const LEAK_MEASURE = '3.0';
 
 const SECONDARY = {
   value: 'java=175123;js=26382',
-  metric: {
-    id: '3',
-    key: 'ncloc_language_distribution',
-    type: 'DATA',
-    name: 'Lines of Code Per Language'
-  }
+  metric: 'ncloc_language_distribution'
 };
 
 const PROPS = {
@@ -66,7 +50,7 @@ const PROPS = {
     mode: 'previous_version',
     parameter: '6,4'
   } as T.Period,
-  measure: MEASURE,
+  measureValue: '3.0',
   metric: METRIC
 };
 
@@ -76,7 +60,7 @@ it('should render correctly', () => {
 
 it('should render correctly for leak', () => {
   expect(
-    shallow(<MeasureHeader {...PROPS} measure={LEAK_MEASURE} metric={LEAK_METRIC} />)
+    shallow(<MeasureHeader {...PROPS} measureValue={LEAK_MEASURE} metric={LEAK_METRIC} />)
   ).toMatchSnapshot();
 });
 
@@ -94,7 +78,7 @@ it('should render with short living branch', () => {
       <MeasureHeader
         {...PROPS}
         branchLike={shortBranch}
-        measure={LEAK_MEASURE}
+        measureValue={LEAK_MEASURE}
         metric={LEAK_METRIC}
       />
     )
@@ -121,5 +105,5 @@ it('should display secondary measure too', () => {
 });
 
 it('should work with measure without value', () => {
-  expect(shallow(<MeasureHeader {...PROPS} measure={undefined} />)).toMatchSnapshot();
+  expect(shallow(<MeasureHeader {...PROPS} measureValue={undefined} />)).toMatchSnapshot();
 });
index 56e8fe4c14f5f81b6d78cc4397e66fc931c2d937..e90f3b035eaa3c291432934e892e5e2bcbcf2a08 100644 (file)
@@ -73,48 +73,13 @@ exports[`should render correctly 1`] = `
     >
       <Component />
     </ScreenPositionHelper>
-    <MeasureContentContainer
+    <MeasureContent
       branchLike={
         Object {
           "isMain": true,
           "name": "master",
         }
       }
-      className="layout-page-main"
-      fetchMeasures={
-        [MockFunction] {
-          "calls": Array [
-            Array [
-              "foo",
-              Array [
-                "lines_to_cover",
-                "coverage",
-                "duplicated_lines_density",
-                "new_bugs",
-              ],
-              Object {
-                "isMain": true,
-                "name": "master",
-              },
-            ],
-          ],
-          "results": Array [
-            Object {
-              "isThrow": false,
-              "value": Promise {},
-            },
-          ],
-        }
-      }
-      metric={
-        Object {
-          "domain": "Coverage",
-          "id": "2",
-          "key": "coverage",
-          "name": "Coverage",
-          "type": "PERCENT",
-        }
-      }
       metrics={
         Object {
           "coverage": Object {
@@ -147,6 +112,15 @@ exports[`should render correctly 1`] = `
           },
         }
       }
+      requestedMetric={
+        Object {
+          "domain": "Coverage",
+          "id": "2",
+          "key": "coverage",
+          "name": "Coverage",
+          "type": "PERCENT",
+        }
+      }
       rootComponent={
         Object {
           "key": "foo",
index 6b88ff482794f3b3e1cb7860ff4e3939aeb15935..561368ea1a3e8000ec0b4267a09c56de97620e01 100644 (file)
@@ -26,7 +26,6 @@ interface Props {
   component: T.ComponentMeasure;
   components: T.ComponentMeasureEnhanced[];
   leakPeriod?: T.Period;
-  metric: T.Metric;
   selectedIdx?: number;
   updateSelected: (component: string) => void;
 }
index dc27d380146658fa4af095aecef4773652c28e7d..07c249ce0edfed8aa74b4bd9a4e9d4f495282fd1 100644 (file)
@@ -30,6 +30,7 @@ import {
   getProjectUrl
 } from '../../../helpers/urls';
 import { translate } from '../../../helpers/l10n';
+import { View } from '../utils';
 
 interface Props {
   branchLike?: T.BranchLike;
@@ -37,6 +38,7 @@ interface Props {
   onClick: (component: string) => void;
   metric: T.Metric;
   rootComponent: T.ComponentMeasure;
+  view: View;
 }
 
 export default class ComponentCell extends React.PureComponent<Props> {
@@ -54,33 +56,34 @@ export default class ComponentCell extends React.PureComponent<Props> {
     const { component } = this.props;
     let head = '';
     let tail = component.name;
-    let branchComponent = null;
 
-    if (['DIR', 'FIL', 'UTS'].includes(component.qualifier) && component.path) {
+    if (
+      this.props.view === 'list' &&
+      ['FIL', 'UTS', 'DIR'].includes(component.qualifier) &&
+      component.path
+    ) {
       ({ head, tail } = splitPath(component.path));
     }
 
-    if (this.props.rootComponent.qualifier === 'APP') {
-      branchComponent = (
-        <>
-          {component.branch ? (
-            <>
-              <LongLivingBranchIcon className="spacer-left little-spacer-right" />
-              <span className="note">{component.branch}</span>
-            </>
-          ) : (
-            <span className="spacer-left outline-badge">{translate('branches.main_branch')}</span>
-          )}
-        </>
-      );
-    }
+    const isApp = this.props.rootComponent.qualifier === 'APP';
+
     return (
       <span title={componentKey}>
-        <QualifierIcon qualifier={component.qualifier} />
-        &nbsp;
+        <QualifierIcon className="little-spacer-right" qualifier={component.qualifier} />
         {head.length > 0 && <span className="note">{head}/</span>}
         <span>{tail}</span>
-        {branchComponent}
+        {isApp && (
+          <>
+            {component.branch ? (
+              <>
+                <LongLivingBranchIcon className="spacer-left little-spacer-right" />
+                <span className="note">{component.branch}</span>
+              </>
+            ) : (
+              <span className="spacer-left outline-badge">{translate('branches.main_branch')}</span>
+            )}
+          </>
+        )}
       </span>
     );
   }
index 1cb3fa627b66c37707d7a542490985e3d7455ef6..21777417bec9435992712edc8694d42deb99cb7f 100644 (file)
@@ -22,6 +22,7 @@ import ComponentsListRow from './ComponentsListRow';
 import EmptyResult from './EmptyResult';
 import { complementary } from '../config/complementary';
 import { getLocalizedMetricName } from '../../../helpers/l10n';
+import { View } from '../utils';
 
 interface Props {
   branchLike?: T.BranchLike;
@@ -31,6 +32,7 @@ interface Props {
   metrics: { [metric: string]: T.Metric };
   rootComponent: T.ComponentMeasure;
   selectedComponent?: string;
+  view: View;
 }
 
 export default function ComponentsList({ components, metric, metrics, ...props }: Props) {
@@ -40,37 +42,35 @@ export default function ComponentsList({ components, metric, metrics, ...props }
 
   const otherMetrics = (complementary[metric.key] || []).map(key => metrics[key]);
   return (
-    <React.Fragment>
-      <table className="data zebra zebra-hover">
-        {otherMetrics.length > 0 && (
-          <thead>
-            <tr>
-              <th>&nbsp;</th>
-              <th className="text-right">
+    <table className="data zebra zebra-hover">
+      {otherMetrics.length > 0 && (
+        <thead>
+          <tr>
+            <th>&nbsp;</th>
+            <th className="text-right">
+              <span className="small">{getLocalizedMetricName(metric)}</span>
+            </th>
+            {otherMetrics.map(metric => (
+              <th className="text-right" key={metric.key}>
                 <span className="small">{getLocalizedMetricName(metric)}</span>
               </th>
-              {otherMetrics.map(metric => (
-                <th className="text-right" key={metric.key}>
-                  <span className="small">{getLocalizedMetricName(metric)}</span>
-                </th>
-              ))}
-            </tr>
-          </thead>
-        )}
+            ))}
+          </tr>
+        </thead>
+      )}
 
-        <tbody>
-          {components.map(component => (
-            <ComponentsListRow
-              component={component}
-              isSelected={component.key === props.selectedComponent}
-              key={component.key}
-              metric={metric}
-              otherMetrics={otherMetrics}
-              {...props}
-            />
-          ))}
-        </tbody>
-      </table>
-    </React.Fragment>
+      <tbody>
+        {components.map(component => (
+          <ComponentsListRow
+            component={component}
+            isSelected={component.key === props.selectedComponent}
+            key={component.key}
+            metric={metric}
+            otherMetrics={otherMetrics}
+            {...props}
+          />
+        ))}
+      </tbody>
+    </table>
   );
 }
index 571c0191e3a96a21365621efd2a98a1ded5b51ad..eba1ed5536aa62b172fd3fc292f42d84c8f734cc 100644 (file)
@@ -21,6 +21,7 @@ import * as React from 'react';
 import * as classNames from 'classnames';
 import ComponentCell from './ComponentCell';
 import MeasureCell from './MeasureCell';
+import { View } from '../utils';
 
 interface Props {
   branchLike?: T.BranchLike;
@@ -30,6 +31,7 @@ interface Props {
   otherMetrics: T.Metric[];
   metric: T.Metric;
   rootComponent: T.ComponentMeasure;
+  view: View;
 }
 
 export default function ComponentsListRow(props: Props) {
@@ -49,6 +51,7 @@ export default function ComponentsListRow(props: Props) {
         metric={props.metric}
         onClick={props.onClick}
         rootComponent={rootComponent}
+        view={props.view}
       />
 
       <MeasureCell component={component} metric={props.metric} />
index d4f364f6f34662625522879c99cd846a88a7e297..919bb516797f28f2c8ad415094f767dc8fbb729c 100644 (file)
@@ -27,6 +27,7 @@ import { translate, translateWithParameters } from '../../../helpers/l10n';
 import { isPeriodBestValue, isDiffMetric, formatMeasure } from '../../../helpers/measures';
 import { scrollToElement } from '../../../helpers/scrolling';
 import { Alert } from '../../../components/ui/Alert';
+import { View } from '../utils';
 
 interface Props {
   branchLike?: T.BranchLike;
@@ -42,12 +43,15 @@ interface Props {
   rootComponent: T.ComponentMeasure;
   selectedKey?: string;
   selectedIdx?: number;
+  view: View;
 }
 
 interface State {
   showBestMeasures: boolean;
 }
 
+const keyScope = 'measures-files';
+
 export default class FilesView extends React.PureComponent<Props, State> {
   listContainer?: HTMLElement | null;
 
@@ -79,22 +83,22 @@ export default class FilesView extends React.PureComponent<Props, State> {
   }
 
   attachShortcuts() {
-    key('up', 'measures-files', () => {
+    key('up', keyScope, () => {
       this.selectPrevious();
       return false;
     });
-    key('down', 'measures-files', () => {
+    key('down', keyScope, () => {
       this.selectNext();
       return false;
     });
-    key('right', 'measures-files', () => {
+    key('right', keyScope, () => {
       this.openSelected();
       return false;
     });
   }
 
   detachShortcuts() {
-    ['up', 'down', 'right'].forEach(action => key.unbind(action, 'measures-files'));
+    ['up', 'down', 'right'].forEach(action => key.unbind(action, keyScope));
   }
 
   getVisibleComponents = (components: T.ComponentMeasureEnhanced[], showBestMeasures: boolean) => {
@@ -170,6 +174,7 @@ export default class FilesView extends React.PureComponent<Props, State> {
           onClick={this.props.handleOpen}
           rootComponent={this.props.rootComponent}
           selectedComponent={this.props.selectedKey}
+          view={this.props.view}
         />
         {hidingBestMeasures && (
           <Alert className="spacer-top" variant="info">
index 6ab93f9f2e5b7f8c81ab88ca6841ad42af6f328f..6139ea9bee47c90c0d130a480d4c3c90c528db3e 100644 (file)
@@ -47,6 +47,7 @@ it('should renders correctly', () => {
         metrics={METRICS}
         onClick={jest.fn()}
         rootComponent={COMPONENTS[0]}
+        view="tree"
       />
     )
   ).toMatchSnapshot();
@@ -61,6 +62,7 @@ it('should renders empty', () => {
         metrics={METRICS}
         onClick={jest.fn()}
         rootComponent={COMPONENTS[0]}
+        view="tree"
       />
     )
   ).toMatchSnapshot();
@@ -75,6 +77,7 @@ it('should renders with multiple measures', () => {
         metrics={METRICS}
         onClick={jest.fn()}
         rootComponent={COMPONENTS[0]}
+        view="tree"
       />
     )
   ).toMatchSnapshot();
index 425024d3e864ce525a419277be498f4bb9325957..ae2682cefc991bc963b6a3f749b564471875f37e 100644 (file)
@@ -73,6 +73,7 @@ function getWrapper(props = {}) {
         organization: 'foo',
         qualifier: 'TRK'
       }}
+      view="tree"
       {...props}
     />
   );
index 743f6ea0ba862797160e163bb891758403a73171..c16ae7b6dffad607a7226c3469dddb33738609c5 100644 (file)
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
 exports[`should renders correctly 1`] = `
-<Fragment>
-  <table
-    className="data zebra zebra-hover"
-  >
-    <tbody>
-      <ComponentsListRow
-        component={
-          Object {
-            "key": "foo",
-            "measures": Array [],
-            "name": "Foo",
-            "organization": "foo",
-            "qualifier": "TRK",
-          }
+<table
+  className="data zebra zebra-hover"
+>
+  <tbody>
+    <ComponentsListRow
+      component={
+        Object {
+          "key": "foo",
+          "measures": Array [],
+          "name": "Foo",
+          "organization": "foo",
+          "qualifier": "TRK",
         }
-        isSelected={false}
-        key="foo"
-        metric={
-          Object {
-            "id": "2",
-            "key": "new_bugs",
-            "name": "New Bugs",
-            "type": "INT",
-          }
+      }
+      isSelected={false}
+      key="foo"
+      metric={
+        Object {
+          "id": "2",
+          "key": "new_bugs",
+          "name": "New Bugs",
+          "type": "INT",
         }
-        onClick={[MockFunction]}
-        otherMetrics={Array []}
-        rootComponent={
-          Object {
-            "key": "foo",
-            "measures": Array [],
-            "name": "Foo",
-            "organization": "foo",
-            "qualifier": "TRK",
-          }
+      }
+      onClick={[MockFunction]}
+      otherMetrics={Array []}
+      rootComponent={
+        Object {
+          "key": "foo",
+          "measures": Array [],
+          "name": "Foo",
+          "organization": "foo",
+          "qualifier": "TRK",
         }
-      />
-    </tbody>
-  </table>
-</Fragment>
+      }
+      view="tree"
+    />
+  </tbody>
+</table>
 `;
 
 exports[`should renders empty 1`] = `<EmptyResult />`;
 
 exports[`should renders with multiple measures 1`] = `
-<Fragment>
-  <table
-    className="data zebra zebra-hover"
-  >
-    <thead>
-      <tr>
-        <th>
-           
-        </th>
-        <th
-          className="text-right"
+<table
+  className="data zebra zebra-hover"
+>
+  <thead>
+    <tr>
+      <th>
+         
+      </th>
+      <th
+        className="text-right"
+      >
+        <span
+          className="small"
         >
-          <span
-            className="small"
-          >
-            Coverage
-          </span>
-        </th>
-        <th
-          className="text-right"
-          key="uncovered_lines"
+          Coverage
+        </span>
+      </th>
+      <th
+        className="text-right"
+        key="uncovered_lines"
+      >
+        <span
+          className="small"
         >
-          <span
-            className="small"
-          >
-            Lines
-          </span>
-        </th>
-        <th
-          className="text-right"
-          key="uncovered_conditions"
+          Lines
+        </span>
+      </th>
+      <th
+        className="text-right"
+        key="uncovered_conditions"
+      >
+        <span
+          className="small"
         >
-          <span
-            className="small"
-          >
-            Conditions
-          </span>
-        </th>
-      </tr>
-    </thead>
-    <tbody>
-      <ComponentsListRow
-        component={
-          Object {
-            "key": "foo",
-            "measures": Array [],
-            "name": "Foo",
-            "organization": "foo",
-            "qualifier": "TRK",
-          }
+          Conditions
+        </span>
+      </th>
+    </tr>
+  </thead>
+  <tbody>
+    <ComponentsListRow
+      component={
+        Object {
+          "key": "foo",
+          "measures": Array [],
+          "name": "Foo",
+          "organization": "foo",
+          "qualifier": "TRK",
         }
-        isSelected={false}
-        key="foo"
-        metric={
-          Object {
-            "id": "1",
-            "key": "coverage",
-            "name": "Coverage",
-            "type": "PERCENT",
-          }
-        }
-        onClick={[MockFunction]}
-        otherMetrics={
-          Array [
-            Object {
-              "id": "3",
-              "key": "uncovered_lines",
-              "name": "Lines",
-              "type": "INT",
-            },
-            Object {
-              "id": "4",
-              "key": "uncovered_conditions",
-              "name": "Conditions",
-              "type": "INT",
-            },
-          ]
+      }
+      isSelected={false}
+      key="foo"
+      metric={
+        Object {
+          "id": "1",
+          "key": "coverage",
+          "name": "Coverage",
+          "type": "PERCENT",
         }
-        rootComponent={
+      }
+      onClick={[MockFunction]}
+      otherMetrics={
+        Array [
           Object {
-            "key": "foo",
-            "measures": Array [],
-            "name": "Foo",
-            "organization": "foo",
-            "qualifier": "TRK",
-          }
+            "id": "3",
+            "key": "uncovered_lines",
+            "name": "Lines",
+            "type": "INT",
+          },
+          Object {
+            "id": "4",
+            "key": "uncovered_conditions",
+            "name": "Conditions",
+            "type": "INT",
+          },
+        ]
+      }
+      rootComponent={
+        Object {
+          "key": "foo",
+          "measures": Array [],
+          "name": "Foo",
+          "organization": "foo",
+          "qualifier": "TRK",
         }
-      />
-    </tbody>
-  </table>
-</Fragment>
+      }
+      view="tree"
+    />
+  </tbody>
+</table>
 `;
index e9c9171459361bb49e62ab25f0c802800809f133..0efe85f4ba852acad364cc91edce68386c03ca4d 100644 (file)
@@ -42,6 +42,7 @@ exports[`should render with best values hidden 1`] = `
         "qualifier": "TRK",
       }
     }
+    view="tree"
   />
   <Alert
     className="spacer-top"
@@ -100,6 +101,7 @@ exports[`should renders correctly 1`] = `
         "qualifier": "TRK",
       }
     }
+    view="tree"
   />
   <ListFooter
     count={1}
index 045d1686c1bbe4303888b8b95a35dae4c903194d..7b26d3dc8438bbce891ac1ad0c5fb928c070b232 100644 (file)
@@ -22,7 +22,7 @@ import { lazyLoad } from '../../components/lazyLoad';
 
 const routes = [
   {
-    indexRoute: { component: lazyLoad(() => import('./components/AppContainer')) }
+    indexRoute: { component: lazyLoad(() => import('./components/App')) }
   },
   {
     path: 'domain/:domainName',
index 0086d1f4e7682d415ca13ba450ec6de383ec9e0c..c6a3e599d7063bca755b64e11424312369efacc5 100644 (file)
@@ -20,7 +20,7 @@
 import * as React from 'react';
 import ProjectOverviewFacet from './ProjectOverviewFacet';
 import DomainFacet from './DomainFacet';
-import { getDefaultView, groupByDomains, KNOWN_DOMAINS, PROJECT_OVERVEW, Query } from '../utils';
+import { groupByDomains, KNOWN_DOMAINS, PROJECT_OVERVEW, Query } from '../utils';
 
 interface Props {
   hasOverview: boolean;
@@ -34,31 +34,12 @@ interface State {
 }
 
 export default class Sidebar extends React.PureComponent<Props, State> {
-  constructor(props: Props) {
-    super(props);
-    this.state = { openFacets: this.getOpenFacets({}, props) };
+  static getDerivedStateFromProps(props: Props, state: State) {
+    return { openFacets: getOpenFacets(state.openFacets, props) };
   }
 
-  componentWillReceiveProps(nextProps: Props) {
-    if (nextProps.selectedMetric !== this.props.selectedMetric) {
-      this.setState(({ openFacets }) => ({
-        openFacets: this.getOpenFacets(openFacets, nextProps)
-      }));
-    }
-  }
-
-  getOpenFacets = (
-    openFacets: { [metric: string]: boolean },
-    { measures, selectedMetric }: Props
-  ) => {
-    const newOpenFacets = { ...openFacets };
-    const measure = measures.find(measure => measure.metric.key === selectedMetric);
-    if (measure && measure.metric && measure.metric.domain) {
-      newOpenFacets[measure.metric.domain] = true;
-    } else if (KNOWN_DOMAINS.includes(selectedMetric)) {
-      newOpenFacets[selectedMetric] = true;
-    }
-    return newOpenFacets;
+  state: State = {
+    openFacets: {}
   };
 
   toggleFacet = (name: string) => {
@@ -67,10 +48,9 @@ export default class Sidebar extends React.PureComponent<Props, State> {
     }));
   };
 
-  resetSelection = (metric: string) => ({ selected: undefined, view: getDefaultView(metric) });
-
-  changeMetric = (metric: string) =>
-    this.props.updateQuery({ metric, ...this.resetSelection(metric) });
+  changeMetric = (metric: string) => {
+    this.props.updateQuery({ metric });
+  };
 
   render() {
     const { hasOverview } = this.props;
@@ -98,3 +78,17 @@ export default class Sidebar extends React.PureComponent<Props, State> {
     );
   }
 }
+
+function getOpenFacets(
+  openFacets: { [metric: string]: boolean },
+  { measures, selectedMetric }: Props
+) {
+  const newOpenFacets = { ...openFacets };
+  const measure = measures.find(measure => measure.metric.key === selectedMetric);
+  if (measure && measure.metric && measure.metric.domain) {
+    newOpenFacets[measure.metric.domain] = true;
+  } else if (KNOWN_DOMAINS.includes(selectedMetric)) {
+    newOpenFacets[selectedMetric] = true;
+  }
+  return newOpenFacets;
+}
index 49b0b7a677555fca484d5d2a9ef2462e177f26cb..52e39e4e0599a9edb1efcb2316f9350d1ef5a2c0 100644 (file)
@@ -87,7 +87,7 @@ export function addMeasureCategories(domainName: string, measures: T.MeasureEnha
 
 export function enhanceComponent(
   component: T.ComponentMeasure,
-  metric: T.Metric | undefined,
+  metric: Pick<T.Metric, 'key'> | undefined,
   metrics: { [key: string]: T.Metric }
 ): T.ComponentMeasureEnhanced {
   if (!component.measures) {
@@ -124,13 +124,6 @@ export const groupByDomains = memoize((measures: T.MeasureEnhanced[]) => {
   ]);
 });
 
-export function getDefaultView(metric: string): View {
-  if (!hasList(metric)) {
-    return 'tree';
-  }
-  return DEFAULT_VIEW;
-}
-
 export function hasList(metric: string): boolean {
   return !['releasability_rating', 'releasability_effort'].includes(metric);
 }
index 4a7c46e37545381754efe724829f2169505bf3fa..5d3a86e8ca40f5969feb7cbf6e794475f1de1c14 100644 (file)
@@ -27,7 +27,6 @@ import Coverage from '../main/Coverage';
 import Duplications from '../main/Duplications';
 import MetaContainer from '../meta/MetaContainer';
 import QualityGate from '../qualityGate/QualityGate';
-import throwGlobalError from '../../../app/utils/throwGlobalError';
 import { getMeasuresAndMeta } from '../../../api/measures';
 import { getAllTimeMachineData } from '../../../api/time-machine';
 import { parseDate } from '../../../helpers/dates';
@@ -150,8 +149,7 @@ export class OverviewApp extends React.PureComponent<Props, State> {
           });
         }
       },
-      error => {
-        throwGlobalError(error);
+      () => {
         if (this.mounted) {
           this.setState({ loading: false });
         }