}
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">
<li>Size: {this.getSizeMetricsTitle()}</li>
</ul>
</div>
- <div>
+ <div className="overview-bubble-chart">
{this.renderBubbleChart()}
</div>
</div>;
+++ /dev/null
-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']}/>;
- }
-}
+++ /dev/null
-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>;
- }
-}
+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>;
+
}
-}
+});
+++ /dev/null
-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)}/>;
- }
-}
+++ /dev/null
-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}/>;
- }
-}
render () {
return <Domain>
- <DomainHeader title="Duplications"/>
+ <DomainHeader title="Duplications" linkTo="/duplications"/>
<DomainPanel domain="duplications">
<DomainNutshell>
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';
</div>;
},
+ renderDuplications () {
+ return <div className="overview">
+ <DuplicationsMain {...this.props} {...this.state}/>
+ </div>;
+ },
+
render () {
if (!this.state.ready) {
return this.renderLoading();
return this.renderMain();
case '/size':
return this.renderSize();
+ case '/duplications':
+ return this.renderDuplications();
default:
throw new Error('Unknown route: ' + this.state.route);
}
+import _ from 'underscore';
import d3 from 'd3';
import React from 'react';
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>;
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>;
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]})`}>
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'];
.overview-detailed-page {
flex: 1;
+ animation: fadeIn 0.5s forwards;
}
.overview-detailed-measures-list {
.overview-domain-charts {
display: flex;
- .overview-domain-chart {
+ & > .overview-domain,
+ & > .overview-domain-chart {
flex: 1;
}
+
+ & > .overview-domain {
+ max-width: 560px;
+ }
}
.overview-domain-chart {
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
*/
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 %>