diff options
14 files changed, 363 insertions, 231 deletions
diff --git a/server/sonar-web/src/main/js/apps/overview/common-components.js b/server/sonar-web/src/main/js/apps/overview/common-components.js index da03da65c2a..30decc3c7c8 100644 --- a/server/sonar-web/src/main/js/apps/overview/common-components.js +++ b/server/sonar-web/src/main/js/apps/overview/common-components.js @@ -27,10 +27,12 @@ export const DetailedMeasure = React.createClass({ return <div className="overview-detailed-measure"> <div className="overview-detailed-measure-nutshell"> - <span>{localizeMetric(this.props.metric)}</span> - <DrilldownLink component={this.props.component.key} metric={this.props.metric}> - <span className="overview-detailed-measure-value">{formatMeasure(measure, this.props.type)}</span> - </DrilldownLink> + <span className="overview-detailed-measure-name">{localizeMetric(this.props.metric)}</span> + <span className="overview-detailed-measure-value"> + <DrilldownLink component={this.props.component.key} metric={this.props.metric}> + {formatMeasure(measure, this.props.type)} + </DrilldownLink> + </span> {this.props.children} </div> {this.renderLeak()} diff --git a/server/sonar-web/src/main/js/apps/overview/coverage/bubble-chart.js b/server/sonar-web/src/main/js/apps/overview/coverage/bubble-chart.js deleted file mode 100644 index 826aca98364..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/coverage/bubble-chart.js +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; -import { DomainBubbleChart } from '../domain/bubble-chart'; - - -export class CoverageBubbleChart extends React.Component { - render () { - return <DomainBubbleChart {...this.props} - xMetric="complexity" - yMetric="coverage" - sizeMetrics={['sqale_index']}/>; - } -} diff --git a/server/sonar-web/src/main/js/apps/overview/coverage/coverage-details.js b/server/sonar-web/src/main/js/apps/overview/coverage/coverage-details.js deleted file mode 100644 index 3b0057038d0..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/coverage/coverage-details.js +++ /dev/null @@ -1,124 +0,0 @@ -import React from 'react'; - -import { getMeasures } from '../../../api/measures'; -import DrilldownLink from '../helpers/drilldown-link'; -import { formatMeasure } from '../../../helpers/measures'; - - -const METRICS = [ - 'coverage', - 'line_coverage', - 'branch_coverage', - 'it_coverage', - 'it_line_coverage', - 'it_branch_coverage', - 'overall_coverage', - 'overall_line_coverage', - 'overall_branch_coverage' -]; - - -export class CoverageDetails extends React.Component { - constructor () { - super(); - this.state = { measures: {} }; - } - - componentDidMount () { - this.requestDetails(); - } - - requestDetails () { - return getMeasures(this.props.component.key, METRICS).then(measures => { - this.setState({ measures }); - }); - } - - renderValue (value, metricKey) { - if (value != null) { - return <DrilldownLink component={this.props.component.key} metric={metricKey}> - {formatMeasure(value, 'PERCENT')} - </DrilldownLink>; - } else { - return '—'; - } - } - - renderCoverage (coverage, lineCoverage, branchCoverage, prefix) { - return <table className="data zebra"> - <tbody> - <tr> - <td>Coverage</td> - <td className="thin nowrap text-right"> - {this.renderValue(coverage, prefix + 'coverage')} - </td> - </tr> - <tr> - <td>Line Coverage</td> - <td className="thin nowrap text-right"> - {this.renderValue(lineCoverage, prefix + 'line_coverage')} - </td> - </tr> - <tr> - <td>Branch Coverage</td> - <td className="thin nowrap text-right"> - {this.renderValue(branchCoverage, prefix + 'branch_coverage')} - </td> - </tr> - </tbody> - </table>; - } - - renderUTCoverage () { - if (this.state.measures['coverage'] == null) { - return null; - } - return <div className="big-spacer-top"> - <h4 className="spacer-bottom">Unit Tests</h4> - {this.renderCoverage( - this.state.measures['coverage'], - this.state.measures['line_coverage'], - this.state.measures['branch_coverage'], - '')} - </div>; - } - - renderITCoverage () { - if (this.state.measures['it_coverage'] == null) { - return null; - } - return <div className="big-spacer-top"> - <h4 className="spacer-bottom">Integration Tests</h4> - {this.renderCoverage( - this.state.measures['it_coverage'], - this.state.measures['it_line_coverage'], - this.state.measures['it_branch_coverage'], - 'it_')} - </div>; - } - - renderOverallCoverage () { - if (this.state.measures['coverage'] == null || - this.state.measures['it_coverage'] == null || - this.state.measures['overall_coverage'] == null) { - return null; - } - return <div className="big-spacer-top"> - <h4 className="spacer-bottom">Overall</h4> - {this.renderCoverage( - this.state.measures['overall_coverage'], - this.state.measures['overall_line_coverage'], - this.state.measures['overall_branch_coverage'], - 'overall_')} - </div>; - } - - render () { - return <div className="overview-domain-section"> - <h2 className="overview-title">Coverage Details</h2> - {this.renderUTCoverage()} - {this.renderITCoverage()} - {this.renderOverallCoverage()} - </div>; - } -} diff --git a/server/sonar-web/src/main/js/apps/overview/coverage/coverage-measure.js b/server/sonar-web/src/main/js/apps/overview/coverage/coverage-measure.js new file mode 100644 index 00000000000..4ab36466fc7 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/coverage/coverage-measure.js @@ -0,0 +1,85 @@ +import React from 'react'; + +import { formatMeasure, formatMeasureVariation, localizeMetric } from '../../../helpers/measures'; +import DrilldownLink from '../helpers/drilldown-link'; +import { getShortType } from '../helpers/metrics'; +import { DonutChart } from '../../../components/charts/donut-chart'; + + +export const CoverageMeasure = React.createClass({ + renderLeakVariation () { + if (!this.props.leakPeriodDate) { + return null; + } + let leak = this.props.leak[this.props.metric]; + return <div className="overview-detailed-measure-leak"> + <span className="overview-detailed-measure-value"> + {formatMeasureVariation(leak, getShortType(this.props.type))} + </span> + </div>; + }, + + renderLeakValue () { + if (!this.props.leakPeriodDate) { + return null; + } + + if (!this.props.leakMetric) { + return <div className="overview-detailed-measure-leak"> </div>; + } + + let leak = this.props.leak[this.props.leakMetric]; + + let donutData = [ + { value: leak, fill: '#85bb43' }, + { value: 100 - leak, fill: '#d4333f' } + ]; + + return <div className="overview-detailed-measure-leak"> + <div className="overview-donut-chart"> + <DonutChart width="20" height="20" thickness="3" data={donutData}/> + </div> + <span className="overview-detailed-measure-value"> + <DrilldownLink component={this.props.component.key} metric={this.props.leakMetric} + period={this.props.leakPeriodIndex}> + {formatMeasure(leak, this.props.type)} + </DrilldownLink> + </span> + </div>; + }, + + renderDonut (measure) { + if (this.props.metric !== 'PERCENT') { + return null; + } + + let donutData = [ + { value: measure, fill: '#85bb43' }, + { value: 100 - measure, fill: '#d4333f' } + ]; + return <div className="overview-donut-chart"> + <DonutChart width="20" height="20" thickness="3" data={donutData}/> + </div>; + }, + + render () { + let measure = this.props.measures[this.props.metric]; + if (measure == null) { + return null; + } + + return <div className="overview-detailed-measure"> + <div className="overview-detailed-measure-nutshell"> + <span className="overview-detailed-measure-name">{localizeMetric(this.props.metric)}</span> + {this.renderDonut(measure)} + <span className="overview-detailed-measure-value"> + <DrilldownLink component={this.props.component.key} metric={this.props.metric}> + {formatMeasure(measure, this.props.type)} + </DrilldownLink> + </span> + </div> + {this.renderLeakValue()} + {this.renderLeakVariation()} + </div>; + } +}); diff --git a/server/sonar-web/src/main/js/apps/overview/coverage/main.js b/server/sonar-web/src/main/js/apps/overview/coverage/main.js index a4e8f90fa37..7971f22d182 100644 --- a/server/sonar-web/src/main/js/apps/overview/coverage/main.js +++ b/server/sonar-web/src/main/js/apps/overview/coverage/main.js @@ -1,36 +1,184 @@ +import _ from 'underscore'; +import d3 from 'd3'; import React from 'react'; -import { CoverageDetails } from './coverage-details'; -import { TestsDetails } from './tests-details'; -import { CoverageBubbleChart } from './bubble-chart'; -import { CoverageTimeline } from './timeline'; -import { CoverageTreemap } from './treemap'; +import { getMeasuresAndVariations } from '../../../api/measures'; +import { DetailedMeasure } from '../common-components'; +import { DomainTimeline } from '../timeline/domain-timeline'; +import { DomainTreemap } from '../domain/treemap'; +import { DomainBubbleChart } from '../domain/bubble-chart'; +import { getPeriodLabel, getPeriodDate } from './../helpers/period-label'; +import { TooltipsMixin } from '../../../components/mixins/tooltips-mixin'; +import { filterMetrics, filterMetricsForDomains } from '../helpers/metrics'; +import { Legend } from '../common-components'; +import { CHART_COLORS_RANGE_PERCENT } from '../../../helpers/constants'; +import { formatMeasure, formatMeasureVariation, localizeMetric } from '../../../helpers/measures'; +import { DonutChart } from '../../../components/charts/donut-chart'; +import DrilldownLink from '../helpers/drilldown-link'; +import { CoverageMeasure } from './coverage-measure'; -export default class extends React.Component { - render () { - return <div className="overview-detailed-page"> - <div className="overview-domain-header"> - <h2 className="overview-title">Coverage & Tests</h2> - </div> +const UT_COVERAGE_METRICS = ['coverage', 'new_coverage', 'branch_coverage', 'line_coverage', 'uncovered_conditions', + 'uncovered_lines']; +const IT_COVERAGE_METRICS = ['it_coverage', 'new_it_coverage', 'it_branch_coverage', 'it_line_coverage', + 'it_uncovered_conditions', 'it_uncovered_lines']; +const OVERALL_COVERAGE_METRICS = ['overall_coverage', 'new_overall_coverage', 'overall_branch_coverage', + 'overall_line_coverage', 'overall_uncovered_conditions', 'overall_uncovered_lines']; +const TEST_METRICS = ['tests', 'test_execution_time', 'test_errors', 'test_failures', 'skipped_tests', + 'test_success_density']; +const KNOWN_METRICS = [].concat(TEST_METRICS, OVERALL_COVERAGE_METRICS, UT_COVERAGE_METRICS, IT_COVERAGE_METRICS); - <a className="overview-detailed-page-back" href="#"> - <i className="icon-chevron-left"/> - </a> - <CoverageTimeline {...this.props}/> +export const CoverageMain = React.createClass({ + mixins: [TooltipsMixin], - <div className="flex-columns"> - <div className="flex-column flex-column-half"> - <CoverageDetails {...this.props}/> - </div> - <div className="flex-column flex-column-half"> - <TestsDetails {...this.props}/> + getInitialState() { + return { + ready: false, + leakPeriodLabel: getPeriodLabel(this.props.component.periods, this.props.leakPeriodIndex), + leakPeriodDate: getPeriodDate(this.props.component.periods, this.props.leakPeriodIndex) + }; + }, + + componentDidMount() { + this.requestMeasures().then(r => { + let measures = this.getMeasuresValues(r, 'value'); + let leak = this.getMeasuresValues(r, 'var' + this.props.leakPeriodIndex); + this.setState({ ready: true, measures, leak }); + }); + }, + + getMeasuresValues (measures, fieldKey) { + let values = {}; + Object.keys(measures).forEach(measureKey => { + values[measureKey] = measures[measureKey][fieldKey]; + }); + return values; + }, + + getMetricsForDomain() { + return this.props.metrics + .filter(metric => ['Tests', 'Tests (Integration)', 'Tests (Overall)'].indexOf(metric.domain) !== -1) + .map(metric => metric.key); + }, + + getMetricsForTimeline() { + return filterMetricsForDomains(this.props.metrics, ['Tests', 'Tests (Integration)', 'Tests (Overall)']); + }, + + getAllMetricsForTimeline() { + return filterMetrics(this.props.metrics); + }, + + requestMeasures () { + return getMeasuresAndVariations(this.props.component.key, this.getMetricsForDomain()); + }, + + renderLoading () { + return <div className="text-center"> + <i className="spinner spinner-margin"/> + </div>; + }, + + renderLegend () { + return <Legend leakPeriodDate={this.state.leakPeriodDate} leakPeriodLabel={this.state.leakPeriodLabel}/>; + }, + + renderOtherMeasures() { + let metrics = filterMetricsForDomains(this.props.metrics, ['Tests', 'Tests (Integration)', 'Tests (Overall)']) + .filter(metric => KNOWN_METRICS.indexOf(metric.key) === -1) + .map(metric => metric.key); + return this.renderListOfMeasures(metrics); + }, + + renderUTCoverage () { + let hasBothTypes = this.state.measures['coverage'] != null && this.state.measures['it_coverage'] != null; + if (!hasBothTypes) { + return null; + } + return <div className="overview-detailed-measures-list"> + <CoverageMeasure {...this.props} {...this.state} metric="coverage" leakMetric="new_coverage" type="PERCENT"/> + <CoverageMeasure {...this.props} {...this.state} metric="line_coverage" leakMetric="new_line_coverage" type="PERCENT"/> + <CoverageMeasure {...this.props} {...this.state} metric="branch_coverage" leakMetric="new_branch_coverage" type="PERCENT"/> + + <CoverageMeasure {...this.props} {...this.state} metric="uncovered_lines" type="INT"/> + <CoverageMeasure {...this.props} {...this.state} metric="uncovered_conditions" type="INT"/> + </div>; + }, + + renderITCoverage () { + let hasBothTypes = this.state.measures['coverage'] != null && this.state.measures['it_coverage'] != null; + if (!hasBothTypes) { + return null; + } + return <div className="overview-detailed-measures-list"> + <CoverageMeasure {...this.props} {...this.state} metric="it_coverage" leakMetric="new_it_coverage" type="PERCENT"/> + <CoverageMeasure {...this.props} {...this.state} metric="it_line_coverage" leakMetric="new_it_line_coverage" type="PERCENT"/> + <CoverageMeasure {...this.props} {...this.state} metric="it_branch_coverage" leakMetric="new_it_branch_coverage" type="PERCENT"/> + + <CoverageMeasure {...this.props} {...this.state} metric="it_uncovered_lines" type="INT"/> + <CoverageMeasure {...this.props} {...this.state} metric="it_uncovered_conditions" type="INT"/> + </div>; + }, + + renderOverallCoverage () { + return <div className="overview-detailed-measures-list"> + <CoverageMeasure {...this.props} {...this.state} metric="overall_coverage" leakMetric="new_overall_coverage" type="PERCENT"/> + <CoverageMeasure {...this.props} {...this.state} metric="overall_line_coverage" leakMetric="new_overall_line_coverage" type="PERCENT"/> + <CoverageMeasure {...this.props} {...this.state} metric="overall_branch_coverage" leakMetric="new_overall_branch_coverage" type="PERCENT"/> + + <CoverageMeasure {...this.props} {...this.state} metric="overall_uncovered_lines" type="INT"/> + <CoverageMeasure {...this.props} {...this.state} metric="overall_uncovered_conditions" type="INT"/> + </div>; + }, + + renderListOfMeasures(list) { + let metrics = list + .map(key => _.findWhere(this.props.metrics, { key })) + .map(metric => { + return <DetailedMeasure key={metric.key} {...this.props} {...this.state} metric={metric.key} + type={metric.type}/>; + }); + return <div className="overview-detailed-measures-list">{metrics}</div>; + }, + + render () { + if (!this.state.ready) { + return this.renderLoading(); + } + let treemapScale = d3.scale.linear() + .domain([0, 100]) + .range(CHART_COLORS_RANGE_PERCENT); + return <div className="overview-detailed-page"> + <div className="overview-domain-charts"> + <div className="overview-domain"> + <div className="overview-domain-header"> + <div className="overview-title">Tests Overview</div> + {this.renderLegend()} + </div> + {this.renderOverallCoverage()} + {this.renderUTCoverage()} + {this.renderITCoverage()} + {this.renderListOfMeasures(TEST_METRICS)} + {this.renderOtherMeasures()} </div> + <DomainBubbleChart {...this.props} + xMetric="complexity" + yMetric="overall_coverage" + sizeMetrics={['overall_uncovered_lines']}/> </div> - <CoverageBubbleChart {...this.props}/> - <CoverageTreemap {...this.props}/> + <div className="overview-domain-charts"> + <DomainTimeline {...this.props} {...this.state} + initialMetric="overall_coverage" + metrics={this.getMetricsForTimeline()} + allMetrics={this.getAllMetricsForTimeline()}/> + <DomainTreemap {...this.props} + sizeMetric="ncloc" + colorMetric="overall_coverage" + scale={treemapScale}/> + </div> </div>; + } -} +}); diff --git a/server/sonar-web/src/main/js/apps/overview/coverage/tests-details.js b/server/sonar-web/src/main/js/apps/overview/coverage/tests-details.js deleted file mode 100644 index 0bc37699561..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/coverage/tests-details.js +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; - -import { DomainMeasuresList } from '../domain/measures-list'; - - -const METRICS = [ - 'tests', - 'skipped_tests', - 'test_errors', - 'test_failures', - 'test_execution_time', - 'test_success_density' -]; - - -export class TestsDetails extends React.Component { - render () { - return <div className="overview-domain-section"> - <h2 className="overview-title">Tests</h2> - <DomainMeasuresList {...this.props} metricsToDisplay={METRICS}/> - </div>; - } -} diff --git a/server/sonar-web/src/main/js/apps/overview/coverage/timeline.js b/server/sonar-web/src/main/js/apps/overview/coverage/timeline.js deleted file mode 100644 index 60e14a00b6b..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/coverage/timeline.js +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; - -import { DomainTimeline } from '../domain/timeline'; -import { filterMetricsForDomains } from '../helpers/metrics'; - - -const DOMAINS = [ - 'Tests', - 'Tests (Integration)', - 'Tests (Overall)' -]; - - -export class CoverageTimeline extends React.Component { - render () { - return <DomainTimeline {...this.props} - initialMetric="coverage" - metrics={filterMetricsForDomains(this.props.metrics, DOMAINS)}/>; - } -} diff --git a/server/sonar-web/src/main/js/apps/overview/coverage/treemap.js b/server/sonar-web/src/main/js/apps/overview/coverage/treemap.js deleted file mode 100644 index 251d5f0751e..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/coverage/treemap.js +++ /dev/null @@ -1,20 +0,0 @@ -import d3 from 'd3'; -import React from 'react'; - -import { DomainTreemap } from '../domain/treemap'; - - -const COLORS_5 = ['#ee0000', '#f77700', '#ffee00', '#80cc00', '#00aa00']; - - -export class CoverageTreemap extends React.Component { - render () { - let scale = d3.scale.linear() - .domain([0, 25, 50, 75, 100]) - .range(COLORS_5); - return <DomainTreemap {...this.props} - sizeMetric="ncloc" - colorMetric="coverage" - scale={scale}/>; - } -} diff --git a/server/sonar-web/src/main/js/apps/overview/main/coverage.js b/server/sonar-web/src/main/js/apps/overview/main/coverage.js index fa265140f0e..48e9d888ba8 100644 --- a/server/sonar-web/src/main/js/apps/overview/main/coverage.js +++ b/server/sonar-web/src/main/js/apps/overview/main/coverage.js @@ -45,7 +45,7 @@ export const GeneralCoverage = React.createClass({ } return <Domain> - <DomainHeader title="Tests"/> + <DomainHeader title="Tests" linkTo="/tests"/> <DomainPanel domain="coverage"> <DomainNutshell> diff --git a/server/sonar-web/src/main/js/apps/overview/overview.js b/server/sonar-web/src/main/js/apps/overview/overview.js index 21c5eb3ccc0..01cf406a54d 100644 --- a/server/sonar-web/src/main/js/apps/overview/overview.js +++ b/server/sonar-web/src/main/js/apps/overview/overview.js @@ -5,6 +5,7 @@ import GeneralMain from './main/main'; import Meta from './meta'; import { SizeMain } from './size/main'; import { DuplicationsMain } from './duplications/main'; +import { CoverageMain } from './coverage/main'; import { getMetrics } from '../../api/metrics'; import { RouterMixin } from '../../components/router/router'; @@ -53,6 +54,12 @@ export const Overview = React.createClass({ </div>; }, + renderTests () { + return <div className="overview"> + <CoverageMain {...this.props} {...this.state}/> + </div>; + }, + render () { if (!this.state.ready) { return this.renderLoading(); @@ -64,6 +71,8 @@ export const Overview = React.createClass({ return this.renderSize(); case '/duplications': return this.renderDuplications(); + case '/tests': + return this.renderTests(); default: throw new Error('Unknown route: ' + this.state.route); } diff --git a/server/sonar-web/src/main/js/components/charts/bubble-chart.js b/server/sonar-web/src/main/js/components/charts/bubble-chart.js index 391bf25c786..ab1d1833358 100644 --- a/server/sonar-web/src/main/js/components/charts/bubble-chart.js +++ b/server/sonar-web/src/main/js/components/charts/bubble-chart.js @@ -166,6 +166,9 @@ export const BubbleChart = React.createClass({ .domain([0, d3.max(this.props.items, d => d.size)]) .range(this.props.sizeRange); + let xScaleOriginal = xScale.copy(); + let yScaleOriginal = yScale.copy(); + xScale.range(this.getXRange(xScale, sizeScale, availableWidth)); yScale.range(this.getYRange(yScale, sizeScale, availableHeight)); @@ -180,9 +183,9 @@ export const BubbleChart = React.createClass({ return <svg className="bubble-chart" width={this.state.width} height={this.state.height}> <g transform={`translate(${this.props.padding[3]}, ${this.props.padding[0]})`}> {this.renderXGrid(xScale, yScale)} - {this.renderXTicks(xScale, yScale)} + {this.renderXTicks(xScale, yScaleOriginal)} {this.renderYGrid(xScale, yScale)} - {this.renderYTicks(xScale, yScale)} + {this.renderYTicks(xScaleOriginal, yScale)} {bubbles} </g> </svg>; diff --git a/server/sonar-web/src/main/js/components/charts/donut-chart.js b/server/sonar-web/src/main/js/components/charts/donut-chart.js new file mode 100644 index 00000000000..b54597dd208 --- /dev/null +++ b/server/sonar-web/src/main/js/components/charts/donut-chart.js @@ -0,0 +1,59 @@ +import d3 from 'd3'; +import React from 'react'; + +import { ResizeMixin } from './../mixins/resize-mixin'; +import { TooltipsMixin } from './../mixins/tooltips-mixin'; + + +const Sector = React.createClass({ + render() { + let arc = d3.svg.arc() + .outerRadius(this.props.radius) + .innerRadius(this.props.radius - this.props.thickness); + return <path d={arc(this.props.data)} style={{ fill: this.props.fill }}/>; + } +}); + + +export const DonutChart = React.createClass({ + mixins: [ResizeMixin, TooltipsMixin], + + propTypes: { + data: React.PropTypes.arrayOf(React.PropTypes.object).isRequired + }, + + getDefaultProps() { + return { thickness: 6, padding: [0, 0, 0, 0] }; + }, + + getInitialState() { + return { width: this.props.width, height: this.props.height }; + }, + + render () { + if (!this.state.width || !this.state.height) { + return <div/>; + } + + let availableWidth = this.state.width - this.props.padding[1] - this.props.padding[3]; + let availableHeight = this.state.height - this.props.padding[0] - this.props.padding[2]; + + let size = Math.min(availableWidth, availableHeight), + radius = Math.floor(size / 2); + + let pie = d3.layout.pie() + .sort(null) + .value(d => d.value); + let sectors = pie(this.props.data).map((d, i) => { + return <Sector key={i} data={d} radius={radius} fill={this.props.data[i].fill} thickness={this.props.thickness}/>; + }); + + return <svg className="donut-chart" width={this.state.width} height={this.state.height}> + <g transform={`translate(${this.props.padding[3]}, ${this.props.padding[0]})`}> + <g transform={`translate(${radius}, ${radius})`}> + {sectors} + </g> + </g> + </svg>; + } +}); diff --git a/server/sonar-web/src/main/less/init/misc.less b/server/sonar-web/src/main/less/init/misc.less index 272d7552283..bcd74fe3882 100644 --- a/server/sonar-web/src/main/less/init/misc.less +++ b/server/sonar-web/src/main/less/init/misc.less @@ -96,6 +96,10 @@ td.big-spacer-top { padding-top: 16px; } border-radius: 2px; } +.flex-1 { + flex: 1; +} + // Background Color diff --git a/server/sonar-web/src/main/less/pages/overview.less b/server/sonar-web/src/main/less/pages/overview.less index e85da65f77f..5f5cee8629f 100644 --- a/server/sonar-web/src/main/less/pages/overview.less +++ b/server/sonar-web/src/main/less/pages/overview.less @@ -245,15 +245,24 @@ overflow: hidden; } +.overview-detailed-measures-list + .overview-detailed-measures-list { + margin-top: 40px; +} + .overview-detailed-measure { display: flex; background-color: #fff; } .overview-detailed-measure-nutshell, -.overview-detailed-measure-leak { +.overview-detailed-measure-leak, +.overview-detailed-measure-chart { position: relative; padding: 7px 10px; + + & & { + padding-right: 0; + } } .overview-detailed-measure-nutshell { @@ -270,6 +279,10 @@ text-align: center; } +.overview-detailed-measure-name { + flex: 1; +} + .overview-detailed-measure-value { font-size: 16px; } @@ -483,6 +496,14 @@ } } +.overview-donut-chart { + display: inline-block; + vertical-align: top; + margin-right: 8px; +} + + + /* * Responsive Stuff */ |