]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-11478 Update the tree view on the Measures page (#982)
authorStas Vilchik <stas.vilchik@sonarsource.com>
Wed, 28 Nov 2018 12:52:56 +0000 (13:52 +0100)
committersonartech <sonartech@sonarsource.com>
Wed, 16 Jan 2019 08:43:02 +0000 (09:43 +0100)
21 files changed:
server/sonar-web/src/main/js/apps/component-measures/__tests__/utils-test.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
server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.tsx
server/sonar-web/src/main/js/apps/component-measures/components/MeasureContentContainer.tsx
server/sonar-web/src/main/js/apps/component-measures/components/MeasureContentHeader.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/component-measures/components/MeasureFavoriteContainer.tsx [deleted file]
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/MeasureViewSelect.tsx
server/sonar-web/src/main/js/apps/component-measures/components/PageActions.tsx
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__/__snapshots__/App-test.tsx.snap
server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureViewSelect-test.tsx.snap
server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/PageActions-test.tsx.snap
server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.tsx
server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/FilesView-test.tsx
server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainFacet.tsx
server/sonar-web/src/main/js/apps/component-measures/style.css
server/sonar-web/src/main/js/apps/component-measures/utils.ts
server/sonar-web/src/main/js/helpers/l10n.ts

index a1c7505e495aec6294ab8011190d8d4bbf6ebaaa..cecbc7dd5df1db6b7a930a073f8c8773087f7e03 100644 (file)
@@ -121,16 +121,17 @@ describe('parseQuery', () => {
 
 describe('serializeQuery', () => {
   it('should correctly serialize the query', () => {
-    expect(utils.serializeQuery({ metric: '', selected: '', view: 'list' })).toEqual({});
+    expect(utils.serializeQuery({ metric: '', selected: '', view: 'list' })).toEqual({
+      view: 'list'
+    });
     expect(utils.serializeQuery({ metric: 'foo', selected: 'bar', view: 'tree' })).toEqual({
       metric: 'foo',
-      selected: 'bar',
-      view: 'tree'
+      selected: 'bar'
     });
   });
 
   it('should be memoized', () => {
-    const query = { metric: 'foo', selected: 'bar', view: 'tree' };
+    const query: utils.Query = { metric: 'foo', selected: 'bar', view: 'tree' };
     expect(utils.serializeQuery(query)).toBe(utils.serializeQuery(query));
   });
 });
index 2d233e2e2a3bedc070835e77bc94fc459d6dae6b..51de9019d2fb80060d9bd89951024209d4351031 100644 (file)
@@ -62,7 +62,6 @@ import '../style.css';
 interface Props {
   branchLike?: T.BranchLike;
   component: T.ComponentMeasure;
-  currentUser: T.CurrentUser;
   location: { pathname: string; query: RawQuery };
   fetchMeasures: (
     component: string,
@@ -200,7 +199,6 @@ export default class App extends React.PureComponent<Props, State> {
         <MeasureOverviewContainer
           branchLike={branchLike}
           className="layout-page-main"
-          currentUser={this.props.currentUser}
           domain={query.metric}
           leakPeriod={leakPeriod}
           metrics={metrics}
@@ -234,7 +232,6 @@ export default class App extends React.PureComponent<Props, State> {
       <MeasureContentContainer
         branchLike={branchLike}
         className="layout-page-main"
-        currentUser={this.props.currentUser}
         fetchMeasures={fetchMeasures}
         leakPeriod={leakPeriod}
         metric={metric}
index 9a06175a77550a57182a63c2f0c5bd1cec9ee389..e7ea87f126ee0131be8709999724f4e353d565a6 100644 (file)
@@ -22,7 +22,7 @@ import { connect } from 'react-redux';
 import { withRouter, WithRouterProps } from 'react-router';
 import App from './App';
 import throwGlobalError from '../../../app/utils/throwGlobalError';
-import { getCurrentUser, getMetrics, getMetricsKey } from '../../../store/rootReducer';
+import { getMetrics, getMetricsKey } from '../../../store/rootReducer';
 import { fetchMetrics } from '../../../store/rootActions';
 import { getMeasuresAndMeta } from '../../../api/measures';
 import { getLeakPeriod } from '../../../helpers/periods';
@@ -30,7 +30,6 @@ import { enhanceMeasure } from '../../../components/measure/utils';
 import { getBranchLikeQuery } from '../../../helpers/branches';
 
 interface StateToProps {
-  currentUser: T.CurrentUser;
   metrics: { [metric: string]: T.Metric };
   metricsKey: string[];
 }
@@ -54,7 +53,6 @@ interface OwnProps {
 }
 
 const mapStateToProps = (state: any): StateToProps => ({
-  currentUser: getCurrentUser(state),
   metrics: getMetrics(state),
   metricsKey: getMetricsKey(state)
 });
index c254f67dd653ea0ea9e70de2f1cdd29b6281476b..86982a68e0a8cbff44498d7740acde78ad838b50 100644 (file)
@@ -21,7 +21,7 @@ import * as React from 'react';
 import * as classNames from 'classnames';
 import { InjectedRouter } from 'react-router';
 import Breadcrumbs from './Breadcrumbs';
-import MeasureFavoriteContainer from './MeasureFavoriteContainer';
+import MeasureContentHeader from './MeasureContentHeader';
 import MeasureHeader from './MeasureHeader';
 import MeasureViewSelect from './MeasureViewSelect';
 import MetricNotFound from './MetricNotFound';
@@ -31,19 +31,17 @@ import CodeView from '../drilldown/CodeView';
 import TreeMapView from '../drilldown/TreeMapView';
 import { getComponentTree } from '../../../api/components';
 import { complementary } from '../config/complementary';
-import { enhanceComponent, isFileType, isViewType } from '../utils';
+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 { RequestData } from '../../../helpers/request';
-import { isLoggedIn } from '../../../helpers/users';
 
 interface Props {
   branchLike?: T.BranchLike;
   className?: string;
   component: T.ComponentMeasure;
-  currentUser: T.CurrentUser;
   loading: boolean;
   loadingMore: boolean;
   leakPeriod?: T.Period;
@@ -55,8 +53,8 @@ interface Props {
   secondaryMeasure?: T.MeasureEnhanced;
   updateLoading: (param: { [key: string]: boolean }) => void;
   updateSelected: (component: string) => void;
-  updateView: (view: string) => void;
-  view: string;
+  updateView: (view: View) => void;
+  view: View;
 }
 
 interface State {
@@ -64,7 +62,6 @@ interface State {
   metric?: T.Metric;
   paging?: T.Paging;
   selected?: string;
-  view?: string;
 }
 
 export default class MeasureContent extends React.PureComponent<Props, State> {
@@ -99,37 +96,47 @@ export default class MeasureContent extends React.PureComponent<Props, State> {
     return index !== -1 ? index : undefined;
   };
 
-  getComponentRequestParams = (view: string, metric: T.Metric, options: Object = {}) => {
+  getComponentRequestParams = (view: View, metric: T.Metric, options: Object = {}) => {
     const strategy = view === 'list' ? 'leaves' : 'children';
     const metricKeys = [metric.key];
     const opts: RequestData = {
       ...getBranchLikeQuery(this.props.branchLike),
       additionalFields: 'metrics',
-      metricSortFilter: 'withMeasuresOnly'
+      ps: 500
     };
-    const isDiff = isDiffMetric(metric.key);
-    if (isDiff) {
-      opts.metricPeriodSort = 1;
-    }
-    if (view === 'treemap') {
-      const sizeMetric = isDiff ? 'new_lines' : 'ncloc';
-      metricKeys.push(sizeMetric);
-      opts.metricSort = sizeMetric;
+
+    const setMetricSort = () => {
+      const isDiff = isDiffMetric(metric.key);
       opts.s = isDiff ? 'metricPeriod' : 'metric';
-      opts.asc = false;
-    } else {
+      opts.metricSortFilter = 'withMeasuresOnly';
+      if (isDiff) {
+        opts.metricPeriodSort = 1;
+      }
+    };
+
+    const isDiff = isDiffMetric(metric.key);
+    if (view === 'tree') {
+      metricKeys.push(...(complementary[metric.key] || []));
+      opts.asc = true;
+      opts.s = 'qualifier,name';
+    } else if (view === 'list') {
       metricKeys.push(...(complementary[metric.key] || []));
       opts.asc = metric.direction === 1;
-      opts.ps = 100;
       opts.metricSort = metric.key;
-      opts.s = isDiff ? 'metricPeriod' : 'metric';
+      setMetricSort();
+    } else if (view === 'treemap') {
+      const sizeMetric = isDiff ? 'new_lines' : 'ncloc';
+      metricKeys.push(sizeMetric);
+      opts.asc = false;
+      opts.metricSort = sizeMetric;
+      setMetricSort();
     }
+
     return { metricKeys, opts: { ...opts, ...options }, strategy };
   };
 
   fetchComponents = ({ component, metric, metrics, view }: Props) => {
     if (isFileType(component)) {
-      this.setState({ metric: undefined, view: undefined });
       return;
     }
 
@@ -178,9 +185,9 @@ export default class MeasureContent extends React.PureComponent<Props, State> {
                 ...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,
-              view
+              paging: r.paging
             }));
           }
           this.props.updateLoading({ moreComponents: false });
@@ -228,45 +235,44 @@ export default class MeasureContent extends React.PureComponent<Props, State> {
   }
 
   renderMeasure() {
-    const { metric, view } = this.state;
-    if (metric !== undefined) {
-      if (!view || ['list', 'tree'].includes(view)) {
-        const selectedIdx = this.getSelectedIndex();
-        return (
-          <FilesView
-            branchLike={this.props.branchLike}
-            components={this.state.components}
-            fetchMore={this.fetchMoreComponents}
-            handleOpen={this.onOpenComponent}
-            handleSelect={this.onSelectComponent}
-            loadingMore={this.props.loadingMore}
-            metric={metric}
-            metrics={this.props.metrics}
-            paging={this.state.paging}
-            rootComponent={this.props.rootComponent}
-            selectedIdx={selectedIdx}
-            selectedKey={selectedIdx !== undefined ? this.state.selected : undefined}
-          />
-        );
-      }
-
-      if (view === 'treemap') {
-        return (
-          <TreeMapView
-            branchLike={this.props.branchLike}
-            components={this.state.components}
-            handleSelect={this.onOpenComponent}
-            metric={metric}
-          />
-        );
-      }
+    const { view } = this.props;
+    const { metric } = this.state;
+    if (!metric) {
+      return null;
+    }
+    if (view === 'tree' || view === 'list') {
+      const selectedIdx = this.getSelectedIndex();
+      return (
+        <FilesView
+          branchLike={this.props.branchLike}
+          components={this.state.components}
+          defaultShowBestMeasures={view === 'tree'}
+          fetchMore={this.fetchMoreComponents}
+          handleOpen={this.onOpenComponent}
+          handleSelect={this.onSelectComponent}
+          loadingMore={this.props.loadingMore}
+          metric={metric}
+          metrics={this.props.metrics}
+          paging={this.state.paging}
+          rootComponent={this.props.rootComponent}
+          selectedIdx={selectedIdx}
+          selectedKey={selectedIdx !== undefined ? this.state.selected : undefined}
+        />
+      );
+    } else {
+      return (
+        <TreeMapView
+          branchLike={this.props.branchLike}
+          components={this.state.components}
+          handleSelect={this.onOpenComponent}
+          metric={metric}
+        />
+      );
     }
-
-    return null;
   }
 
   render() {
-    const { branchLike, component, currentUser, measure, metric, rootComponent, view } = this.props;
+    const { branchLike, component, measure, metric, rootComponent, view } = this.props;
     const isFile = isFileType(component);
     const selectedIdx = this.getSelectedIndex();
     return (
@@ -276,38 +282,40 @@ export default class MeasureContent extends React.PureComponent<Props, State> {
         <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">
-              <Breadcrumbs
-                backToFirst={view === 'list'}
-                branchLike={branchLike}
-                className="measure-breadcrumbs spacer-right text-ellipsis"
-                component={component}
-                handleSelect={this.onOpenComponent}
-                rootComponent={rootComponent}
-              />
-              {component.key !== rootComponent.key &&
-                isLoggedIn(currentUser) && (
-                  <MeasureFavoriteContainer
+              <MeasureContentHeader
+                left={
+                  <Breadcrumbs
+                    backToFirst={view === 'list'}
                     branchLike={branchLike}
-                    className="measure-favorite spacer-right"
-                    component={component.key}
+                    className="text-ellipsis flex-1"
+                    component={component}
+                    handleSelect={this.onOpenComponent}
+                    rootComponent={rootComponent}
                   />
-                )}
-              {!isFile && (
-                <MeasureViewSelect
-                  className="measure-view-select"
-                  handleViewChange={this.props.updateView}
-                  metric={metric}
-                  view={view}
-                />
-              )}
-              <PageActions
-                current={
-                  selectedIdx !== undefined && view !== 'treemap' ? selectedIdx + 1 : undefined
                 }
-                isFile={isFile}
-                paging={this.state.paging}
-                totalLoadedComponents={this.state.components.length}
-                view={view}
+                right={
+                  <div className="display-flex-center">
+                    {!isFile && (
+                      <MeasureViewSelect
+                        className="measure-view-select big-spacer-right"
+                        handleViewChange={this.props.updateView}
+                        metric={metric}
+                        view={view}
+                      />
+                    )}
+                    <PageActions
+                      current={
+                        selectedIdx !== undefined && view !== 'treemap'
+                          ? selectedIdx + 1
+                          : undefined
+                      }
+                      isFile={isFile}
+                      paging={this.state.paging}
+                      totalLoadedComponents={this.state.components.length}
+                      view={view}
+                    />
+                  </div>
+                }
               />
             </div>
           </div>
index 2695dc15b324875e66da44cbee63a4bf0a6abfd3..cd6edf18d8be808791cfdfadd137f555d97faa1a 100644 (file)
 import * as React from 'react';
 import { InjectedRouter } from 'react-router';
 import MeasureContent from './MeasureContent';
-import { Query } from '../utils';
+import { Query, View } from '../utils';
 
 interface Props {
   branchLike?: T.BranchLike;
   className?: string;
-  currentUser: T.CurrentUser;
   rootComponent: T.ComponentMeasure;
   fetchMeasures: (
     component: string,
@@ -38,7 +37,7 @@ interface Props {
   router: InjectedRouter;
   selected?: string;
   updateQuery: (query: Partial<Query>) => void;
-  view: string;
+  view: View;
 }
 
 interface LoadingState {
@@ -111,7 +110,9 @@ export default class MeasureContentContainer extends React.PureComponent<Props,
     });
   };
 
-  updateView = (view: string) => this.props.updateQuery({ view });
+  updateView = (view: View) => {
+    this.props.updateQuery({ view });
+  };
 
   render() {
     if (!this.state.component) {
@@ -123,7 +124,6 @@ export default class MeasureContentContainer extends React.PureComponent<Props,
         branchLike={this.props.branchLike}
         className={this.props.className}
         component={this.state.component}
-        currentUser={this.props.currentUser}
         leakPeriod={this.props.leakPeriod}
         loading={this.state.loading.measure || this.state.loading.components}
         loadingMore={this.state.loading.moreComponents}
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContentHeader.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContentHeader.tsx
new file mode 100644 (file)
index 0000000..a930afd
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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';
+
+interface Props {
+  left: React.ReactNode;
+  right: React.ReactNode;
+}
+
+export default function MeasureContentHeader({ left, right }: Props) {
+  return (
+    <div className="measure-content-header">
+      <div className="measure-content-header-left">{left}</div>
+      <div className="measure-content-header-right">{right}</div>
+    </div>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureFavoriteContainer.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureFavoriteContainer.tsx
deleted file mode 100644 (file)
index 3ad335a..0000000
+++ /dev/null
@@ -1,85 +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 Favorite from '../../../components/controls/Favorite';
-import { getComponentForSourceViewer } from '../../../api/components';
-import { isMainBranch } from '../../../helpers/branches';
-
-type FavComponent = Pick<T.SourceViewerFile, 'canMarkAsFavorite' | 'fav' | 'key' | 'q'>;
-
-interface Props {
-  branchLike?: T.BranchLike;
-  className?: string;
-  component: string;
-}
-
-interface State {
-  component?: FavComponent;
-}
-
-export default class MeasureFavoriteContainer extends React.PureComponent<Props, State> {
-  mounted = false;
-  state: State = {};
-
-  componentDidMount() {
-    this.mounted = true;
-    this.fetchComponentFavorite();
-  }
-
-  componentDidUpdate(prevProps: Props) {
-    if (prevProps.component !== this.props.component) {
-      this.fetchComponentFavorite();
-    }
-  }
-
-  componentWillUnmount() {
-    this.mounted = false;
-  }
-
-  fetchComponentFavorite() {
-    getComponentForSourceViewer({ component: this.props.component }).then(
-      component => {
-        if (this.mounted) {
-          this.setState({ component });
-        }
-      },
-      () => {}
-    );
-  }
-
-  render() {
-    const { component } = this.state;
-    if (
-      !component ||
-      !component.canMarkAsFavorite ||
-      (this.props.branchLike && !isMainBranch(this.props.branchLike))
-    ) {
-      return null;
-    }
-    return (
-      <Favorite
-        className={this.props.className}
-        component={component.key}
-        favorite={component.fav || false}
-        qualifier={component.q}
-      />
-    );
-  }
-}
index f41816f6a21135fde6d7f45e33a3a69894922648..e18c68211080a9a6963281616c24b7c4f9ddf262 100644 (file)
@@ -20,7 +20,7 @@
 import * as React from 'react';
 import Breadcrumbs from './Breadcrumbs';
 import LeakPeriodLegend from './LeakPeriodLegend';
-import MeasureFavoriteContainer from './MeasureFavoriteContainer';
+import MeasureContentHeader from './MeasureContentHeader';
 import PageActions from './PageActions';
 import BubbleChart from '../drilldown/BubbleChart';
 import SourceViewer from '../../../components/SourceViewer/SourceViewer';
@@ -33,7 +33,6 @@ interface Props {
   branchLike?: T.BranchLike;
   className?: string;
   component: T.ComponentMeasure;
-  currentUser: T.CurrentUser;
   domain: string;
   leakPeriod?: T.Period;
   loading: boolean;
@@ -133,33 +132,31 @@ export default class MeasureOverview extends React.PureComponent<Props, State> {
   }
 
   render() {
-    const { branchLike, component, currentUser, leakPeriod, rootComponent } = this.props;
-    const isLoggedIn = currentUser && currentUser.isLoggedIn;
+    const { branchLike, component, leakPeriod, rootComponent } = this.props;
     const isFile = isFileType(component);
     return (
       <div className={this.props.className}>
         <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">
-              <Breadcrumbs
-                backToFirst={true}
-                branchLike={branchLike}
-                className="measure-breadcrumbs spacer-right text-ellipsis"
-                component={component}
-                handleSelect={this.props.updateSelected}
-                rootComponent={rootComponent}
-              />
-              {component.key !== rootComponent.key &&
-                isLoggedIn && (
-                  <MeasureFavoriteContainer
-                    className="measure-favorite spacer-right"
-                    component={component.key}
+              <MeasureContentHeader
+                left={
+                  <Breadcrumbs
+                    backToFirst={true}
+                    branchLike={branchLike}
+                    className="text-ellipsis"
+                    component={component}
+                    handleSelect={this.props.updateSelected}
+                    rootComponent={rootComponent}
+                  />
+                }
+                right={
+                  <PageActions
+                    current={this.state.components.length}
+                    isFile={isFile}
+                    paging={this.state.paging}
                   />
-                )}
-              <PageActions
-                current={this.state.components.length}
-                isFile={isFile}
-                paging={this.state.paging}
+                }
               />
             </div>
           </div>
index 3f2dbfbba2eb338b6ab194b9e307d0693c0f5827..200fce078a2a0c20742738ef35ad755d0a281025 100644 (file)
@@ -28,7 +28,6 @@ import { getBranchLikeQuery } from '../../../helpers/branches';
 interface Props {
   branchLike?: T.BranchLike;
   className?: string;
-  currentUser: T.CurrentUser;
   domain: string;
   leakPeriod?: T.Period;
   metrics: { [metric: string]: T.Metric };
@@ -119,7 +118,6 @@ export default class MeasureOverviewContainer extends React.PureComponent<Props,
         branchLike={this.props.branchLike}
         className={this.props.className}
         component={this.state.component}
-        currentUser={this.props.currentUser}
         domain={this.props.domain}
         leakPeriod={this.props.leakPeriod}
         loading={this.state.loading.component || this.state.loading.bubbles}
index ec8730eb5acb56ef88bf864560ce42ddabf468d6..236c1217645b13572ed5a6249d38d31b019c34b1 100644 (file)
@@ -22,27 +22,20 @@ import ListIcon from '../../../components/icons-components/ListIcon';
 import TreeIcon from '../../../components/icons-components/TreeIcon';
 import TreemapIcon from '../../../components/icons-components/TreemapIcon';
 import Select from '../../../components/controls/Select';
-import { hasList, hasTree, hasTreemap } from '../utils';
+import { hasList, hasTree, hasTreemap, View } from '../utils';
 import { translate } from '../../../helpers/l10n';
 
 interface Props {
   className?: string;
   metric: T.Metric;
-  handleViewChange: (view: string) => void;
-  view: string;
+  handleViewChange: (view: View) => void;
+  view: View;
 }
 
 export default class MeasureViewSelect extends React.PureComponent<Props> {
   getOptions = () => {
     const { metric } = this.props;
     const options = [];
-    if (hasList(metric.key)) {
-      options.push({
-        icon: <ListIcon />,
-        label: translate('component_measures.tab.list'),
-        value: 'list'
-      });
-    }
     if (hasTree(metric.key)) {
       options.push({
         icon: <TreeIcon />,
@@ -50,6 +43,13 @@ export default class MeasureViewSelect extends React.PureComponent<Props> {
         value: 'tree'
       });
     }
+    if (hasList(metric.key)) {
+      options.push({
+        icon: <ListIcon />,
+        label: translate('component_measures.tab.list'),
+        value: 'list'
+      });
+    }
     if (hasTreemap(metric.key, metric.type)) {
       options.push({
         icon: <TreemapIcon />,
@@ -61,7 +61,7 @@ export default class MeasureViewSelect extends React.PureComponent<Props> {
   };
 
   handleChange = (option: { value: string }) => {
-    return this.props.handleViewChange(option.value);
+    return this.props.handleViewChange(option.value as View);
   };
 
   renderOption = (option: { icon: JSX.Element; label: string }) => {
index e2c57b912697aecaf2c6d1fb0041802dcddb3f55..32dc2706fe7543c0b9bb25ff448b876bb032007f 100644 (file)
 import * as React from 'react';
 import FilesCounter from './FilesCounter';
 import { translate } from '../../../helpers/l10n';
+import { View } from '../utils';
 
 interface Props {
   current?: number;
   isFile?: boolean;
   paging?: T.Paging;
   totalLoadedComponents?: number;
-  view?: string;
+  view?: View;
 }
 
 export default function PageActions(props: Props) {
   const { isFile, paging, totalLoadedComponents } = props;
   const showShortcuts = props.view && ['list', 'tree'].includes(props.view);
   return (
-    <div className="pull-right">
+    <div className="display-flex-center">
       {!isFile && showShortcuts && renderShortcuts()}
       {isFile && paging && renderFileShortcuts()}
-      <div className="measure-details-page-actions">
+      <div className="measure-details-page-actions nowrap">
         {paging != null && (
           <FilesCounter
             className="spacer-left"
@@ -51,7 +52,7 @@ export default function PageActions(props: Props) {
 
 function renderShortcuts() {
   return (
-    <span className="note big-spacer-right">
+    <span className="note big-spacer-right nowrap">
       <span className="big-spacer-right">
         <span className="shortcut-button little-spacer-right">↑</span>
         <span className="shortcut-button little-spacer-right">↓</span>
@@ -69,7 +70,7 @@ function renderShortcuts() {
 
 function renderFileShortcuts() {
   return (
-    <span className="note spacer-right">
+    <span className="note spacer-right nowrap">
       <span>
         <span className="shortcut-button little-spacer-right">j</span>
         <span className="shortcut-button little-spacer-right">k</span>
index 939481b748067f694ecba70aaf1e97be73de1880..de3394ecc88051bee3b336dd79cb5677cbd10833 100644 (file)
@@ -46,7 +46,6 @@ const METRICS = {
 const PROPS: App['props'] = {
   branchLike: { isMain: true, name: 'master' },
   component: COMPONENT,
-  currentUser: { isLoggedIn: false },
   location: { pathname: '/component_measures', query: { metric: 'coverage' } },
   fetchMeasures: jest.fn().mockResolvedValue({
     component: COMPONENT,
index df50df204271d7ab6b52d0995593d7e548b48cf3..56e8fe4c14f5f81b6d78cc4397e66fc931c2d937 100644 (file)
@@ -81,11 +81,6 @@ exports[`should render correctly 1`] = `
         }
       }
       className="layout-page-main"
-      currentUser={
-        Object {
-          "isLoggedIn": false,
-        }
-      }
       fetchMeasures={
         [MockFunction] {
           "calls": Array [
@@ -166,7 +161,7 @@ exports[`should render correctly 1`] = `
       }
       selected=""
       updateQuery={[Function]}
-      view="list"
+      view="tree"
     />
   </div>
 </div>
index 43cd04e06442362d8b57eb6f9f981845ac232bce..bc440003070a0126ad6e84e238b1f4fa1b687884 100644 (file)
@@ -8,16 +8,16 @@ exports[`should display correctly with treemap option 1`] = `
   optionRenderer={[Function]}
   options={
     Array [
-      Object {
-        "icon": <ListIcon />,
-        "label": "component_measures.tab.list",
-        "value": "list",
-      },
       Object {
         "icon": <TreeIcon />,
         "label": "component_measures.tab.tree",
         "value": "tree",
       },
+      Object {
+        "icon": <ListIcon />,
+        "label": "component_measures.tab.list",
+        "value": "list",
+      },
       Object {
         "icon": <TreemapIcon />,
         "label": "component_measures.tab.treemap",
index 9849310b4c26d057ac3350966ef962a6be4720c5..002f43d4c7e6b4cffc042d47f3d6b0f2cc65b624 100644 (file)
@@ -2,20 +2,20 @@
 
 exports[`should display correctly for a file 1`] = `
 <div
-  className="pull-right"
+  className="display-flex-center"
 >
   <div
-    className="measure-details-page-actions"
+    className="measure-details-page-actions nowrap"
   />
 </div>
 `;
 
 exports[`should display correctly for a file 2`] = `
 <div
-  className="pull-right"
+  className="display-flex-center"
 >
   <span
-    className="note spacer-right"
+    className="note spacer-right nowrap"
   >
     <span>
       <span
@@ -32,7 +32,7 @@ exports[`should display correctly for a file 2`] = `
     </span>
   </span>
   <div
-    className="measure-details-page-actions"
+    className="measure-details-page-actions nowrap"
   >
     <FilesCounter
       className="spacer-left"
@@ -44,10 +44,10 @@ exports[`should display correctly for a file 2`] = `
 
 exports[`should display correctly for a project 1`] = `
 <div
-  className="pull-right"
+  className="display-flex-center"
 >
   <span
-    className="note big-spacer-right"
+    className="note big-spacer-right nowrap"
   >
     <span
       className="big-spacer-right"
@@ -79,17 +79,17 @@ exports[`should display correctly for a project 1`] = `
     </span>
   </span>
   <div
-    className="measure-details-page-actions"
+    className="measure-details-page-actions nowrap"
   />
 </div>
 `;
 
 exports[`should display the total of files 1`] = `
 <div
-  className="pull-right"
+  className="display-flex-center"
 >
   <div
-    className="measure-details-page-actions"
+    className="measure-details-page-actions nowrap"
   >
     <FilesCounter
       className="spacer-left"
@@ -102,10 +102,10 @@ exports[`should display the total of files 1`] = `
 
 exports[`should display the total of files 2`] = `
 <div
-  className="pull-right"
+  className="display-flex-center"
 >
   <span
-    className="note spacer-right"
+    className="note spacer-right nowrap"
   >
     <span>
       <span
@@ -122,7 +122,7 @@ exports[`should display the total of files 2`] = `
     </span>
   </span>
   <div
-    className="measure-details-page-actions"
+    className="measure-details-page-actions nowrap"
   >
     <FilesCounter
       className="spacer-left"
@@ -135,10 +135,10 @@ exports[`should display the total of files 2`] = `
 
 exports[`should not display shortcuts for treemap 1`] = `
 <div
-  className="pull-right"
+  className="display-flex-center"
 >
   <div
-    className="measure-details-page-actions"
+    className="measure-details-page-actions nowrap"
   />
 </div>
 `;
index e9621984a296e33d1f89af98b4c0aad5fc3f71a3..d4f364f6f34662625522879c99cd846a88a7e297 100644 (file)
@@ -31,6 +31,7 @@ import { Alert } from '../../../components/ui/Alert';
 interface Props {
   branchLike?: T.BranchLike;
   components: T.ComponentMeasureEnhanced[];
+  defaultShowBestMeasures: boolean;
   fetchMore: () => void;
   handleSelect: (component: string) => void;
   handleOpen: (component: string) => void;
@@ -47,12 +48,12 @@ interface State {
   showBestMeasures: boolean;
 }
 
-export default class ListView extends React.PureComponent<Props, State> {
+export default class FilesView extends React.PureComponent<Props, State> {
   listContainer?: HTMLElement | null;
 
   constructor(props: Props) {
     super(props);
-    this.state = { showBestMeasures: false };
+    this.state = { showBestMeasures: props.defaultShowBestMeasures };
     this.selectNext = throttle(this.selectNext, 100);
     this.selectPrevious = throttle(this.selectPrevious, 100);
   }
@@ -69,7 +70,7 @@ export default class ListView extends React.PureComponent<Props, State> {
       this.scrollToElement();
     }
     if (prevProps.metric.key !== this.props.metric.key) {
-      this.setState({ showBestMeasures: false });
+      this.setState({ showBestMeasures: this.props.defaultShowBestMeasures });
     }
   }
 
index 8001e681cbd64bc56c9e7ed0c6329272b0db5dbb..425024d3e864ce525a419277be498f4bb9325957 100644 (file)
@@ -58,6 +58,7 @@ function getWrapper(props = {}) {
   return shallow(
     <FilesView
       components={COMPONENTS}
+      defaultShowBestMeasures={false}
       fetchMore={jest.fn()}
       handleOpen={jest.fn()}
       handleSelect={jest.fn()}
index 2da615d12e7f79b13efce28033c595b706307537..c81765cde2fc696b1aa4e69b8f22fb9a65c74438 100644 (file)
@@ -35,7 +35,8 @@ import {
   getLocalizedCategoryMetricName,
   getLocalizedMetricDomain,
   getLocalizedMetricName,
-  translate
+  translate,
+  hasMessage
 } from '../../../helpers/l10n';
 
 interface Props {
@@ -148,12 +149,12 @@ export default class DomainFacet extends React.PureComponent<Props> {
 
   render() {
     const { domain } = this.props;
-    const helper = `component_measures.domain_facets.${domain.name}.help`;
-    const translatedHelper = translate(helper);
+    const helperMessageKey = `component_measures.domain_facets.${domain.name}.help`;
+    const helper = hasMessage(helperMessageKey) ? translate(helperMessageKey) : undefined;
     return (
       <FacetBox property={domain.name}>
         <FacetHeader
-          helper={helper !== translatedHelper ? translatedHelper : undefined}
+          helper={helper}
           name={getLocalizedMetricDomain(domain.name)}
           onClick={this.handleHeaderClick}
           open={this.props.open}
index e6689fb2877d8cebda0229f23fd1acbfe14052cd..853316d50e198c98953903076c25105f47d92852 100644 (file)
   border-top-right-radius: 4px;
 }
 
-.measure-breadcrumbs {
-  display: inline-block;
-  max-width: 60%;
-  vertical-align: middle;
+.measure-content-header {
+  display: flex;
+  align-items: center;
+}
+
+.measure-content-header-left {
+  flex: 1;
+  min-width: 0;
+  white-space: nowrap;
+}
+
+.measure-content-header-right {
+  margin-left: calc(2 * var(--gridSize));
+  white-space: nowrap;
 }
 
 .measure-favorite svg {
index 988897e731ad7d479e5c530ffc96e24401622c30..49b0b7a677555fca484d5d2a9ef2462e177f26cb 100644 (file)
@@ -26,8 +26,10 @@ import { cleanQuery, parseAsString, RawQuery, serializeString } from '../../help
 import { isLongLivingBranch, isMainBranch } from '../../helpers/branches';
 import { getDisplayMetrics } from '../../helpers/measures';
 
+export type View = 'list' | 'tree' | 'treemap';
+
 export const PROJECT_OVERVEW = 'project_overview';
-export const DEFAULT_VIEW = 'list';
+export const DEFAULT_VIEW: View = 'tree';
 export const DEFAULT_METRIC = PROJECT_OVERVEW;
 export const KNOWN_DOMAINS = [
   'Releasability',
@@ -122,7 +124,7 @@ export const groupByDomains = memoize((measures: T.MeasureEnhanced[]) => {
   ]);
 });
 
-export function getDefaultView(metric: string): string {
+export function getDefaultView(metric: string): View {
   if (!hasList(metric)) {
     return 'tree';
   }
@@ -196,35 +198,37 @@ export function isProjectOverview(metric: string) {
   return metric === PROJECT_OVERVEW;
 }
 
-const parseView = (metric: string, rawView?: string) => {
-  const view = parseAsString(rawView) || DEFAULT_VIEW;
+function parseView(metric: string, rawView?: string): View {
+  const view = (parseAsString(rawView) || DEFAULT_VIEW) as View;
   if (!hasTree(metric)) {
     return 'list';
   } else if (view === 'list' && !hasList(metric)) {
     return 'tree';
   }
   return view;
-};
+}
 
 export interface Query {
   metric: string;
   selected?: string;
-  view: string;
+  view: View;
 }
 
-export const parseQuery = memoize((urlQuery: RawQuery) => {
-  const metric = parseAsString(urlQuery['metric']) || DEFAULT_METRIC;
-  return {
-    metric,
-    selected: parseAsString(urlQuery['selected']),
-    view: parseView(metric, urlQuery['view'])
-  };
-});
+export const parseQuery = memoize(
+  (urlQuery: RawQuery): Query => {
+    const metric = parseAsString(urlQuery['metric']) || DEFAULT_METRIC;
+    return {
+      metric,
+      selected: parseAsString(urlQuery['selected']),
+      view: parseView(metric, urlQuery['view'])
+    };
+  }
+);
 
 export const serializeQuery = memoize((query: Query) => {
   return cleanQuery({
-    metric: query.metric === DEFAULT_METRIC ? null : serializeString(query.metric),
+    metric: query.metric === DEFAULT_METRIC ? undefined : serializeString(query.metric),
     selected: serializeString(query.selected),
-    view: query.view === DEFAULT_VIEW ? null : serializeString(query.view)
+    view: query.view === DEFAULT_VIEW ? undefined : serializeString(query.view)
   });
 });
index 4da10579dd12d24b5d7e7e293b39d31374742d66..6e13e95de9128b261c2e63c83c6af7930f992339 100644 (file)
@@ -147,12 +147,6 @@ export function installGlobal() {
   (window as any).requestMessages = requestMessages;
 }
 
-export function getLocalizedDashboardName(baseName: string) {
-  const l10nKey = `dashboard.${baseName}.name`;
-  const l10nLabel = translate(l10nKey);
-  return l10nLabel !== l10nKey ? l10nLabel : baseName;
-}
-
 export function getLocalizedMetricName(
   metric: { key: string; name?: string },
   short?: boolean
@@ -160,24 +154,21 @@ export function getLocalizedMetricName(
   const bundleKey = `metric.${metric.key}.${short ? 'short_name' : 'name'}`;
   if (hasMessage(bundleKey)) {
     return translate(bundleKey);
+  } else if (short) {
+    return getLocalizedMetricName(metric);
   } else {
-    if (short) {
-      return getLocalizedMetricName(metric);
-    }
     return metric.name || metric.key;
   }
 }
 
 export function getLocalizedCategoryMetricName(metric: { key: string; name?: string }) {
   const bundleKey = `metric.${metric.key}.extra_short_name`;
-  const fromBundle = translate(bundleKey);
-  return fromBundle === bundleKey ? getLocalizedMetricName(metric, true) : fromBundle;
+  return hasMessage(bundleKey) ? translate(bundleKey) : getLocalizedMetricName(metric, true);
 }
 
 export function getLocalizedMetricDomain(domainName: string) {
   const bundleKey = `metric_domain.${domainName}`;
-  const fromBundle = translate(bundleKey);
-  return fromBundle !== bundleKey ? fromBundle : domainName;
+  return hasMessage(bundleKey) ? translate(bundleKey) : domainName;
 }
 
 export function getCurrentLocale() {