]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-11159 SONAR-11163 Update Code page for pull requests and short living branches...
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>
Wed, 22 Aug 2018 13:19:20 +0000 (15:19 +0200)
committersonartech <sonartech@sonarsource.com>
Wed, 19 Sep 2018 08:51:41 +0000 (10:51 +0200)
22 files changed:
server/sonar-web/src/main/js/apps/code/__tests__/buckets-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/code/bucket.ts
server/sonar-web/src/main/js/apps/code/components/App.tsx
server/sonar-web/src/main/js/apps/code/components/Breadcrumbs.tsx
server/sonar-web/src/main/js/apps/code/components/Component.tsx
server/sonar-web/src/main/js/apps/code/components/ComponentLink.tsx
server/sonar-web/src/main/js/apps/code/components/ComponentMeasure.tsx
server/sonar-web/src/main/js/apps/code/components/ComponentName.tsx
server/sonar-web/src/main/js/apps/code/components/ComponentPin.tsx
server/sonar-web/src/main/js/apps/code/components/Components.tsx
server/sonar-web/src/main/js/apps/code/components/ComponentsHeader.tsx
server/sonar-web/src/main/js/apps/code/components/Search.tsx
server/sonar-web/src/main/js/apps/code/components/__tests__/App-test.tsx
server/sonar-web/src/main/js/apps/code/components/__tests__/ComponentMeasure-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/code/components/__tests__/Components-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/code/components/__tests__/ComponentsHeader-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/ComponentMeasure-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/Components-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/ComponentsHeader-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/code/components/__tests__/buckets-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/code/types.ts [deleted file]
server/sonar-web/src/main/js/apps/code/utils.ts

diff --git a/server/sonar-web/src/main/js/apps/code/__tests__/buckets-test.tsx b/server/sonar-web/src/main/js/apps/code/__tests__/buckets-test.tsx
new file mode 100644 (file)
index 0000000..5eeb0c8
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * 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 { addComponent, getComponent, addComponentChildren, getComponentChildren } from '../bucket';
+import { ComponentMeasure } from '../../../app/types';
+
+const component: ComponentMeasure = { key: 'frodo', name: 'frodo', qualifier: 'frodo' };
+
+const componentKey: string = 'foo';
+const childrenA: ComponentMeasure[] = [
+  { key: 'foo', name: 'foo', qualifier: 'foo' },
+  { key: 'bar', name: 'bar', qualifier: 'bar' }
+];
+const childrenB: ComponentMeasure[] = [
+  { key: 'bart', name: 'bart', qualifier: 'bart' },
+  { key: 'simpson', name: 'simpson', qualifier: 'simpson' }
+];
+
+it('should have empty bucket at start', () => {
+  expect(getComponent(component.key)).toBeUndefined();
+});
+
+it('should be able to store components in a bucket', () => {
+  addComponent(component);
+  expect(getComponent(component.key)).toEqual(component);
+});
+
+it('should have empty children bucket at start', () => {
+  expect(getComponentChildren(componentKey)).toBeUndefined();
+});
+
+it('should be able to store children components in a bucket', () => {
+  addComponentChildren(componentKey, childrenA, childrenA.length, 1);
+  expect(getComponentChildren(componentKey).children).toEqual(childrenA);
+});
+
+it('should append new children components at the end of the bucket', () => {
+  addComponentChildren(componentKey, childrenB, 4, 2);
+  const finalBucket = getComponentChildren(componentKey);
+  expect(finalBucket.children).toEqual([...childrenA, ...childrenB]);
+  expect(finalBucket.total).toBe(4);
+  expect(finalBucket.page).toBe(2);
+});
index dd172ca8eef0d9f3d3d9edcf7e5b84d9fc9c34fc..3e1600fbde7dffd927640acd182a8bdeee2b5318 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { Breadcrumb, Component } from './types';
+import { ComponentMeasure, Breadcrumb } from '../../app/types';
 
-let bucket: { [key: string]: Component } = {};
+let bucket: { [key: string]: ComponentMeasure } = {};
 let childrenBucket: {
   [key: string]: {
-    children: Component[];
+    children: ComponentMeasure[];
     page: number;
     total: number;
   };
 } = {};
 let breadcrumbsBucket: { [key: string]: Breadcrumb[] } = {};
 
-export function addComponent(component: Component): void {
+export function addComponent(component: ComponentMeasure): void {
   bucket[component.key] = component;
 }
 
-export function getComponent(componentKey: string): Component {
+export function getComponent(componentKey: string): ComponentMeasure {
   return bucket[componentKey];
 }
 
 export function addComponentChildren(
   componentKey: string,
-  children: Component[],
+  children: ComponentMeasure[],
   total: number,
   page: number
 ): void {
@@ -53,7 +53,7 @@ export function addComponentChildren(
 export function getComponentChildren(
   componentKey: string
 ): {
-  children: Component[];
+  children: ComponentMeasure[];
   page: number;
   total: number;
 } {
index f7a7354b0e6cd22817af38359362a4445c372439..e6579501d88e55952c74c9155a5c01b16b1c1963 100644 (file)
  * 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 classNames from 'classnames';
 import * as React from 'react';
+import * as classNames from 'classnames';
+import { connect } from 'react-redux';
 import Helmet from 'react-helmet';
 import Components from './Components';
 import Breadcrumbs from './Breadcrumbs';
 import Search from './Search';
 import { addComponent, addComponentBreadcrumbs, clearBucket } from '../bucket';
-import { Component as CodeComponent } from '../types';
 import { retrieveComponentChildren, retrieveComponent, loadMoreChildren } from '../utils';
-import { Component, BranchLike } from '../../../app/types';
+import { Breadcrumb, Component, ComponentMeasure, BranchLike, Metric } from '../../../app/types';
 import ListFooter from '../../../components/controls/ListFooter';
 import SourceViewer from '../../../components/SourceViewer/SourceViewer';
 import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
+import { fetchMetrics } from '../../../store/rootActions';
+import { getMetrics } from '../../../store/rootReducer';
 import { isSameBranchLike } from '../../../helpers/branches';
 import { translate } from '../../../helpers/l10n';
-import { parseError } from '../../../helpers/request';
 import '../code.css';
 
-interface Props {
+interface StateToProps {
+  metrics: { [metric: string]: Metric };
+}
+
+interface DispatchToProps {
+  fetchMetrics: () => void;
+}
+
+interface OwnProps {
   branchLike?: BranchLike;
   component: Component;
   location: { query: { [x: string]: string } };
 }
 
+type Props = StateToProps & DispatchToProps & OwnProps;
+
 interface State {
-  baseComponent?: CodeComponent;
-  breadcrumbs: Array<CodeComponent>;
-  components?: Array<CodeComponent>;
-  error?: string;
+  baseComponent?: ComponentMeasure;
+  breadcrumbs: Breadcrumb[];
+  components?: ComponentMeasure[];
   loading: boolean;
   page: number;
-  searchResults?: Array<CodeComponent>;
-  sourceViewer?: CodeComponent;
+  searchResults?: ComponentMeasure[];
+  sourceViewer?: ComponentMeasure;
   total: number;
 }
 
-export default class App extends React.PureComponent<Props, State> {
+export class App extends React.PureComponent<Props, State> {
   mounted = false;
   state: State = {
     loading: true,
@@ -64,6 +74,7 @@ export default class App extends React.PureComponent<Props, State> {
 
   componentDidMount() {
     this.mounted = true;
+    this.props.fetchMetrics();
     this.handleComponentChange();
   }
 
@@ -90,28 +101,19 @@ export default class App extends React.PureComponent<Props, State> {
     addComponentBreadcrumbs(component.key, component.breadcrumbs);
 
     this.setState({ loading: true });
-    const isPortfolio = ['VW', 'SVW'].includes(component.qualifier);
-    retrieveComponentChildren(component.key, isPortfolio, branchLike)
-      .then(() => {
-        addComponent(component);
-        if (this.mounted) {
-          this.handleUpdate();
-        }
-      })
-      .catch(e => {
-        if (this.mounted) {
-          this.setState({ loading: false });
-          parseError(e).then(this.handleError);
-        }
-      });
+    retrieveComponentChildren(component.key, component.qualifier, branchLike).then(() => {
+      addComponent(component);
+      if (this.mounted) {
+        this.handleUpdate();
+      }
+    }, this.stopLoading);
   }
 
   loadComponent(componentKey: string) {
     this.setState({ loading: true });
 
-    const isPortfolio = ['VW', 'SVW'].includes(this.props.component.qualifier);
-    retrieveComponent(componentKey, isPortfolio, this.props.branchLike)
-      .then(r => {
+    retrieveComponent(componentKey, this.props.component.qualifier, this.props.branchLike).then(
+      r => {
         if (this.mounted) {
           if (['FIL', 'UTS'].includes(r.component.qualifier)) {
             this.setState({
@@ -133,13 +135,9 @@ export default class App extends React.PureComponent<Props, State> {
             });
           }
         }
-      })
-      .catch(e => {
-        if (this.mounted) {
-          this.setState({ loading: false });
-          parseError(e).then(this.handleError);
-        }
-      });
+      },
+      this.stopLoading
+    );
   }
 
   handleUpdate() {
@@ -155,42 +153,31 @@ export default class App extends React.PureComponent<Props, State> {
     if (!baseComponent || !components) {
       return;
     }
-    const isPortfolio = ['VW', 'SVW'].includes(this.props.component.qualifier);
-    loadMoreChildren(baseComponent.key, page + 1, isPortfolio, this.props.branchLike)
-      .then(r => {
-        if (this.mounted) {
-          this.setState({
-            components: [...components, ...r.components],
-            page: r.page,
-            total: r.total
-          });
-        }
-      })
-      .catch(e => {
-        if (this.mounted) {
-          this.setState({ loading: false });
-          parseError(e).then(this.handleError);
-        }
-      });
+    loadMoreChildren(
+      baseComponent.key,
+      page + 1,
+      this.props.component.qualifier,
+      this.props.branchLike
+    ).then(r => {
+      if (this.mounted) {
+        this.setState({
+          components: [...components, ...r.components],
+          page: r.page,
+          total: r.total
+        });
+      }
+    }, this.stopLoading);
   };
 
-  handleError = (error: string) => {
+  stopLoading = () => {
     if (this.mounted) {
-      this.setState({ error });
+      this.setState({ loading: false });
     }
   };
 
   render() {
     const { branchLike, component, location } = this.props;
-    const {
-      loading,
-      error,
-      baseComponent,
-      components,
-      breadcrumbs,
-      total,
-      sourceViewer
-    } = this.state;
+    const { loading, baseComponent, components, breadcrumbs, total, sourceViewer } = this.state;
     const shouldShowBreadcrumbs = breadcrumbs.length > 1;
 
     const componentsClassName = classNames('boxed-group', 'boxed-group-inner', 'spacer-top', {
@@ -207,14 +194,7 @@ export default class App extends React.PureComponent<Props, State> {
         <Suggestions suggestions="code" />
         <Helmet title={sourceViewer !== undefined ? sourceViewer.name : defaultTitle} />
 
-        {error && <div className="alert alert-danger">{error}</div>}
-
-        <Search
-          branchLike={branchLike}
-          component={component}
-          location={location}
-          onError={this.handleError}
-        />
+        <Search branchLike={branchLike} component={component} location={location} />
 
         <div className="code-components">
           {shouldShowBreadcrumbs && (
@@ -232,6 +212,7 @@ export default class App extends React.PureComponent<Props, State> {
                   baseComponent={baseComponent}
                   branchLike={branchLike}
                   components={components}
+                  metrics={this.props.metrics}
                   rootComponent={component}
                 />
               </div>
@@ -252,3 +233,14 @@ export default class App extends React.PureComponent<Props, State> {
     );
   }
 }
+
+const mapStateToProps = (state: any): StateToProps => ({
+  metrics: getMetrics(state)
+});
+
+const mapDispatchToProps: DispatchToProps = { fetchMetrics };
+
+export default connect<StateToProps, DispatchToProps, Props>(
+  mapStateToProps,
+  mapDispatchToProps
+)(App);
index 7ea212cd7ee23d325c414ec32061fe6df247a529..c1fca8d200bbb697f924db5d5b6c3d89459afe48 100644 (file)
  */
 import * as React from 'react';
 import ComponentName from './ComponentName';
-import { Component } from '../types';
-import { BranchLike } from '../../../app/types';
+import { BranchLike, Breadcrumb, ComponentMeasure } from '../../../app/types';
 
 interface Props {
   branchLike?: BranchLike;
-  breadcrumbs: Component[];
-  rootComponent: Component;
+  breadcrumbs: Breadcrumb[];
+  rootComponent: ComponentMeasure;
 }
 
 export default function Breadcrumbs({ branchLike, breadcrumbs, rootComponent }: Props) {
index c12248a826aca667f76d340945c61d0af2cefd01..e62b377faa7ea184287c595c7f1798d5bad3a5be 100644 (file)
@@ -23,9 +23,7 @@ import ComponentName from './ComponentName';
 import ComponentMeasure from './ComponentMeasure';
 import ComponentLink from './ComponentLink';
 import ComponentPin from './ComponentPin';
-import { Component as IComponent } from '../types';
-import { BranchLike } from '../../../app/types';
-import { isShortLivingBranch, isPullRequest } from '../../../helpers/branches';
+import { BranchLike, Metric, ComponentMeasure as IComponentMeasure } from '../../../app/types';
 
 const TOP_OFFSET = 200;
 const BOTTOM_OFFSET = 10;
@@ -33,9 +31,10 @@ const BOTTOM_OFFSET = 10;
 interface Props {
   branchLike?: BranchLike;
   canBrowse?: boolean;
-  component: IComponent;
-  previous?: IComponent;
-  rootComponent: IComponent;
+  component: IComponentMeasure;
+  metrics: Metric[];
+  previous?: IComponentMeasure;
+  rootComponent: IComponentMeasure;
   selected?: boolean;
 }
 
@@ -76,15 +75,13 @@ export default class Component extends React.PureComponent<Props> {
   render() {
     const {
       branchLike,
+      canBrowse = false,
       component,
-      rootComponent,
-      selected = false,
+      metrics,
       previous,
-      canBrowse = false
+      rootComponent,
+      selected = false
     } = this.props;
-    const isPortfolio = ['VW', 'SVW'].includes(rootComponent.qualifier);
-    const isApplication = rootComponent.qualifier === 'APP';
-    const hideCoverageAndDuplicates = isShortLivingBranch(branchLike) || isPullRequest(branchLike);
 
     let componentAction = null;
 
@@ -99,24 +96,6 @@ export default class Component extends React.PureComponent<Props> {
       }
     }
 
-    const columns = isPortfolio
-      ? [
-          { metric: 'releasability_rating', type: 'RATING' },
-          { metric: 'reliability_rating', type: 'RATING' },
-          { metric: 'security_rating', type: 'RATING' },
-          { metric: 'sqale_rating', type: 'RATING' },
-          { metric: 'ncloc', type: 'SHORT_INT' }
-        ]
-      : ([
-          isApplication && { metric: 'alert_status', type: 'LEVEL' },
-          { metric: 'ncloc', type: 'SHORT_INT' },
-          { metric: 'bugs', type: 'SHORT_INT' },
-          { metric: 'vulnerabilities', type: 'SHORT_INT' },
-          { metric: 'code_smells', type: 'SHORT_INT' },
-          !hideCoverageAndDuplicates && { metric: 'coverage', type: 'PERCENT' },
-          !hideCoverageAndDuplicates && { metric: 'duplicated_lines_density', type: 'PERCENT' }
-        ].filter(Boolean) as Array<{ metric: string; type: string }>);
-
     return (
       <tr className={classNames({ selected })} ref={node => (this.node = node)}>
         <td className="thin nowrap">
@@ -132,14 +111,10 @@ export default class Component extends React.PureComponent<Props> {
           />
         </td>
 
-        {columns.map(column => (
-          <td className="thin nowrap text-right" key={column.metric}>
+        {metrics.map(metric => (
+          <td className="thin nowrap text-right" key={metric.key}>
             <div className="code-components-cell">
-              <ComponentMeasure
-                component={component}
-                metricKey={column.metric}
-                metricType={column.type}
-              />
+              <ComponentMeasure component={component} metric={metric} />
             </div>
           </td>
         ))}
index 60927947472bc5051b1f66b702a0e233d8dbeeec..eb6e931c945e0378cec3c95af7da3df5ac7e7411 100644 (file)
  */
 import * as React from 'react';
 import { Link } from 'react-router';
-import { Component } from '../types';
-import { BranchLike } from '../../../app/types';
+import { BranchLike, ComponentMeasure } from '../../../app/types';
 import LinkIcon from '../../../components/icons-components/LinkIcon';
 import { translate } from '../../../helpers/l10n';
 import { getBranchLikeUrl } from '../../../helpers/urls';
 
 interface Props {
   branchLike?: BranchLike;
-  component: Component;
+  component: ComponentMeasure;
 }
 
 export default function ComponentLink({ component, branchLike }: Props) {
index bb3cbe506a20fd8bf4b30830d17208811aa05f93..e33ff7e5d68df36640fbd769670806ae7c55494f 100644 (file)
  */
 import * as React from 'react';
 import Measure from '../../../components/measure/Measure';
-import { Component } from '../types';
+import { Metric, ComponentMeasure as IComponentMeasure } from '../../../app/types';
+import { isDiffMetric } from '../../../helpers/measures';
+import { getLeakValue } from '../../../components/measure/utils';
 
 interface Props {
-  component: Component;
-  metricKey: string;
-  metricType: string;
+  component: IComponentMeasure;
+  metric: Metric;
 }
 
-export default function ComponentMeasure({ component, metricKey, metricType }: Props) {
+export default function ComponentMeasure({ component, metric }: Props) {
   const isProject = component.qualifier === 'TRK';
-  const isReleasability = metricKey === 'releasability_rating';
+  const isReleasability = metric.key === 'releasability_rating';
 
-  const finalMetricKey = isProject && isReleasability ? 'alert_status' : metricKey;
-  const finalMetricType = isProject && isReleasability ? 'LEVEL' : metricType;
+  const finalMetricKey = isProject && isReleasability ? 'alert_status' : metric.key;
+  const finalMetricType = isProject && isReleasability ? 'LEVEL' : metric.type;
 
   const measure =
     Array.isArray(component.measures) &&
@@ -42,5 +43,6 @@ export default function ComponentMeasure({ component, metricKey, metricType }: P
     return <span />;
   }
 
-  return <Measure metricKey={finalMetricKey} metricType={finalMetricType} value={measure.value} />;
+  const value = isDiffMetric(metric.key) ? getLeakValue(measure) : measure.value;
+  return <Measure metricKey={finalMetricKey} metricType={finalMetricType} value={value} />;
 }
index 391c1862c04835f26ac66e3931b9a646c899ead4..e303cffef939c4911a20675281fb955ab1462c77 100644 (file)
 import * as React from 'react';
 import { Link } from 'react-router';
 import Truncated from './Truncated';
-import { Component } from '../types';
 import * as theme from '../../../app/theme';
-import { BranchLike } from '../../../app/types';
+import { BranchLike, ComponentMeasure } from '../../../app/types';
 import QualifierIcon from '../../../components/icons-components/QualifierIcon';
 import { getBranchLikeQuery } from '../../../helpers/branches';
 import LongLivingBranchIcon from '../../../components/icons-components/LongLivingBranchIcon';
 import { translate } from '../../../helpers/l10n';
 
-function getTooltip(component: Component) {
+function getTooltip(component: ComponentMeasure) {
   const isFile = component.qualifier === 'FIL' || component.qualifier === 'UTS';
   if (isFile && component.path) {
     return component.path + '\n\n' + component.key;
@@ -55,9 +54,9 @@ function mostCommitPrefix(strings: string[]) {
 interface Props {
   branchLike?: BranchLike;
   canBrowse?: boolean;
-  component: Component;
-  previous?: Component;
-  rootComponent: Component;
+  component: ComponentMeasure;
+  previous?: ComponentMeasure;
+  rootComponent: ComponentMeasure;
 }
 
 export default function ComponentName(props: Props) {
index 18d77d03f67bd2a019828e660bb640192d8a7f2d..24ef4d656217bd625c112ad9bba002b297a5f357 100644 (file)
  */
 import * as React from 'react';
 import * as PropTypes from 'prop-types';
-import { Component } from '../types';
-import { BranchLike } from '../../../app/types';
+import { BranchLike, ComponentMeasure } from '../../../app/types';
 import PinIcon from '../../../components/icons-components/PinIcon';
 import { WorkspaceContext } from '../../../components/workspace/context';
 import { translate } from '../../../helpers/l10n';
 
 interface Props {
   branchLike?: BranchLike;
-  component: Component;
+  component: ComponentMeasure;
 }
 
 export default class ComponentPin extends React.PureComponent<Props> {
index 59b017c40b662bda281f2ab58c978db90e7e63a9..030bdfd188b7a08cde14847249dbe2766f835146 100644 (file)
@@ -21,24 +21,27 @@ import * as React from 'react';
 import Component from './Component';
 import ComponentsEmpty from './ComponentsEmpty';
 import ComponentsHeader from './ComponentsHeader';
-import { Component as IComponent } from '../types';
-import { BranchLike } from '../../../app/types';
+import { BranchLike, ComponentMeasure, Metric } from '../../../app/types';
+import { getCodeMetrics } from '../utils';
 
 interface Props {
-  baseComponent?: IComponent;
+  baseComponent?: ComponentMeasure;
   branchLike?: BranchLike;
-  components: IComponent[];
-  rootComponent: IComponent;
-  selected?: IComponent;
+  components: ComponentMeasure[];
+  metrics: { [metric: string]: Metric };
+  rootComponent: ComponentMeasure;
+  selected?: ComponentMeasure;
 }
 
 export default function Components(props: Props) {
   const { baseComponent, branchLike, components, rootComponent, selected } = props;
+  const metricKeys = getCodeMetrics(rootComponent.qualifier, branchLike);
+  const metrics = metricKeys.map(metric => props.metrics[metric]).filter(Boolean);
   return (
     <table className="data zebra">
       <ComponentsHeader
         baseComponent={baseComponent}
-        branchLike={branchLike}
+        metrics={metricKeys}
         rootComponent={rootComponent}
       />
       {baseComponent && (
@@ -47,6 +50,7 @@ export default function Components(props: Props) {
             branchLike={branchLike}
             component={baseComponent}
             key={baseComponent.key}
+            metrics={metrics}
             rootComponent={rootComponent}
           />
           <tr className="blank">
@@ -62,6 +66,7 @@ export default function Components(props: Props) {
               canBrowse={true}
               component={component}
               key={component.key}
+              metrics={metrics}
               previous={index > 0 ? list[index - 1] : undefined}
               rootComponent={rootComponent}
               selected={component === selected}
index 3455f7972fc4dc0d32c50345979689d790ce55b3..2aeb89071a9fe5270e075c2534fe09cf3f112c02 100644 (file)
 import * as React from 'react';
 import * as classNames from 'classnames';
 import { translate } from '../../../helpers/l10n';
-import { Component } from '../types';
-import { isShortLivingBranch, isPullRequest } from '../../../helpers/branches';
-import { BranchLike } from '../../../app/types';
+import { ComponentMeasure } from '../../../app/types';
 
 interface Props {
-  branchLike?: BranchLike;
-  baseComponent?: Component;
-  rootComponent: Component;
+  baseComponent?: ComponentMeasure;
+  metrics: string[];
+  rootComponent: ComponentMeasure;
 }
 
-export default function ComponentsHeader({ baseComponent, branchLike, rootComponent }: Props) {
-  const isPortfolio = rootComponent.qualifier === 'VW' || rootComponent.qualifier === 'SVW';
-  const isApplication = rootComponent.qualifier === 'APP';
-  const hideCoverageAndDuplicates = isShortLivingBranch(branchLike) || isPullRequest(branchLike);
+const SHORT_NAME_METRICS = ['duplicated_lines_density'];
 
-  const columns = isPortfolio
-    ? [
-        translate('metric_domain.Releasability'),
-        translate('metric_domain.Reliability'),
-        translate('metric_domain.Security'),
-        translate('metric_domain.Maintainability'),
-        translate('metric', 'ncloc', 'name')
-      ]
-    : ([
-        isApplication && translate('metric.alert_status.name'),
-        translate('metric', 'ncloc', 'name'),
-        translate('metric', 'bugs', 'name'),
-        translate('metric', 'vulnerabilities', 'name'),
-        translate('metric', 'code_smells', 'name'),
-        !hideCoverageAndDuplicates && translate('metric', 'coverage', 'name'),
-        !hideCoverageAndDuplicates && translate('metric', 'duplicated_lines_density', 'short_name')
-      ].filter(Boolean) as string[]);
+export default function ComponentsHeader({ baseComponent, metrics, rootComponent }: Props) {
+  const isPortfolio = ['VW', 'SVW'].includes(rootComponent.qualifier);
+  let columns: string[] = [];
+  if (isPortfolio) {
+    columns = [
+      translate('metric_domain.Releasability'),
+      translate('metric_domain.Reliability'),
+      translate('metric_domain.Security'),
+      translate('metric_domain.Maintainability'),
+      translate('metric', 'ncloc', 'name')
+    ];
+  } else {
+    columns = metrics.map(metric =>
+      translate('metric', metric, SHORT_NAME_METRICS.includes(metric) ? 'short_name' : 'name')
+    );
+  }
 
   return (
     <thead>
       <tr className="code-components-header">
         <th className="thin nowrap">&nbsp;</th>
         <th>&nbsp;</th>
-        {columns.map((column, index) => (
-          <th
-            className={classNames('thin', 'nowrap', 'text-right', {
-              'code-components-cell': index > 0
-            })}
-            key={column}>
-            {baseComponent && column}
-          </th>
-        ))}
+        {baseComponent &&
+          columns.map((column, index) => (
+            <th
+              className={classNames('thin', 'nowrap', 'text-right', {
+                'code-components-cell': index > 0
+              })}
+              key={column}>
+              {column}
+            </th>
+          ))}
       </tr>
     </thead>
   );
index 7cdd3af90acc304d9ed73fa5331cef3ccb262ce3..8129b592c16506fcf3684eb3922603f6a2a23141 100644 (file)
@@ -21,26 +21,23 @@ import * as React from 'react';
 import * as PropTypes from 'prop-types';
 import * as classNames from 'classnames';
 import Components from './Components';
-import { Component } from '../types';
 import { getTree } from '../../../api/components';
-import { BranchLike } from '../../../app/types';
+import { BranchLike, ComponentMeasure } from '../../../app/types';
 import SearchBox from '../../../components/controls/SearchBox';
 import { getBranchLikeQuery } from '../../../helpers/branches';
 import { translate } from '../../../helpers/l10n';
-import { parseError } from '../../../helpers/request';
 import { getProjectUrl } from '../../../helpers/urls';
 
 interface Props {
   branchLike?: BranchLike;
-  component: Component;
+  component: ComponentMeasure;
   location: {};
-  onError: (error: string) => void;
 }
 
 interface State {
   query: string;
   loading: boolean;
-  results?: Component[];
+  results?: ComponentMeasure[];
   selectedIndex?: number;
 }
 
@@ -78,14 +75,14 @@ export default class Search extends React.PureComponent<Props, State> {
 
   handleSelectNext() {
     const { selectedIndex, results } = this.state;
-    if (results != null && selectedIndex != null && selectedIndex < results.length - 1) {
+    if (results && selectedIndex !== undefined && selectedIndex < results.length - 1) {
       this.setState({ selectedIndex: selectedIndex + 1 });
     }
   }
 
   handleSelectPrevious() {
     const { selectedIndex, results } = this.state;
-    if (results != null && selectedIndex != null && selectedIndex > 0) {
+    if (results && selectedIndex !== undefined && selectedIndex > 0) {
       this.setState({ selectedIndex: selectedIndex - 1 });
     }
   }
@@ -93,7 +90,7 @@ export default class Search extends React.PureComponent<Props, State> {
   handleSelectCurrent() {
     const { branchLike, component } = this.props;
     const { results, selectedIndex } = this.state;
-    if (results != null && selectedIndex != null) {
+    if (results && selectedIndex !== undefined) {
       const selected = results[selectedIndex];
 
       if (selected.refKey) {
@@ -127,7 +124,7 @@ export default class Search extends React.PureComponent<Props, State> {
 
   handleSearch = (query: string) => {
     if (this.mounted) {
-      const { branchLike, component, onError } = this.props;
+      const { branchLike, component } = this.props;
       this.setState({ loading: true });
 
       const isPortfolio = ['VW', 'SVW', 'APP'].includes(component.qualifier);
@@ -149,10 +146,9 @@ export default class Search extends React.PureComponent<Props, State> {
             });
           }
         })
-        .catch(e => {
+        .catch(() => {
           if (this.mounted) {
             this.setState({ loading: false });
-            parseError(e).then(onError);
           }
         });
     }
@@ -170,9 +166,9 @@ export default class Search extends React.PureComponent<Props, State> {
   render() {
     const { component } = this.props;
     const { loading, selectedIndex, results } = this.state;
-    const selected = selectedIndex != null && results != null ? results[selectedIndex] : undefined;
+    const selected = selectedIndex !== undefined && results ? results[selectedIndex] : undefined;
     const containerClassName = classNames('code-search', {
-      'code-search-with-results': results != null
+      'code-search-with-results': Boolean(results)
     });
     const isPortfolio = ['VW', 'SVW', 'APP'].includes(component.qualifier);
 
@@ -189,11 +185,12 @@ export default class Search extends React.PureComponent<Props, State> {
         />
         {loading && <i className="spinner spacer-left" />}
 
-        {results != null && (
+        {results && (
           <div className="boxed-group boxed-group-inner spacer-top">
             <Components
               branchLike={this.props.branchLike}
               components={results}
+              metrics={{}}
               rootComponent={component}
               selected={selected}
             />
index 0171b0bca0f2c5f8da9a3a9e7cb2b000f845a423..42b5af7bb2fc109edf20fed9d3ae76d14ae3493b 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+/* eslint-disable camelcase */
 import * as React from 'react';
 import { shallow } from 'enzyme';
-import App from '../App';
+import { App } from '../App';
 import { waitAndUpdate } from '../../../../helpers/testUtils';
 import { retrieveComponent } from '../../utils';
 
@@ -34,6 +35,11 @@ jest.mock('../../utils', () => ({
   retrieveComponentChildren: () => Promise.resolve()
 }));
 
+const METRICS = {
+  coverage: { id: '2', key: 'coverage', type: 'PERCENT', name: 'Coverage', domain: 'Coverage' },
+  new_bugs: { id: '4', key: 'new_bugs', type: 'INT', name: 'New Bugs', domain: 'Reliability' }
+};
+
 beforeEach(() => {
   (retrieveComponent as jest.Mock<any>).mockClear();
 });
@@ -80,7 +86,9 @@ const getWrapper = () => {
         organization: 'foo',
         qualifier: 'FOO'
       }}
+      fetchMetrics={jest.fn()}
       location={{ query: { branch: 'b', id: 'foo', line: '7' } }}
+      metrics={METRICS}
     />
   );
 };
diff --git a/server/sonar-web/src/main/js/apps/code/components/__tests__/ComponentMeasure-test.tsx b/server/sonar-web/src/main/js/apps/code/components/__tests__/ComponentMeasure-test.tsx
new file mode 100644 (file)
index 0000000..c317d15
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * 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';
+import { shallow } from 'enzyme';
+import ComponentMeasure from '../ComponentMeasure';
+
+const METRIC = { id: '1', key: 'coverage', type: 'PERCENT', name: 'Coverage' };
+const LEAK_METRIC = { id: '2', key: 'new_coverage', type: 'PERCENT', name: 'Coverage on New Code' };
+const COMPONENT = { key: 'foo', name: 'Foo', qualifier: 'TRK' };
+const COMPONENT_MEASURE = {
+  ...COMPONENT,
+  measures: [{ value: '3.0', periods: [{ index: 1, value: '10.0' }], metric: METRIC.key }]
+};
+
+it('renders correctly', () => {
+  expect(
+    shallow(<ComponentMeasure component={COMPONENT_MEASURE} metric={METRIC} />)
+  ).toMatchSnapshot();
+});
+
+it('renders correctly for leak values', () => {
+  expect(
+    shallow(
+      <ComponentMeasure
+        component={{
+          ...COMPONENT,
+          measures: [
+            { value: '3.0', periods: [{ index: 1, value: '10.0' }], metric: LEAK_METRIC.key }
+          ]
+        }}
+        metric={LEAK_METRIC}
+      />
+    )
+  ).toMatchSnapshot();
+});
+
+it('renders correctly when no measure found', () => {
+  expect(shallow(<ComponentMeasure component={COMPONENT} metric={METRIC} />)).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/code/components/__tests__/Components-test.tsx b/server/sonar-web/src/main/js/apps/code/components/__tests__/Components-test.tsx
new file mode 100644 (file)
index 0000000..8d0dfbe
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * 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';
+import { shallow } from 'enzyme';
+import Components from '../Components';
+
+const COMPONENT = { key: 'foo', name: 'Foo', qualifier: 'TRK' };
+const PORTFOLIO = { key: 'bar', name: 'Bar', qualifier: 'VW' };
+const METRICS = { coverage: { id: '1', key: 'coverage', type: 'PERCENT', name: 'Coverage' } };
+
+it('renders correctly', () => {
+  expect(
+    shallow(
+      <Components
+        baseComponent={COMPONENT}
+        components={[COMPONENT]}
+        metrics={METRICS}
+        rootComponent={COMPONENT}
+      />
+    )
+  ).toMatchSnapshot();
+});
+
+it('renders correctly for a search', () => {
+  expect(
+    shallow(<Components components={[COMPONENT]} metrics={METRICS} rootComponent={COMPONENT} />)
+  ).toMatchSnapshot();
+});
+
+it('handle no components correctly', () => {
+  expect(
+    shallow(
+      <Components
+        baseComponent={PORTFOLIO}
+        components={[]}
+        metrics={METRICS}
+        rootComponent={PORTFOLIO}
+      />
+    )
+      .find('ComponentsEmpty')
+      .exists()
+  ).toBe(true);
+});
diff --git a/server/sonar-web/src/main/js/apps/code/components/__tests__/ComponentsHeader-test.tsx b/server/sonar-web/src/main/js/apps/code/components/__tests__/ComponentsHeader-test.tsx
new file mode 100644 (file)
index 0000000..4c7ab9f
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * 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';
+import { shallow } from 'enzyme';
+import ComponentsHeader from '../ComponentsHeader';
+
+const COMPONENT = { key: 'foo', name: 'Foo', qualifier: 'TRK' };
+const PORTFOLIO = { key: 'bar', name: 'Bar', qualifier: 'VW' };
+const METRICS = ['foo', 'bar'];
+
+it('renders correctly for projects', () => {
+  expect(
+    shallow(
+      <ComponentsHeader baseComponent={COMPONENT} metrics={METRICS} rootComponent={COMPONENT} />
+    )
+  ).toMatchSnapshot();
+});
+
+it('renders correctly for portfolios', () => {
+  expect(
+    shallow(
+      <ComponentsHeader baseComponent={PORTFOLIO} metrics={METRICS} rootComponent={PORTFOLIO} />
+    )
+  ).toMatchSnapshot();
+});
+
+it('renders correctly for a search', () => {
+  expect(
+    shallow(<ComponentsHeader metrics={METRICS} rootComponent={COMPONENT} />)
+  ).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/ComponentMeasure-test.tsx.snap b/server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/ComponentMeasure-test.tsx.snap
new file mode 100644 (file)
index 0000000..824a3eb
--- /dev/null
@@ -0,0 +1,19 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+<Measure
+  metricKey="coverage"
+  metricType="PERCENT"
+  value="3.0"
+/>
+`;
+
+exports[`renders correctly for leak values 1`] = `
+<Measure
+  metricKey="new_coverage"
+  metricType="PERCENT"
+  value="10.0"
+/>
+`;
+
+exports[`renders correctly when no measure found 1`] = `<span />`;
diff --git a/server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/Components-test.tsx.snap b/server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/Components-test.tsx.snap
new file mode 100644 (file)
index 0000000..5c20c57
--- /dev/null
@@ -0,0 +1,160 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+<table
+  className="data zebra"
+>
+  <ComponentsHeader
+    baseComponent={
+      Object {
+        "key": "foo",
+        "name": "Foo",
+        "qualifier": "TRK",
+      }
+    }
+    metrics={
+      Array [
+        "ncloc",
+        "bugs",
+        "vulnerabilities",
+        "code_smells",
+        "coverage",
+        "duplicated_lines_density",
+      ]
+    }
+    rootComponent={
+      Object {
+        "key": "foo",
+        "name": "Foo",
+        "qualifier": "TRK",
+      }
+    }
+  />
+  <tbody>
+    <Component
+      component={
+        Object {
+          "key": "foo",
+          "name": "Foo",
+          "qualifier": "TRK",
+        }
+      }
+      key="foo"
+      metrics={
+        Array [
+          Object {
+            "id": "1",
+            "key": "coverage",
+            "name": "Coverage",
+            "type": "PERCENT",
+          },
+        ]
+      }
+      rootComponent={
+        Object {
+          "key": "foo",
+          "name": "Foo",
+          "qualifier": "TRK",
+        }
+      }
+    />
+    <tr
+      className="blank"
+    >
+      <td
+        colSpan={8}
+      >
+         
+      </td>
+    </tr>
+  </tbody>
+  <tbody>
+    <Component
+      canBrowse={true}
+      component={
+        Object {
+          "key": "foo",
+          "name": "Foo",
+          "qualifier": "TRK",
+        }
+      }
+      key="foo"
+      metrics={
+        Array [
+          Object {
+            "id": "1",
+            "key": "coverage",
+            "name": "Coverage",
+            "type": "PERCENT",
+          },
+        ]
+      }
+      rootComponent={
+        Object {
+          "key": "foo",
+          "name": "Foo",
+          "qualifier": "TRK",
+        }
+      }
+      selected={false}
+    />
+  </tbody>
+</table>
+`;
+
+exports[`renders correctly for a search 1`] = `
+<table
+  className="data zebra"
+>
+  <ComponentsHeader
+    metrics={
+      Array [
+        "ncloc",
+        "bugs",
+        "vulnerabilities",
+        "code_smells",
+        "coverage",
+        "duplicated_lines_density",
+      ]
+    }
+    rootComponent={
+      Object {
+        "key": "foo",
+        "name": "Foo",
+        "qualifier": "TRK",
+      }
+    }
+  />
+  <tbody>
+    <Component
+      canBrowse={true}
+      component={
+        Object {
+          "key": "foo",
+          "name": "Foo",
+          "qualifier": "TRK",
+        }
+      }
+      key="foo"
+      metrics={
+        Array [
+          Object {
+            "id": "1",
+            "key": "coverage",
+            "name": "Coverage",
+            "type": "PERCENT",
+          },
+        ]
+      }
+      rootComponent={
+        Object {
+          "key": "foo",
+          "name": "Foo",
+          "qualifier": "TRK",
+        }
+      }
+      selected={false}
+    />
+  </tbody>
+</table>
+`;
diff --git a/server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/ComponentsHeader-test.tsx.snap b/server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/ComponentsHeader-test.tsx.snap
new file mode 100644 (file)
index 0000000..0d0a7b3
--- /dev/null
@@ -0,0 +1,94 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly for a search 1`] = `
+<thead>
+  <tr
+    className="code-components-header"
+  >
+    <th
+      className="thin nowrap"
+    >
+       
+    </th>
+    <th>
+       
+    </th>
+  </tr>
+</thead>
+`;
+
+exports[`renders correctly for portfolios 1`] = `
+<thead>
+  <tr
+    className="code-components-header"
+  >
+    <th
+      className="thin nowrap"
+    >
+       
+    </th>
+    <th>
+       
+    </th>
+    <th
+      className="thin nowrap text-right"
+      key="metric_domain.Releasability"
+    >
+      metric_domain.Releasability
+    </th>
+    <th
+      className="thin nowrap text-right code-components-cell"
+      key="metric_domain.Reliability"
+    >
+      metric_domain.Reliability
+    </th>
+    <th
+      className="thin nowrap text-right code-components-cell"
+      key="metric_domain.Security"
+    >
+      metric_domain.Security
+    </th>
+    <th
+      className="thin nowrap text-right code-components-cell"
+      key="metric_domain.Maintainability"
+    >
+      metric_domain.Maintainability
+    </th>
+    <th
+      className="thin nowrap text-right code-components-cell"
+      key="metric.ncloc.name"
+    >
+      metric.ncloc.name
+    </th>
+  </tr>
+</thead>
+`;
+
+exports[`renders correctly for projects 1`] = `
+<thead>
+  <tr
+    className="code-components-header"
+  >
+    <th
+      className="thin nowrap"
+    >
+       
+    </th>
+    <th>
+       
+    </th>
+    <th
+      className="thin nowrap text-right"
+      key="metric.foo.name"
+    >
+      metric.foo.name
+    </th>
+    <th
+      className="thin nowrap text-right code-components-cell"
+      key="metric.bar.name"
+    >
+      metric.bar.name
+    </th>
+  </tr>
+</thead>
+`;
diff --git a/server/sonar-web/src/main/js/apps/code/components/__tests__/buckets-test.tsx b/server/sonar-web/src/main/js/apps/code/components/__tests__/buckets-test.tsx
deleted file mode 100644 (file)
index 9ccf00c..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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 { Component } from '../../types';
-import {
-  addComponent,
-  getComponent,
-  addComponentChildren,
-  getComponentChildren
-} from '../../bucket';
-
-const component: Component = { key: 'frodo', name: 'frodo', qualifier: 'frodo' };
-
-const componentKey: string = 'foo';
-const childrenA: Component[] = [
-  { key: 'foo', name: 'foo', qualifier: 'foo' },
-  { key: 'bar', name: 'bar', qualifier: 'bar' }
-];
-const childrenB: Component[] = [
-  { key: 'bart', name: 'bart', qualifier: 'bart' },
-  { key: 'simpson', name: 'simpson', qualifier: 'simpson' }
-];
-
-it('should have empty bucket at start', () => {
-  expect(getComponent(component.key)).toBeUndefined();
-});
-
-it('should be able to store components in a bucket', () => {
-  addComponent(component);
-  expect(getComponent(component.key)).toEqual(component);
-});
-
-it('should have empty children bucket at start', () => {
-  expect(getComponentChildren(componentKey)).toBeUndefined();
-});
-
-it('should be able to store children components in a bucket', () => {
-  addComponentChildren(componentKey, childrenA, childrenA.length, 1);
-  expect(getComponentChildren(componentKey).children).toEqual(childrenA);
-});
-
-it('should append new children components at the end of the bucket', () => {
-  addComponentChildren(componentKey, childrenB, 4, 2);
-  const finalBucket = getComponentChildren(componentKey);
-  expect(finalBucket.children).toEqual([...childrenA, ...childrenB]);
-  expect(finalBucket.total).toBe(4);
-  expect(finalBucket.page).toBe(2);
-});
diff --git a/server/sonar-web/src/main/js/apps/code/types.ts b/server/sonar-web/src/main/js/apps/code/types.ts
deleted file mode 100644 (file)
index f8fbc5a..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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 { Measure } from '../../app/types';
-
-export interface Component extends Breadcrumb {
-  branch?: string;
-  measures?: Measure[];
-  path?: string;
-  refKey?: string;
-}
-
-export interface Breadcrumb {
-  key: string;
-  name: string;
-  qualifier: string;
-}
index 75ef812c9774f342eb5991deba649c4a59f128a8..cd429f3d6fad2d786d32dfb53a949b7f5920f4dd 100644 (file)
@@ -26,30 +26,37 @@ import {
   addComponentBreadcrumbs,
   getComponentBreadcrumbs
 } from './bucket';
-import { Breadcrumb, Component } from './types';
 import { getChildren, getComponent, getBreadcrumbs } from '../../api/components';
-import { BranchLike } from '../../app/types';
-import { getBranchLikeQuery } from '../../helpers/branches';
+import { BranchLike, ComponentMeasure, Breadcrumb } from '../../app/types';
+import { getBranchLikeQuery, isShortLivingBranch, isPullRequest } from '../../helpers/branches';
 
 const METRICS = [
   'ncloc',
-  'code_smells',
   'bugs',
   'vulnerabilities',
+  'code_smells',
   'coverage',
-  'duplicated_lines_density',
-  'alert_status'
+  'duplicated_lines_density'
 ];
 
+const APPLICATION_METRICS = ['alert_status', ...METRICS];
+
 const PORTFOLIO_METRICS = [
   'releasability_rating',
-  'alert_status',
   'reliability_rating',
   'security_rating',
   'sqale_rating',
   'ncloc'
 ];
 
+const LEAK_METRICS = [
+  'new_lines',
+  'new_bugs',
+  'new_vulnerabilities',
+  'new_code_smells',
+  'new_coverage'
+];
+
 const PAGE_SIZE = 100;
 
 function requestChildren(
@@ -57,7 +64,7 @@ function requestChildren(
   metrics: string[],
   page: number,
   branchLike?: BranchLike
-): Promise<Component[]> {
+): Promise<ComponentMeasure[]> {
   return getChildren(componentKey, metrics, {
     p: page,
     ps: PAGE_SIZE,
@@ -76,12 +83,12 @@ function requestAllChildren(
   componentKey: string,
   metrics: string[],
   branchLike?: BranchLike
-): Promise<Component[]> {
+): Promise<ComponentMeasure[]> {
   return requestChildren(componentKey, metrics, 1, branchLike);
 }
 
 interface Children {
-  components: Component[];
+  components: ComponentMeasure[];
   page: number;
   total: number;
 }
@@ -93,7 +100,7 @@ interface ExpandRootDirFunc {
 function expandRootDir(metrics: string[], branchLike?: BranchLike): ExpandRootDirFunc {
   return function({ components, total, ...other }) {
     const rootDir = components.find(
-      (component: Component) => component.qualifier === 'DIR' && component.name === '/'
+      (component: ComponentMeasure) => component.qualifier === 'DIR' && component.name === '/'
     );
     if (rootDir) {
       return requestAllChildren(rootDir.key, metrics, branchLike).then(rootDirComponents => {
@@ -107,6 +114,10 @@ function expandRootDir(metrics: string[], branchLike?: BranchLike): ExpandRootDi
   };
 }
 
+function showLeakMeasure(branchLike?: BranchLike) {
+  return isShortLivingBranch(branchLike) || isPullRequest(branchLike);
+}
+
 function prepareChildren(r: any): Children {
   return {
     components: r.components,
@@ -115,13 +126,13 @@ function prepareChildren(r: any): Children {
   };
 }
 
-function skipRootDir(breadcrumbs: Component[]) {
+function skipRootDir(breadcrumbs: ComponentMeasure[]) {
   return breadcrumbs.filter(component => {
     return !(component.qualifier === 'DIR' && component.name === '/');
   });
 }
 
-function storeChildrenBase(children: Component[]) {
+function storeChildrenBase(children: ComponentMeasure[]) {
   children.forEach(addComponent);
 }
 
@@ -135,21 +146,26 @@ function storeChildrenBreadcrumbs(parentComponentKey: string, children: Breadcru
   }
 }
 
-function getMetrics(isPortfolio: boolean) {
-  return isPortfolio ? PORTFOLIO_METRICS : METRICS;
+export function getCodeMetrics(qualifier: string, branchLike?: BranchLike) {
+  if (['VW', 'SVW'].includes(qualifier)) {
+    return PORTFOLIO_METRICS;
+  }
+  if (qualifier === 'APP') {
+    return APPLICATION_METRICS;
+  }
+  if (showLeakMeasure(branchLike)) {
+    return LEAK_METRICS;
+  }
+  return METRICS;
 }
 
-function retrieveComponentBase(
-  componentKey: string,
-  isPortfolio: boolean,
-  branchLike?: BranchLike
-) {
+function retrieveComponentBase(componentKey: string, qualifier: string, branchLike?: BranchLike) {
   const existing = getComponentFromBucket(componentKey);
   if (existing) {
     return Promise.resolve(existing);
   }
 
-  const metrics = getMetrics(isPortfolio);
+  const metrics = getCodeMetrics(qualifier, branchLike);
 
   return getComponent({
     componentKey,
@@ -163,9 +179,9 @@ function retrieveComponentBase(
 
 export function retrieveComponentChildren(
   componentKey: string,
-  isPortfolio: boolean,
+  qualifier: string,
   branchLike?: BranchLike
-): Promise<{ components: Component[]; page: number; total: number }> {
+): Promise<{ components: ComponentMeasure[]; page: number; total: number }> {
   const existing = getComponentChildren(componentKey);
   if (existing) {
     return Promise.resolve({
@@ -175,7 +191,7 @@ export function retrieveComponentChildren(
     });
   }
 
-  const metrics = getMetrics(isPortfolio);
+  const metrics = getCodeMetrics(qualifier, branchLike);
 
   return getChildren(componentKey, metrics, {
     ps: PAGE_SIZE,
@@ -211,26 +227,26 @@ function retrieveComponentBreadcrumbs(
 
 export function retrieveComponent(
   componentKey: string,
-  isPortfolio: boolean,
+  qualifier: string,
   branchLike?: BranchLike
 ): Promise<{
-  breadcrumbs: Component[];
-  component: Component;
-  components: Component[];
+  breadcrumbs: Breadcrumb[];
+  component: ComponentMeasure;
+  components: ComponentMeasure[];
   page: number;
   total: number;
 }> {
   return Promise.all([
-    retrieveComponentBase(componentKey, isPortfolio, branchLike),
-    retrieveComponentChildren(componentKey, isPortfolio, branchLike),
+    retrieveComponentBase(componentKey, qualifier, branchLike),
+    retrieveComponentChildren(componentKey, qualifier, branchLike),
     retrieveComponentBreadcrumbs(componentKey, branchLike)
   ]).then(r => {
     return {
+      breadcrumbs: r[2],
       component: r[0],
       components: r[1].components,
-      total: r[1].total,
       page: r[1].page,
-      breadcrumbs: r[2]
+      total: r[1].total
     };
   });
 }
@@ -238,10 +254,10 @@ export function retrieveComponent(
 export function loadMoreChildren(
   componentKey: string,
   page: number,
-  isPortfolio: boolean,
+  qualifier: string,
   branchLike?: BranchLike
 ): Promise<Children> {
-  const metrics = getMetrics(isPortfolio);
+  const metrics = getCodeMetrics(qualifier, branchLike);
 
   return getChildren(componentKey, metrics, {
     ps: PAGE_SIZE,