diff options
author | Stas Vilchik <vilchiks@gmail.com> | 2016-03-03 14:55:20 +0100 |
---|---|---|
committer | Stas Vilchik <vilchiks@gmail.com> | 2016-03-07 16:10:23 +0100 |
commit | 2f5d0cf228a0df8301ffd5a555b50bb38afc0817 (patch) | |
tree | 1e1afe17c7b1e5860ad6690df5cc62f7d8b8d620 /server/sonar-web | |
parent | 1ca986e90c183bce327bfb3d2c9094db8a3083e1 (diff) | |
download | sonarqube-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.js | 9 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/apps/component-measures/app.js | 4 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/apps/component-measures/components/AllMeasuresList.js | 66 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/apps/component-measures/components/ComponentsList.js | 8 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/apps/component-measures/components/MeasureDetails.js | 47 | ||||
-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.js | 7 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/apps/component-measures/styles.css | 72 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/apps/component-measures/utils.js | 23 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/apps/overview/helpers/periods.js | 9 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/helpers/measures.js | 18 |
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, <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); |