aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src
diff options
context:
space:
mode:
Diffstat (limited to 'server/sonar-web/src')
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/app.js10
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/AllMeasures.js (renamed from server/sonar-web/src/main/js/apps/component-measures/components/AllMeasuresList.js)57
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/AllMeasuresDomain.js64
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/IconList.js (renamed from server/sonar-web/src/main/js/apps/component-measures/components/ListIcon.js)0
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/IconTree.js (renamed from server/sonar-web/src/main/js/apps/component-measures/components/TreeIcon.js)2
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/IconUp.js (renamed from server/sonar-web/src/main/js/apps/component-measures/components/UpIcon.js)2
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/MeasureDetails.js69
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/MeasureDetailsHeader.js56
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/MeasureDrilldown.js15
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/MeasureDrilldownComponents.js (renamed from server/sonar-web/src/main/js/apps/component-measures/components/ComponentsList.js)14
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/MeasureDrilldownEmpty.js (renamed from server/sonar-web/src/main/js/apps/component-measures/components/NoResults.js)2
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/MeasureDrilldownList.js (renamed from server/sonar-web/src/main/js/apps/component-measures/components/MeasureList.js)40
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/MeasureDrilldownTree.js (renamed from server/sonar-web/src/main/js/apps/component-measures/components/MeasureTree.js)46
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/styles.css53
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/utils.js24
-rw-r--r--server/sonar-web/src/main/js/apps/overview/helpers/periods.js10
-rw-r--r--server/sonar-web/src/main/js/helpers/periods.js46
17 files changed, 303 insertions, 207 deletions
diff --git a/server/sonar-web/src/main/js/apps/component-measures/app.js b/server/sonar-web/src/main/js/apps/component-measures/app.js
index 4077b2f777d..babee01e50d 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/app.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/app.js
@@ -23,10 +23,10 @@ import { Router, Route, IndexRoute, Redirect, IndexRedirect, useRouterHistory }
import { createHistory } from 'history';
import ComponentMeasuresApp from './components/ComponentMeasuresApp';
-import AllMeasuresList from './components/AllMeasuresList';
+import AllMeasuresList from './components/AllMeasures';
import MeasureDetails from './components/MeasureDetails';
-import MeasureTree from './components/MeasureTree';
-import MeasureList from './components/MeasureList';
+import MeasureDrilldownTree from './components/MeasureDrilldownTree';
+import MeasureDrilldownList from './components/MeasureDrilldownList';
import './styles.css';
@@ -53,8 +53,8 @@ window.sonarqube.appStarted.then(options => {
<IndexRoute component={AllMeasuresList}/>
<Route path=":metricKey" component={MeasureDetails}>
<IndexRedirect to="tree"/>
- <Route path="tree" component={MeasureTree}/>
- <Route path="list" component={MeasureList}/>
+ <Route path="tree" component={MeasureDrilldownTree}/>
+ <Route path="list" component={MeasureDrilldownList}/>
</Route>
</Route>
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/AllMeasuresList.js b/server/sonar-web/src/main/js/apps/component-measures/components/AllMeasures.js
index 12a43009b1b..26c6ca254d2 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/AllMeasuresList.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/AllMeasures.js
@@ -19,17 +19,14 @@
*/
import _ from 'underscore';
import React from 'react';
-import { Link } from 'react-router';
import Spinner from './Spinner';
-import { getLeakValue, formatLeak } from '../utils';
+import AllMeasuresDomain from './AllMeasuresDomain';
+import { getLeakValue } from '../utils';
import { getMeasuresAndMeta } from '../../../api/measures';
-import { formatMeasure } from '../../../helpers/measures';
-import { translateWithParameters } from '../../../helpers/l10n';
+import { getLeakPeriodLabel } from '../../../helpers/periods';
-import { getPeriodLabel } from '../../overview/helpers/periods';
-
-export default class ComponentMeasuresApp extends React.Component {
+export default class AllMeasures extends React.Component {
state = {
fetching: true,
measures: []
@@ -86,51 +83,23 @@ export default class ComponentMeasuresApp extends React.Component {
return { name, measures: sortedMeasures };
}), 'name');
- const leakLabel = getPeriodLabel(periods, 1);
+ const leakPeriodLabel = getLeakPeriodLabel(periods);
return (
- <ul className="component-measures-domains">
+ <ul className="measures-domains">
{domains.map((domain, index) => (
- <li key={domain.name}>
- <header className="page-header">
- <h3 className="page-title">{domain.name}</h3>
- {index === 0 && (
- <div className="component-measures-domains-leak-header">
- {translateWithParameters('overview.leak_period_x', leakLabel)}
- </div>
- )}
- </header>
-
- <ul className="component-measures-domain-measures">
- {domain.measures.map(measure => (
- <li key={measure.metric.key}>
- <div className="component-measures-domain-measures-name">
- {measure.metric.name}
- </div>
- <div className="component-measures-domain-measures-value">
- {measure.value != null && (
- <Link to={{ pathname: measure.metric.key, query: { id: component.key } }}>
- {formatMeasure(measure.value, measure.metric.type)}
- </Link>
- )}
- </div>
- <div className="component-measures-domain-measures-value component-measures-leak-cell">
- {measure.leak != null && (
- <Link to={{ pathname: measure.metric.key, query: { id: component.key } }}>
- {formatLeak(measure.leak, measure.metric)}
- </Link>
- )}
- </div>
- </li>
- ))}
- </ul>
- </li>
+ <AllMeasuresDomain
+ key={domain.name}
+ domain={domain}
+ component={component}
+ displayLeakHeader={index === 0}
+ leakPeriodLabel={leakPeriodLabel}/>
))}
</ul>
);
}
}
-ComponentMeasuresApp.contextTypes = {
+AllMeasures.contextTypes = {
component: React.PropTypes.object
};
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/AllMeasuresDomain.js b/server/sonar-web/src/main/js/apps/component-measures/components/AllMeasuresDomain.js
new file mode 100644
index 00000000000..ba5c86459bf
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/AllMeasuresDomain.js
@@ -0,0 +1,64 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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 React from 'react';
+import { Link } from 'react-router';
+
+import { formatLeak } from '../utils';
+import { formatMeasure } from '../../../helpers/measures';
+import { translateWithParameters } from '../../../helpers/l10n';
+
+export default function AllMeasuresDomain ({ domain, component, displayLeakHeader, leakPeriodLabel }) {
+ return (
+ <li>
+ <header className="page-header">
+ <h3 className="page-title">{domain.name}</h3>
+ {displayLeakHeader && (
+ <div className="measures-domains-leak-header">
+ {translateWithParameters('overview.leak_period_x', leakPeriodLabel)}
+ </div>
+ )}
+ </header>
+
+ <ul className="domain-measures">
+ {domain.measures.map(measure => (
+ <li key={measure.metric.key}>
+ <div className="domain-measures-name">
+ {measure.metric.name}
+ </div>
+ <div className="domain-measures-value">
+ {measure.value != null && (
+ <Link to={{ pathname: measure.metric.key, query: { id: component.key } }}>
+ {formatMeasure(measure.value, measure.metric.type)}
+ </Link>
+ )}
+ </div>
+ <div className="domain-measures-value domain-measures-leak">
+ {measure.leak != null && (
+ <Link to={{ pathname: measure.metric.key, query: { id: component.key } }}>
+ {formatLeak(measure.leak, measure.metric)}
+ </Link>
+ )}
+ </div>
+ </li>
+ ))}
+ </ul>
+ </li>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/ListIcon.js b/server/sonar-web/src/main/js/apps/component-measures/components/IconList.js
index 6725685c36b..6725685c36b 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/ListIcon.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/IconList.js
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/TreeIcon.js b/server/sonar-web/src/main/js/apps/component-measures/components/IconTree.js
index cd8d28d0313..1b9cb2ca79d 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/TreeIcon.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/IconTree.js
@@ -19,7 +19,7 @@
*/
import React from 'react';
-export default function TreeIcon () {
+export default function IconTree () {
/* eslint max-len: 0 */
return (
<svg className="measure-tab-icon"
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/UpIcon.js b/server/sonar-web/src/main/js/apps/component-measures/components/IconUp.js
index 69c2dd828a1..462d07bb17b 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/UpIcon.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/IconUp.js
@@ -19,7 +19,7 @@
*/
import React from 'react';
-export default function UpIcon () {
+export default function IconUp () {
/* eslint max-len: 0 */
return (
<svg className="measure-details-components-up-icon"
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureDetails.js b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureDetails.js
index 20e3cf6c53e..adeef588454 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureDetails.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureDetails.js
@@ -20,14 +20,12 @@
import React from 'react';
import Spinner from './Spinner';
+import MeasureDetailsHeader from './MeasureDetailsHeader';
import MeasureDrilldown from './MeasureDrilldown';
-import { getLeakValue, formatLeak } from '../utils';
-import { getMeasuresAndMeta } from '../../../api/measures';
-import { formatMeasure } from '../../../helpers/measures';
-import { translateWithParameters } from '../../../helpers/l10n';
-import { TooltipsContainer } from '../../../components/mixins/tooltips-mixin';
-import { getPeriodLabel } from '../../overview/helpers/periods';
+import { enhanceWithLeak } from '../utils';
+import { getMeasuresAndMeta } from '../../../api/measures';
+import { getLeakPeriodLabel } from '../../../helpers/periods';
export default class MeasureDetails extends React.Component {
state = {};
@@ -35,10 +33,12 @@ export default class MeasureDetails extends React.Component {
componentWillMount () {
const { metrics } = this.props;
const { metricKey } = this.props.params;
- const { router, component } = this.context;
- const metric = metrics.find(metric => metric.key === metricKey);
- if (!metric) {
+ this.metric = metrics.find(metric => metric.key === metricKey);
+
+ if (!this.metric) {
+ const { router, component } = this.context;
+
router.replace({
pathname: '/',
query: { id: component.key }
@@ -66,14 +66,16 @@ export default class MeasureDetails extends React.Component {
const { metricKey } = this.props.params;
const { component } = this.context;
- getMeasuresAndMeta(component.key, [metricKey], { additionalFields: 'periods' }).then(r => {
+ getMeasuresAndMeta(
+ component.key,
+ [metricKey],
+ { additionalFields: 'periods' }
+ ).then(r => {
const measures = r.component.measures;
if (this.mounted && measures.length === 1) {
- const measure = {
- ...measures[0],
- leak: getLeakValue(measures[0])
- };
+ const measure = enhanceWithLeak(measures[0]);
+
this.setState({
measure,
periods: r.periods
@@ -83,48 +85,25 @@ export default class MeasureDetails extends React.Component {
}
render () {
- const { metrics, children } = this.props;
- const { metricKey, tab } = this.props.params;
- const metric = metrics.find(metric => metric.key === metricKey);
const { measure, periods } = this.state;
if (!measure) {
return <Spinner/>;
}
- const finalTab = tab || 'tree';
- const leakLabel = getPeriodLabel(periods, 1);
+ const { tab } = this.props.params;
+ const leakPeriodLabel = getLeakPeriodLabel(periods);
return (
<div className="measure-details">
- <h2 className="measure-details-metric">
- {metric.name}
- </h2>
-
- {measure && (
- <TooltipsContainer>
- <div className="measure-details-value">
- {measure.value != null && (
- <span className="measure-details-value-absolute">
- {formatMeasure(measure.value, metric.type)}
- </span>
- )}
-
- {measure.leak != null && (
- <span
- className="measure-details-value-leak"
- title={translateWithParameters('overview.leak_period_x', leakLabel)}
- data-toggle="tooltip">
- {formatLeak(measure.leak, metric)}
- </span>
- )}
- </div>
- </TooltipsContainer>
- )}
+ <MeasureDetailsHeader
+ measure={measure}
+ metric={this.metric}
+ leakPeriodLabel={leakPeriodLabel}/>
{measure && (
- <MeasureDrilldown metric={metric} tab={finalTab}>
- {children}
+ <MeasureDrilldown metric={this.metric} tab={tab}>
+ {this.props.children}
</MeasureDrilldown>
)}
</div>
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureDetailsHeader.js b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureDetailsHeader.js
new file mode 100644
index 00000000000..93d0e8b58f3
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureDetailsHeader.js
@@ -0,0 +1,56 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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 React from 'react';
+
+import { formatLeak } from '../utils';
+import { formatMeasure } from '../../../helpers/measures';
+import { translateWithParameters } from '../../../helpers/l10n';
+import { TooltipsContainer } from '../../../components/mixins/tooltips-mixin';
+
+export default function MeasureDetailsHeader ({ measure, metric, leakPeriodLabel }) {
+ const leakPeriodTooltip = translateWithParameters('overview.leak_period_x', leakPeriodLabel);
+
+ return (
+ <header className="measure-details-header">
+ <h2 className="measure-details-metric">
+ {metric.name}
+ </h2>
+
+ <TooltipsContainer>
+ <div className="measure-details-value">
+ {measure.value != null && (
+ <div className="measure-details-value-absolute">
+ {formatMeasure(measure.value, metric.type)}
+ </div>
+ )}
+
+ {measure.leak != null && (
+ <div
+ className="measure-details-value-leak"
+ title={leakPeriodTooltip}
+ data-toggle="tooltip">
+ {formatLeak(measure.leak, metric)}
+ </div>
+ )}
+ </div>
+ </TooltipsContainer>
+ </header>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureDrilldown.js b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureDrilldown.js
index 7d1ebec1867..a66d23ebcda 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureDrilldown.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureDrilldown.js
@@ -20,8 +20,8 @@
import React from 'react';
import { Link } from 'react-router';
-import ListIcon from './ListIcon';
-import TreeIcon from './TreeIcon';
+import IconList from './IconList';
+import IconTree from './IconTree';
import { translate } from '../../../helpers/l10n';
export default class MeasureDrilldown extends React.Component {
@@ -29,19 +29,16 @@ export default class MeasureDrilldown extends React.Component {
const { metric, children } = this.props;
const { component } = this.context;
- const child = React.cloneElement(children, {
- component,
- metric
- });
+ const child = React.cloneElement(children, { component, metric });
return (
<div className="measure-details-drilldown">
- <ul className="measure-details-mode">
+ <ul className="measure-details-drilldown-mode">
<li>
<Link
activeClassName="active"
to={{ pathname: `${metric.key}/tree`, query: { id: component.key } }}>
- <TreeIcon/>
+ <IconTree/>
{translate('component_measures.tab.tree')}
</Link>
</li>
@@ -49,7 +46,7 @@ export default class MeasureDrilldown extends React.Component {
<Link
activeClassName="active"
to={{ pathname: `${metric.key}/list`, query: { id: component.key } }}>
- <ListIcon/>
+ <IconList/>
{translate('component_measures.tab.list')}
</Link>
</li>
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/ComponentsList.js b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureDrilldownComponents.js
index 9f0ebd78cbf..a020e83f0db 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/ComponentsList.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureDrilldownComponents.js
@@ -19,12 +19,13 @@
*/
import React from 'react';
-import UpIcon from './UpIcon';
+import MeasureDrilldownEmpty from './MeasureDrilldownEmpty';
+import IconUp from './IconUp';
import QualifierIcon from '../../../components/shared/qualifier-icon';
import { formatMeasure } from '../../../helpers/measures';
import { formatLeak } from '../utils';
-export default function ComponentsList ({ components, selected, parent, metric, onClick }) {
+export default function MeasureDrilldownComponents ({ components, selected, parent, metric, onClick }) {
const handleClick = (component, e) => {
e.preventDefault();
e.target.blur();
@@ -32,18 +33,19 @@ export default function ComponentsList ({ components, selected, parent, metric,
};
return (
- <ul>
+ <ul className="measure-details-components">
{parent && (
<li key={parent.id} className="measure-details-components-parent">
<a href="#" onClick={handleClick.bind(this, parent)}>
<div className="measure-details-component-name">
- <UpIcon/>
- &nbsp;
- ..
+ <IconUp/>&nbsp;..
</div>
</a>
</li>
)}
+
+ {!components.length && <MeasureDrilldownEmpty/>}
+
{components.map(component => (
<li key={component.id}>
<a
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/NoResults.js b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureDrilldownEmpty.js
index db8afedd0be..ede861f8382 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/NoResults.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureDrilldownEmpty.js
@@ -21,7 +21,7 @@ import React from 'react';
import { translate } from '../../../helpers/l10n';
-export default function NoResults () {
+export default function MeasureDrilldownEmpty () {
return (
<div className="measures-details-components-empty note">
{translate('no_results')}
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureList.js b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureDrilldownList.js
index 322d7b5eb13..0a0e49601aa 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureList.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureDrilldownList.js
@@ -20,13 +20,13 @@
import React from 'react';
import Spinner from './Spinner';
-import ComponentsList from './ComponentsList';
+import MeasureDrilldownComponents from './MeasureDrilldownComponents';
import SourceViewer from '../../code/components/SourceViewer';
-import NoResults from './NoResults';
-import { getSingleMeasureValue, getSingleLeakValue } from '../utils';
+
+import { enhanceWithSingleMeasure } from '../utils';
import { getFiles } from '../../../api/components';
-export default class MeasurePlainList extends React.Component {
+export default class MeasureDrilldownList extends React.Component {
state = {
components: [],
selected: null,
@@ -61,20 +61,12 @@ export default class MeasurePlainList extends React.Component {
this.setState({ fetching: true });
- getFiles(baseComponent.key, [metric.key], options).then(children => {
+ getFiles(baseComponent.key, [metric.key], options).then(files => {
if (this.mounted) {
- const componentsWithMappedMeasure = children
- .map(component => {
- return {
- ...component,
- value: getSingleMeasureValue(component.measures),
- leak: getSingleLeakValue(component.measures)
- };
- })
- .filter(component => component.value != null || component.leak != null);
+ const components = enhanceWithSingleMeasure(files);
this.setState({
- components: componentsWithMappedMeasure,
+ components,
selected: null,
fetching: false
});
@@ -94,19 +86,13 @@ export default class MeasurePlainList extends React.Component {
return <Spinner/>;
}
- if (!components.length) {
- return <NoResults/>;
- }
-
return (
<div className="measure-details-plain-list">
- <div className="measure-details-components">
- <ComponentsList
- components={components}
- selected={selected}
- metric={metric}
- onClick={this.handleFileClick.bind(this)}/>
- </div>
+ <MeasureDrilldownComponents
+ components={components}
+ selected={selected}
+ metric={metric}
+ onClick={this.handleFileClick.bind(this)}/>
{selected && (
<div className="measure-details-viewer">
@@ -118,6 +104,6 @@ export default class MeasurePlainList extends React.Component {
}
}
-MeasurePlainList.contextTypes = {
+MeasureDrilldownList.contextTypes = {
component: React.PropTypes.object
};
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureTree.js b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureDrilldownTree.js
index d39f0eda0bc..47ae38b67cb 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureTree.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureDrilldownTree.js
@@ -20,13 +20,13 @@
import React from 'react';
import Spinner from './Spinner';
-import ComponentsList from './ComponentsList';
-import NoResults from './NoResults';
+import MeasureDrilldownComponents from './MeasureDrilldownComponents';
import SourceViewer from '../../code/components/SourceViewer';
-import { getSingleMeasureValue, getSingleLeakValue } from '../utils';
+
+import { enhanceWithSingleMeasure } from '../utils';
import { getChildren } from '../../../api/components';
-export default class MeasureTree extends React.Component {
+export default class MeasureDrilldownTree extends React.Component {
state = {
components: [],
breadcrumbs: [],
@@ -64,17 +64,11 @@ export default class MeasureTree extends React.Component {
getChildren(baseComponent.key, [metric.key], options).then(children => {
if (this.mounted) {
- const componentsWithMappedMeasure = children
- .map(component => {
- return {
- ...component,
- value: getSingleMeasureValue(component.measures),
- leak: getSingleLeakValue(component.measures)
- };
- })
- .filter(component => component.value != null || component.leak != null);
-
- const indexInBreadcrumbs = this.state.breadcrumbs.findIndex(component => component === baseComponent);
+ const components = enhanceWithSingleMeasure(children);
+
+ const indexInBreadcrumbs = this.state.breadcrumbs
+ .findIndex(component => component === baseComponent);
+
const breadcrumbs = indexInBreadcrumbs !== -1 ?
this.state.breadcrumbs.slice(0, indexInBreadcrumbs + 1) :
[...this.state.breadcrumbs, baseComponent];
@@ -82,7 +76,7 @@ export default class MeasureTree extends React.Component {
this.setState({
baseComponent,
breadcrumbs,
- components: componentsWithMappedMeasure,
+ components,
selected: null,
fetching: false
});
@@ -111,20 +105,14 @@ export default class MeasureTree extends React.Component {
return <Spinner/>;
}
- if (!components.length) {
- return <NoResults/>;
- }
-
return (
<div className="measure-details-tree">
- <div className="measure-details-components">
- <ComponentsList
- components={components}
- selected={selected}
- parent={parent}
- metric={metric}
- onClick={this.handleFileClick.bind(this)}/>
- </div>
+ <MeasureDrilldownComponents
+ components={components}
+ selected={selected}
+ parent={parent}
+ metric={metric}
+ onClick={this.handleFileClick.bind(this)}/>
{selected && (
<div className="measure-details-viewer">
@@ -136,6 +124,6 @@ export default class MeasureTree extends React.Component {
}
}
-MeasureTree.contextTypes = {
+MeasureDrilldownTree.contextTypes = {
component: React.PropTypes.object
};
diff --git a/server/sonar-web/src/main/js/apps/component-measures/styles.css b/server/sonar-web/src/main/js/apps/component-measures/styles.css
index 5e2a065cd2d..09e238d3e02 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/styles.css
+++ b/server/sonar-web/src/main/js/apps/component-measures/styles.css
@@ -1,16 +1,13 @@
-.component-measures-domains {
+.measures-domains {
width: 600px;
margin: 20px auto;
}
-.component-measures-domains > li {
-}
-
-.component-measures-domains > li + li {
+.measures-domains > li + li {
margin-top: 40px;
}
-.component-measures-domains-leak-header {
+.measures-domains-leak-header {
float: right;
line-height: 22px;
padding: 0 10px;
@@ -18,43 +15,37 @@
background-color: #fbf3d5;
}
-.component-measures-domain-measures {
-}
-
-.component-measures-domain-measures > li {
+.domain-measures > li {
display: flex;
}
-.component-measures-domain-measures > li:nth-child(odd) {
+.domain-measures > li:nth-child(odd) {
background-color: #f8f8f8;
}
-.component-measures-domain-measures-name,
-.component-measures-domain-measures-value {
+.domain-measures-name,
+.domain-measures-value {
padding: 7px 10px;
box-sizing: border-box;
}
-.component-measures-domain-measures-name {
+.domain-measures-name {
width: calc(100% - 160px);
}
-.component-measures-domain-measures-value {
+.domain-measures-value {
width: 80px;
text-align: right;
}
-.component-measures-leak-cell {
+.domain-measures-leak {
background-color: #fbf3d5;
}
-.component-measures-domain-measures > li:nth-child(odd) .component-measures-leak-cell {
+.domain-measures > li:nth-child(odd) .domain-measures-leak {
background-color: #f5eed0;
}
-.component-measures-domain-name {
- margin-bottom: 8px;
-}
.measure-details {
margin-top: 10px;
@@ -95,34 +86,34 @@
margin-top: 20px;
}
-.measure-details-mode {
+.measure-details-drilldown-mode {
display: flex;
padding-left: 10px;
padding-right: 10px;
border-bottom: 1px solid #e6e6e6;
}
-.measure-details-mode > li {
+.measure-details-drilldown-mode > li {
margin-bottom: -1px;
}
-.measure-details-mode > li + li {
+.measure-details-drilldown-mode > li + li {
margin-left: 2px;
}
-.measure-details-mode > li > a {
+.measure-details-drilldown-mode > li > a {
display: inline-block;
padding: 5px 10px;
border-bottom: 2px solid transparent;
color: #444;
}
-.measure-details-mode > li > a:hover,
-.measure-details-mode > li > a.active {
+.measure-details-drilldown-mode > li > a:hover,
+.measure-details-drilldown-mode > li > a.active {
border-bottom-color: #4b9fd5;
}
-.measure-details-mode > li > a.active .measure-tab-icon path {
+.measure-details-drilldown-mode > li > a.active .measure-tab-icon path {
fill: #4b9fd5;
}
@@ -140,7 +131,7 @@
padding-bottom: 6px;
}
-.measure-details-components > ul > li > a {
+.measure-details-components > li > a {
display: flex;
padding-top: 5px;
padding-bottom: 5px;
@@ -148,8 +139,8 @@
color: #444;
}
-.measure-details-components > ul > li > a:hover,
-.measure-details-components > ul > li > a.selected {
+.measure-details-components > li > a:hover,
+.measure-details-components > li > a.selected {
background-color: #cae3f2 !important;
}
@@ -207,5 +198,5 @@
}
.measures-details-components-empty {
- padding: 20px;
+ padding: 10px;
}
diff --git a/server/sonar-web/src/main/js/apps/component-measures/utils.js b/server/sonar-web/src/main/js/apps/component-measures/utils.js
index 39d0f5ec2cc..b3eef46c626 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/utils.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/utils.js
@@ -58,3 +58,27 @@ export function formatLeak (value, metric) {
return formatMeasureVariation(value, metric.type);
}
}
+
+export function enhanceWithLeak (measures) {
+ function enhanceSingle (measure) {
+ return { ...measure, leak: getLeakValue(measure) };
+ }
+
+ if (Array.isArray(measures)) {
+ return measures.map(enhanceSingle);
+ } else {
+ return enhanceSingle(measures);
+ }
+}
+
+export function enhanceWithSingleMeasure (components) {
+ return components
+ .map(component => {
+ return {
+ ...component,
+ value: getSingleMeasureValue(component.measures),
+ leak: getSingleLeakValue(component.measures)
+ };
+ })
+ .filter(component => component.value != null || component.leak != null);
+}
diff --git a/server/sonar-web/src/main/js/apps/overview/helpers/periods.js b/server/sonar-web/src/main/js/apps/overview/helpers/periods.js
index 3e1e1d5d89f..80bb9b7a308 100644
--- a/server/sonar-web/src/main/js/apps/overview/helpers/periods.js
+++ b/server/sonar-web/src/main/js/apps/overview/helpers/periods.js
@@ -19,8 +19,8 @@
*/
import _ from 'underscore';
import moment from 'moment';
-import { translate, translateWithParameters } from '../../../helpers/l10n';
+import { getPeriodLabel as getLabel } from '../../../helpers/periods';
export function getPeriodLabel (periods, periodIndex) {
const period = _.findWhere(periods, { index: periodIndex });
@@ -29,13 +29,7 @@ export function getPeriodLabel (periods, periodIndex) {
return null;
}
- const parameter = period.modeParam || period.parameter;
-
- if (period.mode === 'previous_version' && !parameter) {
- return translate('overview.period.previous_version_only_date');
- }
-
- return translateWithParameters(`overview.period.${period.mode}`, parameter);
+ return getLabel(period);
}
diff --git a/server/sonar-web/src/main/js/helpers/periods.js b/server/sonar-web/src/main/js/helpers/periods.js
new file mode 100644
index 00000000000..48006199d2a
--- /dev/null
+++ b/server/sonar-web/src/main/js/helpers/periods.js
@@ -0,0 +1,46 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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 { translate, translateWithParameters } from './l10n';
+
+export function getLeakPeriod (periods) {
+ if (!Array.isArray(periods)) {
+ return null;
+ }
+
+ return periods.find(period => period.index === 1);
+}
+
+export function getPeriodLabel (period) {
+ if (!period) {
+ return null;
+ }
+
+ const parameter = period.modeParam || period.parameter;
+
+ if (period.mode === 'previous_version' && !parameter) {
+ return translate('overview.period.previous_version_only_date');
+ }
+
+ return translateWithParameters(`overview.period.${period.mode}`, parameter);
+}
+
+export function getLeakPeriodLabel (periods) {
+ return getPeriodLabel(getLeakPeriod(periods));
+}