aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web
diff options
context:
space:
mode:
authorStas Vilchik <vilchiks@gmail.com>2016-03-03 14:55:20 +0100
committerStas Vilchik <vilchiks@gmail.com>2016-03-07 16:10:23 +0100
commit2f5d0cf228a0df8301ffd5a555b50bb38afc0817 (patch)
tree1e1afe17c7b1e5860ad6690df5cc62f7d8b8d620 /server/sonar-web
parent1ca986e90c183bce327bfb3d2c9094db8a3083e1 (diff)
downloadsonarqube-2f5d0cf228a0df8301ffd5a555b50bb38afc0817.tar.gz
sonarqube-2f5d0cf228a0df8301ffd5a555b50bb38afc0817.zip
SONAR-7403 Display the leak period value on the "Measures" page
Diffstat (limited to 'server/sonar-web')
-rw-r--r--server/sonar-web/src/main/js/api/measures.js9
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/app.js4
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/AllMeasuresList.js66
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/ComponentsList.js8
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/MeasureDetails.js47
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/MeasureList.js (renamed from server/sonar-web/src/main/js/apps/component-measures/components/MeasurePlainList.js)7
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/MeasureTree.js7
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/styles.css72
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/utils.js23
-rw-r--r--server/sonar-web/src/main/js/apps/overview/helpers/periods.js9
-rw-r--r--server/sonar-web/src/main/js/helpers/measures.js18
11 files changed, 208 insertions, 62 deletions
diff --git a/server/sonar-web/src/main/js/api/measures.js b/server/sonar-web/src/main/js/api/measures.js
index a22b042104d..4676b338b53 100644
--- a/server/sonar-web/src/main/js/api/measures.js
+++ b/server/sonar-web/src/main/js/api/measures.js
@@ -25,3 +25,12 @@ export function getMeasures (componentKey, metrics) {
const data = { componentKey, metricKeys: metrics.join(',') };
return getJSON(url, data).then(r => r.component.measures);
}
+
+export function getMeasuresAndMeta (componentKey, metrics, additional = {}) {
+ const url = '/api/measures/component';
+ const data = Object.assign({}, additional, {
+ componentKey,
+ metricKeys: metrics.join(',')
+ });
+ return getJSON(url, data);
+}
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 d805e9b42f5..4077b2f777d 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
@@ -26,7 +26,7 @@ import ComponentMeasuresApp from './components/ComponentMeasuresApp';
import AllMeasuresList from './components/AllMeasuresList';
import MeasureDetails from './components/MeasureDetails';
import MeasureTree from './components/MeasureTree';
-import MeasurePlainList from './components/MeasurePlainList';
+import MeasureList from './components/MeasureList';
import './styles.css';
@@ -54,7 +54,7 @@ window.sonarqube.appStarted.then(options => {
<Route path=":metricKey" component={MeasureDetails}>
<IndexRedirect to="tree"/>
<Route path="tree" component={MeasureTree}/>
- <Route path="list" component={MeasurePlainList}/>
+ <Route path="list" component={MeasureList}/>
</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/AllMeasuresList.js
index 64ff6335ee8..12a43009b1b 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/AllMeasuresList.js
@@ -22,8 +22,12 @@ import React from 'react';
import { Link } from 'react-router';
import Spinner from './Spinner';
-import { getMeasures } from '../../../api/measures';
+import { getLeakValue, formatLeak } from '../utils';
+import { getMeasuresAndMeta } from '../../../api/measures';
import { formatMeasure } from '../../../helpers/measures';
+import { translateWithParameters } from '../../../helpers/l10n';
+
+import { getPeriodLabel } from '../../overview/helpers/periods';
export default class ComponentMeasuresApp extends React.Component {
state = {
@@ -48,17 +52,19 @@ export default class ComponentMeasuresApp extends React.Component {
.filter(metric => metric.type !== 'DATA' && metric.type !== 'DISTRIB')
.map(metric => metric.key);
- getMeasures(component.key, metricKeys).then(measures => {
+ getMeasuresAndMeta(component.key, metricKeys, { additionalFields: 'periods' }).then(r => {
if (this.mounted) {
- const measuresWithMetrics = measures
+ const measures = r.component.measures
.map(measure => {
const metric = metrics.find(metric => metric.key === measure.metric);
- return { ...measure, metric };
+ const leak = getLeakValue(measure);
+ return { ...measure, metric, leak };
})
- .filter(measure => measure.value != null);
+ .filter(measure => measure.value != null || measure.leak != null);
this.setState({
- measures: measuresWithMetrics,
+ measures,
+ periods: r.periods,
fetching: false
});
}
@@ -66,7 +72,7 @@ export default class ComponentMeasuresApp extends React.Component {
}
render () {
- const { fetching, measures } = this.state;
+ const { fetching, measures, periods } = this.state;
if (fetching) {
return <Spinner/>;
@@ -80,32 +86,44 @@ export default class ComponentMeasuresApp extends React.Component {
return { name, measures: sortedMeasures };
}), 'name');
+ const leakLabel = getPeriodLabel(periods, 1);
+
return (
<ul className="component-measures-domains">
- {domains.map(domain => (
+ {domains.map((domain, index) => (
<li key={domain.name}>
- <h3 className="component-measures-domain-name">{domain.name}</h3>
+ <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>
- <table className="data zebra">
- <tbody>
+ <ul className="component-measures-domain-measures">
{domain.measures.map(measure => (
- <tr key={measure.metric.key}>
- <td>
+ <li key={measure.metric.key}>
+ <div className="component-measures-domain-measures-name">
{measure.metric.name}
- </td>
- <td className="thin nowrap text-right">
+ </div>
+ <div className="component-measures-domain-measures-value">
{measure.value != null && (
- <div style={{ width: 80 }}>
- <Link to={{ pathname: measure.metric.key, query: { id: component.key } }}>
- {formatMeasure(measure.value, measure.metric.type)}
- </Link>
- </div>
+ <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>
)}
- </td>
- </tr>
+ </div>
+ </li>
))}
- </tbody>
- </table>
+ </ul>
</li>
))}
</ul>
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/ComponentsList.js
index de7b13632f1..9f0ebd78cbf 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/ComponentsList.js
@@ -22,6 +22,7 @@ import React from 'react';
import UpIcon from './UpIcon';
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 }) {
const handleClick = (component, e) => {
@@ -55,9 +56,14 @@ export default function ComponentsList ({ components, selected, parent, metric,
&nbsp;
<span>{component.name}</span>
</div>
+
<div className="measure-details-component-value">
- {component.value != null && (
+ {component.value != null ? (
formatMeasure(component.value, metric.type)
+ ) : (
+ component.leak != null && (
+ formatLeak(component.leak, metric)
+ )
)}
</div>
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 e5a3bb2d426..9389e6b765b 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
@@ -21,8 +21,13 @@ import React from 'react';
import Spinner from './Spinner';
import MeasureDrilldown from './MeasureDrilldown';
-import { getMeasures } from '../../../api/measures';
+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';
export default class MeasureDetails extends React.Component {
state = {};
@@ -47,9 +52,18 @@ export default class MeasureDetails extends React.Component {
const { metricKey } = this.props.params;
const { component } = this.context;
- getMeasures(component.key, [metricKey]).then(measures => {
+ getMeasuresAndMeta(component.key, [metricKey], { additionalFields: 'periods' }).then(r => {
+ const measures = r.component.measures;
+
if (this.mounted && measures.length === 1) {
- this.setState({ measure: measures[0] });
+ const measure = {
+ ...measures[0],
+ leak: getLeakValue(measures[0])
+ };
+ this.setState({
+ measure,
+ periods: r.periods
+ });
}
});
}
@@ -57,7 +71,7 @@ export default class MeasureDetails extends React.Component {
render () {
const { metricKey, tab } = this.props.params;
const { metrics, children } = this.props;
- const { measure } = this.state;
+ const { measure, periods } = this.state;
const metric = metrics.find(metric => metric.key === metricKey);
const finalTab = tab || 'tree';
@@ -65,6 +79,8 @@ export default class MeasureDetails extends React.Component {
return <Spinner/>;
}
+ const leakLabel = getPeriodLabel(periods, 1);
+
return (
<div className="measure-details">
<h2 className="measure-details-metric">
@@ -72,11 +88,24 @@ export default class MeasureDetails extends React.Component {
</h2>
{measure && (
- <div className="measure-details-value">
- {measure.value != null && (
- formatMeasure(measure.value, metric.type)
- )}
- </div>
+ <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>
)}
{measure && (
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasurePlainList.js b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureList.js
index 45ad7256a86..322d7b5eb13 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasurePlainList.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureList.js
@@ -23,7 +23,7 @@ import Spinner from './Spinner';
import ComponentsList from './ComponentsList';
import SourceViewer from '../../code/components/SourceViewer';
import NoResults from './NoResults';
-import { getSingleMeasureValue } from '../utils';
+import { getSingleMeasureValue, getSingleLeakValue } from '../utils';
import { getFiles } from '../../../api/components';
export default class MeasurePlainList extends React.Component {
@@ -67,10 +67,11 @@ export default class MeasurePlainList extends React.Component {
.map(component => {
return {
...component,
- value: getSingleMeasureValue(component.measures)
+ value: getSingleMeasureValue(component.measures),
+ leak: getSingleLeakValue(component.measures)
};
})
- .filter(component => component.value != null);
+ .filter(component => component.value != null || component.leak != null);
this.setState({
components: componentsWithMappedMeasure,
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/MeasureTree.js
index 8e4ceb16542..d39f0eda0bc 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/MeasureTree.js
@@ -23,7 +23,7 @@ import Spinner from './Spinner';
import ComponentsList from './ComponentsList';
import NoResults from './NoResults';
import SourceViewer from '../../code/components/SourceViewer';
-import { getSingleMeasureValue } from '../utils';
+import { getSingleMeasureValue, getSingleLeakValue } from '../utils';
import { getChildren } from '../../../api/components';
export default class MeasureTree extends React.Component {
@@ -68,10 +68,11 @@ export default class MeasureTree extends React.Component {
.map(component => {
return {
...component,
- value: getSingleMeasureValue(component.measures)
+ value: getSingleMeasureValue(component.measures),
+ leak: getSingleLeakValue(component.measures)
};
})
- .filter(component => component.value != null);
+ .filter(component => component.value != null || component.leak != null);
const indexInBreadcrumbs = this.state.breadcrumbs.findIndex(component => component === baseComponent);
const breadcrumbs = indexInBreadcrumbs !== -1 ?
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 8be9ce18d5f..5e2a065cd2d 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,19 +1,55 @@
.component-measures-domains {
- display: flex;
- flex-wrap: wrap;
- justify-content: space-between;
- width: 980px;
- margin: 20px auto -20px;
+ width: 600px;
+ margin: 20px auto;
}
.component-measures-domains > li {
- width: 300px;
- margin-right: 40px;
- margin-bottom: 40px;
}
-.component-measures-domains > li:nth-child(3n) {
- margin-right: 0;
+.component-measures-domains > li + li {
+ margin-top: 40px;
+}
+
+.component-measures-domains-leak-header {
+ float: right;
+ line-height: 22px;
+ padding: 0 10px;
+ border: 1px solid #eae3c7;
+ background-color: #fbf3d5;
+}
+
+.component-measures-domain-measures {
+}
+
+.component-measures-domain-measures > li {
+ display: flex;
+}
+
+.component-measures-domain-measures > li:nth-child(odd) {
+ background-color: #f8f8f8;
+}
+
+.component-measures-domain-measures-name,
+.component-measures-domain-measures-value {
+ padding: 7px 10px;
+ box-sizing: border-box;
+}
+
+.component-measures-domain-measures-name {
+ width: calc(100% - 160px);
+}
+
+.component-measures-domain-measures-value {
+ width: 80px;
+ text-align: right;
+}
+
+.component-measures-leak-cell {
+ background-color: #fbf3d5;
+}
+
+.component-measures-domain-measures > li:nth-child(odd) .component-measures-leak-cell {
+ background-color: #f5eed0;
}
.component-measures-domain-name {
@@ -39,6 +75,22 @@
font-weight: 300;
}
+.measure-details-value-absolute {
+ display: inline-block;
+ padding: 5px 0;
+}
+
+.measure-details-value-leak {
+ display: inline-block;
+ padding: 4px 10px;
+ border: 1px solid #eae3c7;
+ background-color: #fbf3d5;
+}
+
+.measure-details-value-absolute + .measure-details-value-leak {
+ margin-left: 20px;
+}
+
.measure-details-drilldown {
margin-top: 20px;
}
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 1f3da160aa7..39d0f5ec2cc 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
@@ -17,6 +17,8 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { formatMeasure, formatMeasureVariation } from '../../helpers/measures';
+
export function getLeakValue (measure) {
if (!measure) {
return null;
@@ -35,3 +37,24 @@ export function getSingleMeasureValue (measures) {
return measures[0].value;
}
+
+export function getSingleLeakValue (measures) {
+ if (!measures || !measures.length) {
+ return null;
+ }
+
+ const measure = measures[0];
+
+ const period = measure.periods ?
+ measure.periods.find(period => period.index === 1) : null;
+
+ return period ? period.value : null;
+}
+
+export function formatLeak (value, metric) {
+ if (metric.key.indexOf('new_') === 0) {
+ return formatMeasure(value, metric.type);
+ } else {
+ return formatMeasureVariation(value, metric.type);
+ }
+}
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 52a51637b85..3e1e1d5d89f 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
@@ -24,13 +24,18 @@ import { translate, translateWithParameters } from '../../../helpers/l10n';
export function getPeriodLabel (periods, periodIndex) {
const period = _.findWhere(periods, { index: periodIndex });
+
if (!period) {
return null;
}
- if (period.mode === 'previous_version' && !period.modeParam) {
+
+ 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}`, period.modeParam);
+
+ return translateWithParameters(`overview.period.${period.mode}`, parameter);
}
diff --git a/server/sonar-web/src/main/js/helpers/measures.js b/server/sonar-web/src/main/js/helpers/measures.js
index d5cf7214289..6059a3af3cf 100644
--- a/server/sonar-web/src/main/js/helpers/measures.js
+++ b/server/sonar-web/src/main/js/helpers/measures.js
@@ -103,8 +103,8 @@ function getVariationFormatter (type) {
'PERCENT': percentVariationFormatter,
'WORK_DUR': durationVariationFormatter,
'SHORT_WORK_DUR': shortDurationVariationFormatter,
- 'RATING': ratingFormatter,
- 'LEVEL': levelFormatter,
+ 'RATING': emptyFormatter,
+ 'LEVEL': emptyFormatter,
'MILLISEC': millisecondsVariationFormatter
};
return FORMATTERS[type] || noFormatter;
@@ -120,6 +120,10 @@ function noFormatter (value) {
return value;
}
+function emptyFormatter () {
+ return null;
+}
+
function intFormatter (value) {
return numeral(value).format('0,0');
}
@@ -261,7 +265,7 @@ function formatDurationShort (isNegative, days, hours, minutes) {
}
function durationFormatter (value) {
- if (value === 0) {
+ if (value === 0 || value === '0') {
return '0';
}
const hoursInDay = window.SS.hoursInDay;
@@ -276,7 +280,7 @@ function durationFormatter (value) {
function shortDurationFormatter (value) {
value = parseInt(value, 10);
- if (value === 0) {
+ if (value === 0 || value === '0') {
return '0';
}
const hoursInDay = window.SS.hoursInDay;
@@ -290,8 +294,7 @@ function shortDurationFormatter (value) {
}
function durationVariationFormatter (value) {
- /* eslint eqeqeq: 0 */
- if (value == 0) {
+ if (value === 0 || value === '0') {
return '+0';
}
const formatted = durationFormatter(value);
@@ -299,8 +302,7 @@ function durationVariationFormatter (value) {
}
function shortDurationVariationFormatter (value) {
- /* eslint eqeqeq: 0 */
- if (value == 0) {
+ if (value === 0 || value === '0') {
return '+0';
}
const formatted = shortDurationFormatter(value);