summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStas Vilchik <vilchiks@gmail.com>2015-11-09 14:07:58 +0100
committerStas Vilchik <vilchiks@gmail.com>2015-11-09 17:24:30 +0100
commit5a0ff33ea0c8496aa923dba9e64ca642d79898ed (patch)
tree90fd3fc7abbbcc4d79b1bc8c26c876b6976b6e4d
parentae7efa83ddab2008efab798ef9b8b119009aca69 (diff)
downloadsonarqube-5a0ff33ea0c8496aa923dba9e64ca642d79898ed.tar.gz
sonarqube-5a0ff33ea0c8496aa923dba9e64ca642d79898ed.zip
SONAR-6361 add detailed "Duplications" panel for the "Overview" main page
-rw-r--r--server/sonar-web/src/main/js/apps/overview/domain/bubble-chart.js4
-rw-r--r--server/sonar-web/src/main/js/apps/overview/duplications/bubble-chart.js12
-rw-r--r--server/sonar-web/src/main/js/apps/overview/duplications/duplications-details.js21
-rw-r--r--server/sonar-web/src/main/js/apps/overview/duplications/main.js126
-rw-r--r--server/sonar-web/src/main/js/apps/overview/duplications/timeline.js16
-rw-r--r--server/sonar-web/src/main/js/apps/overview/duplications/treemap.js20
-rw-r--r--server/sonar-web/src/main/js/apps/overview/main/duplications.js2
-rw-r--r--server/sonar-web/src/main/js/apps/overview/overview.js9
-rw-r--r--server/sonar-web/src/main/js/components/charts/bubble-chart.js37
-rw-r--r--server/sonar-web/src/main/js/helpers/constants.js2
-rw-r--r--server/sonar-web/src/main/less/pages/overview.less40
-rw-r--r--server/sonar-web/src/main/webapp/WEB-INF/app/views/overview/index.html.erb2
12 files changed, 178 insertions, 113 deletions
diff --git a/server/sonar-web/src/main/js/apps/overview/domain/bubble-chart.js b/server/sonar-web/src/main/js/apps/overview/domain/bubble-chart.js
index 58c65c45ad1..e112d8a230d 100644
--- a/server/sonar-web/src/main/js/apps/overview/domain/bubble-chart.js
+++ b/server/sonar-web/src/main/js/apps/overview/domain/bubble-chart.js
@@ -101,7 +101,7 @@ export class DomainBubbleChart extends React.Component {
}
render () {
- return <div className="overview-bubble-chart overview-domain-dark">
+ return <div className="overview-domain overview-domain-chart">
<div className="overview-domain-header">
<h2 className="overview-title">Project Files</h2>
<ul className="list-inline small">
@@ -110,7 +110,7 @@ export class DomainBubbleChart extends React.Component {
<li>Size: {this.getSizeMetricsTitle()}</li>
</ul>
</div>
- <div>
+ <div className="overview-bubble-chart">
{this.renderBubbleChart()}
</div>
</div>;
diff --git a/server/sonar-web/src/main/js/apps/overview/duplications/bubble-chart.js b/server/sonar-web/src/main/js/apps/overview/duplications/bubble-chart.js
deleted file mode 100644
index 299a47978f1..00000000000
--- a/server/sonar-web/src/main/js/apps/overview/duplications/bubble-chart.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import React from 'react';
-import { DomainBubbleChart } from '../domain/bubble-chart';
-
-
-export class DuplicationsBubbleChart extends React.Component {
- render () {
- return <DomainBubbleChart {...this.props}
- xMetric="ncloc"
- yMetric="duplicated_blocks"
- sizeMetrics={['duplicated_lines']}/>;
- }
-}
diff --git a/server/sonar-web/src/main/js/apps/overview/duplications/duplications-details.js b/server/sonar-web/src/main/js/apps/overview/duplications/duplications-details.js
deleted file mode 100644
index b1b9ec79138..00000000000
--- a/server/sonar-web/src/main/js/apps/overview/duplications/duplications-details.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import React from 'react';
-
-import { DomainMeasuresList } from '../domain/measures-list';
-
-
-const METRICS = [
- 'duplicated_blocks',
- 'duplicated_files',
- 'duplicated_lines',
- 'duplicated_lines_density'
-];
-
-
-export class DuplicationsDetails extends React.Component {
- render () {
- return <div className="overview-domain-section">
- <h2 className="overview-title">Duplications</h2>
- <DomainMeasuresList {...this.props} metricsToDisplay={METRICS}/>
- </div>;
- }
-}
diff --git a/server/sonar-web/src/main/js/apps/overview/duplications/main.js b/server/sonar-web/src/main/js/apps/overview/duplications/main.js
index 02aa9eceecc..a3aa1d482dd 100644
--- a/server/sonar-web/src/main/js/apps/overview/duplications/main.js
+++ b/server/sonar-web/src/main/js/apps/overview/duplications/main.js
@@ -1,31 +1,117 @@
+import d3 from 'd3';
import React from 'react';
-import { DuplicationsDetails } from './duplications-details';
-import { DuplicationsBubbleChart } from './bubble-chart';
-import { DuplicationsTimeline } from './timeline';
-import { DuplicationsTreemap } 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';
-export default class extends React.Component {
- render () {
- return <div className="overview-detailed-page">
- <div className="overview-domain-header">
- <h2 className="overview-title">Duplications</h2>
- </div>
+export const DuplicationsMain = React.createClass({
+ mixins: [TooltipsMixin],
+
+ 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 => ['Duplication'].indexOf(metric.domain) !== -1)
+ .map(metric => metric.key);
+ },
+
+ getMetricsForTimeline() {
+ return filterMetricsForDomains(this.props.metrics, ['Duplication']);
+ },
+
+ getAllMetricsForTimeline() {
+ return filterMetrics(this.props.metrics);
+ },
- <a className="overview-detailed-page-back" href="#">
- <i className="icon-chevron-left"/>
- </a>
+ requestMeasures () {
+ return getMeasuresAndVariations(this.props.component.key, this.getMetricsForDomain());
+ },
- <DuplicationsTimeline {...this.props}/>
- <div className="flex-columns">
- <div className="flex-column flex-column-half">
- <DuplicationsDetails {...this.props}/>
+ renderLoading () {
+ return <div className="text-center">
+ <i className="spinner spinner-margin"/>
+ </div>;
+ },
+
+ renderLegend () {
+ return <Legend leakPeriodDate={this.state.leakPeriodDate} leakPeriodLabel={this.state.leakPeriodLabel}/>;
+ },
+
+ renderMeasures() {
+ let metrics = filterMetricsForDomains(this.props.metrics, ['Duplication'])
+ .map(metric => {
+ return <DetailedMeasure key={metric.key} {...this.props} {...this.state} metric={metric.key}
+ type={metric.type}/>;
+ });
+ return <div>{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">Duplications Overview</div>
+ {this.renderLegend()}
+ </div>
+ <div className="overview-detailed-measures-list">
+ {this.renderMeasures()}
+ </div>
</div>
+ <DomainBubbleChart {...this.props}
+ xMetric="ncloc"
+ yMetric="duplicated_blocks"
+ sizeMetrics={['duplicated_lines']}/>
</div>
- <DuplicationsBubbleChart {...this.props}/>
- <DuplicationsTreemap {...this.props}/>
+ <div className="overview-domain-charts">
+ <DomainTimeline {...this.props} {...this.state}
+ initialMetric="duplicated_lines_density"
+ metrics={this.getMetricsForTimeline()}
+ allMetrics={this.getAllMetricsForTimeline()}/>
+ <DomainTreemap {...this.props}
+ sizeMetric="ncloc"
+ colorMetric="duplicated_lines_density"
+ scale={treemapScale}/>
+ </div>
</div>;
+
}
-}
+});
diff --git a/server/sonar-web/src/main/js/apps/overview/duplications/timeline.js b/server/sonar-web/src/main/js/apps/overview/duplications/timeline.js
deleted file mode 100644
index abd6987af7d..00000000000
--- a/server/sonar-web/src/main/js/apps/overview/duplications/timeline.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import React from 'react';
-
-import { DomainTimeline } from '../domain/timeline';
-import { filterMetricsForDomains } from '../helpers/metrics';
-
-
-const DOMAINS = ['Duplication'];
-
-
-export class DuplicationsTimeline extends React.Component {
- render () {
- return <DomainTimeline {...this.props}
- initialMetric="duplicated_lines_density"
- metrics={filterMetricsForDomains(this.props.metrics, DOMAINS)}/>;
- }
-}
diff --git a/server/sonar-web/src/main/js/apps/overview/duplications/treemap.js b/server/sonar-web/src/main/js/apps/overview/duplications/treemap.js
deleted file mode 100644
index 019e78e4cab..00000000000
--- a/server/sonar-web/src/main/js/apps/overview/duplications/treemap.js
+++ /dev/null
@@ -1,20 +0,0 @@
-import d3 from 'd3';
-import React from 'react';
-
-import { DomainTreemap } from '../domain/treemap';
-
-
-const COLORS_5 = ['#00aa00', '#80cc00', '#ffee00', '#f77700', '#ee0000'];
-
-
-export class DuplicationsTreemap 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="duplicated_lines_density"
- scale={scale}/>;
- }
-}
diff --git a/server/sonar-web/src/main/js/apps/overview/main/duplications.js b/server/sonar-web/src/main/js/apps/overview/main/duplications.js
index 4ae63e785bd..db4c6e6c320 100644
--- a/server/sonar-web/src/main/js/apps/overview/main/duplications.js
+++ b/server/sonar-web/src/main/js/apps/overview/main/duplications.js
@@ -31,7 +31,7 @@ export const GeneralDuplications = React.createClass({
render () {
return <Domain>
- <DomainHeader title="Duplications"/>
+ <DomainHeader title="Duplications" linkTo="/duplications"/>
<DomainPanel domain="duplications">
<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 dae5318a477..21c5eb3ccc0 100644
--- a/server/sonar-web/src/main/js/apps/overview/overview.js
+++ b/server/sonar-web/src/main/js/apps/overview/overview.js
@@ -4,6 +4,7 @@ import Gate from './main/gate/gate';
import GeneralMain from './main/main';
import Meta from './meta';
import { SizeMain } from './size/main';
+import { DuplicationsMain } from './duplications/main';
import { getMetrics } from '../../api/metrics';
import { RouterMixin } from '../../components/router/router';
@@ -46,6 +47,12 @@ export const Overview = React.createClass({
</div>;
},
+ renderDuplications () {
+ return <div className="overview">
+ <DuplicationsMain {...this.props} {...this.state}/>
+ </div>;
+ },
+
render () {
if (!this.state.ready) {
return this.renderLoading();
@@ -55,6 +62,8 @@ export const Overview = React.createClass({
return this.renderMain();
case '/size':
return this.renderSize();
+ case '/duplications':
+ return this.renderDuplications();
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 2dd929c7c4c..391bf25c786 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
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import d3 from 'd3';
import React from 'react';
@@ -93,8 +94,7 @@ export const BubbleChart = React.createClass({
let x = xScale(tick);
let y1 = yScale.range()[0];
let y2 = yScale.range()[1];
- return <line key={index} x1={x} x2={x} y1={y1} y2={y2} className="bubble-chart-grid"
- shapeRendering="crispEdges" strokeWidth="0.3"/>;
+ return <line key={index} x1={x} x2={x} y1={y1} y2={y2} className="bubble-chart-grid"/>;
});
return <g ref="xGrid">{lines}</g>;
@@ -109,8 +109,7 @@ export const BubbleChart = React.createClass({
let y = yScale(tick);
let x1 = xScale.range()[0];
let x2 = xScale.range()[1];
- return <line key={index} x1={x1} x2={x2} y1={y} y2={y} className="bubble-chart-grid"
- shapeRendering="crispEdges" strokeWidth="0.3"/>;
+ return <line key={index} x1={x1} x2={x2} y1={y} y2={y} className="bubble-chart-grid"/>;
});
return <g ref="yGrid">{lines}</g>;
@@ -156,27 +155,27 @@ export const BubbleChart = React.createClass({
let availableHeight = this.state.height - this.props.padding[0] - this.props.padding[2];
let xScale = d3.scale.linear()
- .domain([0, d3.max(this.props.items, d => d.x)])
- .range([0, availableWidth])
- .nice();
+ .domain([0, d3.max(this.props.items, d => d.x)])
+ .range([0, availableWidth])
+ .nice();
let yScale = d3.scale.linear()
- .domain([0, d3.max(this.props.items, d => d.y)])
- .range([availableHeight, 0])
- .nice();
+ .domain([0, d3.max(this.props.items, d => d.y)])
+ .range([availableHeight, 0])
+ .nice();
let sizeScale = d3.scale.linear()
- .domain([0, d3.max(this.props.items, d => d.size)])
- .range(this.props.sizeRange);
+ .domain([0, d3.max(this.props.items, d => d.size)])
+ .range(this.props.sizeRange);
xScale.range(this.getXRange(xScale, sizeScale, availableWidth));
yScale.range(this.getYRange(yScale, sizeScale, availableHeight));
- let bubbles = this.props.items
- .map((item, index) => {
- return <Bubble key={index}
- tooltip={item.tooltip}
- link={item.link}
- x={xScale(item.x)} y={yScale(item.y)} r={sizeScale(item.size)}/>;
- });
+ let bubbles = _.sortBy(this.props.items, (a, b) => b.size - a.size)
+ .map((item, index) => {
+ return <Bubble key={index}
+ tooltip={item.tooltip}
+ link={item.link}
+ x={xScale(item.x)} y={yScale(item.y)} r={sizeScale(item.size)}/>;
+ });
return <svg className="bubble-chart" width={this.state.width} height={this.state.height}>
<g transform={`translate(${this.props.padding[3]}, ${this.props.padding[0]})`}>
diff --git a/server/sonar-web/src/main/js/helpers/constants.js b/server/sonar-web/src/main/js/helpers/constants.js
index 392adf2202a..d1ba85c5395 100644
--- a/server/sonar-web/src/main/js/helpers/constants.js
+++ b/server/sonar-web/src/main/js/helpers/constants.js
@@ -1,2 +1,4 @@
export const SEVERITIES = ['BLOCKER', 'CRITICAL', 'MAJOR', 'MINOR', 'INFO'];
export const STATUSES = ['OPEN', 'REOPENED', 'CONFIRMED', 'RESOLVED', 'CLOSED'];
+
+export const CHART_COLORS_RANGE_PERCENT = ['#00aa00', '#80cc00', '#ffee00', '#f77700', '#ee0000'];
diff --git a/server/sonar-web/src/main/less/pages/overview.less b/server/sonar-web/src/main/less/pages/overview.less
index 1379e215a94..e85da65f77f 100644
--- a/server/sonar-web/src/main/less/pages/overview.less
+++ b/server/sonar-web/src/main/less/pages/overview.less
@@ -236,6 +236,7 @@
.overview-detailed-page {
flex: 1;
+ animation: fadeIn 0.5s forwards;
}
.overview-detailed-measures-list {
@@ -316,9 +317,14 @@
.overview-domain-charts {
display: flex;
- .overview-domain-chart {
+ & > .overview-domain,
+ & > .overview-domain-chart {
flex: 1;
}
+
+ & > .overview-domain {
+ max-width: 560px;
+ }
}
.overview-domain-chart {
@@ -445,6 +451,38 @@
align-items: center;
}
+.overview-bubble-chart {
+ padding: 10px;
+ border: 1px solid @barBorderColor;
+ box-sizing: border-box;
+ background-color: #fff;
+
+ .bubble-chart-bubble {
+ fill: @blue;
+ fill-opacity: 0.2;
+ stroke: @blue;
+ cursor: pointer;
+ transition: all 0.2s ease;
+
+ &:hover { fill-opacity: 0.8; }
+ }
+
+ .bubble-chart-grid {
+ shape-rendering: crispedges;
+ stroke: #eee;
+ }
+
+ .bubble-chart-tick {
+ fill: @secondFontColor;
+ font-size: 11px;
+ text-anchor: middle;
+ }
+
+ .bubble-chart-tick-y {
+ text-anchor: end;
+ }
+}
+
/*
* Responsive Stuff
*/
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/overview/index.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/overview/index.html.erb
index b9447a88eb5..d1004193763 100644
--- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/overview/index.html.erb
+++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/overview/index.html.erb
@@ -61,7 +61,7 @@
index: '<%= index -%>',
mode: '<%= @snapshot.period_mode(index) -%>',
modeParam: '<%= @snapshot.period_param(index) -%>',
- date: '<%= @snapshot.period_datetime(index).to_date.strftime('%FT%T%z') -%>'
+ date: '<%= @snapshot.period_datetime(index).strftime('%FT%T%z') -%>'
},
<% end %>
<% end %>