@@ -13,6 +13,7 @@ | |||
"browserify": "11.2.0", | |||
"browserify-shim": "3.8.10", | |||
"chai": "3.3.0", | |||
"chai-datetime": "^1.4.0", | |||
"classnames": "^2.2.0", | |||
"d3": "3.5.6", | |||
"del": "2.0.2", |
@@ -7,7 +7,11 @@ import { formatMeasure } from '../../../helpers/measures'; | |||
const HEIGHT = 80; | |||
export class ComplexityDistribution extends React.Component { | |||
export const ComplexityDistribution = React.createClass({ | |||
propTypes: { | |||
distribution: React.PropTypes.string.isRequired | |||
}, | |||
renderBarChart () { | |||
let data = this.props.distribution.split(';').map((point, index) => { | |||
let tokens = point.split('='); | |||
@@ -24,11 +28,11 @@ export class ComplexityDistribution extends React.Component { | |||
height={HEIGHT} | |||
barsWidth={10} | |||
padding={[25, 0, 25, 0]}/>; | |||
} | |||
}, | |||
render () { | |||
return <div className="overview-bar-chart"> | |||
{this.renderBarChart()} | |||
</div>; | |||
} | |||
} | |||
}); |
@@ -0,0 +1,53 @@ | |||
import classNames from 'classnames'; | |||
import React from 'react'; | |||
import { formatMeasure, formatMeasureVariation, localizeMetric } from '../../../helpers/measures'; | |||
import { DrilldownLink } from '../../../components/shared/drilldown-link'; | |||
import { DonutChart } from '../../../components/charts/donut-chart'; | |||
export const CoverageMeasure = React.createClass({ | |||
renderLeak () { | |||
if (this.props.leak == null) { | |||
return null; | |||
} | |||
return <div className="overview-detailed-measure-leak"> | |||
<span className="overview-detailed-measure-value"> | |||
{formatMeasureVariation(this.props.leak, 'PERCENT')} | |||
</span> | |||
</div>; | |||
}, | |||
renderDonut () { | |||
let donutData = [ | |||
{ value: this.props.measure, fill: '#85bb43' }, | |||
{ value: 100 - this.props.measure, fill: '#d4333f' } | |||
]; | |||
return <div className="overview-donut-chart"> | |||
<DonutChart width="90" height="90" thickness="3" data={donutData}/> | |||
<div className="overview-detailed-measure-value"> | |||
<DrilldownLink component={this.props.component.key} metric={this.props.metric} period={this.props.period}> | |||
{formatMeasure(this.props.measure, 'PERCENT')} | |||
</DrilldownLink> | |||
</div> | |||
</div>; | |||
}, | |||
render () { | |||
if (this.props.measure == null) { | |||
return null; | |||
} | |||
let className = classNames('overview-detailed-measure', { | |||
'overview-leak': this.props.period | |||
}); | |||
return <li className={className}> | |||
<div className="overview-detailed-measure-nutshell space-between"> | |||
<span className="overview-detailed-measure-name">{localizeMetric(this.props.metric)}</span> | |||
{this.renderDonut(this.props.measure)} | |||
</div> | |||
{this.renderLeak()} | |||
</li>; | |||
} | |||
}); |
@@ -0,0 +1,123 @@ | |||
import _ from 'underscore'; | |||
import React from 'react'; | |||
import { DetailedMeasure } from '../components/detailed-measure'; | |||
import { filterMetricsForDomains } from '../helpers/metrics'; | |||
import { CoverageMeasure } from '../components/coverage-measure'; | |||
const TEST_DOMAINS = ['Tests', 'Tests (Integration)', 'Tests (Overall)']; | |||
const UT_COVERAGE_METRICS = ['coverage', 'line_coverage', 'branch_coverage']; | |||
const UT_NEW_COVERAGE_METRICS = ['new_coverage', 'new_line_coverage', 'new_branch_coverage']; | |||
const IT_COVERAGE_METRICS = ['it_coverage', 'it_line_coverage', 'it_branch_coverage']; | |||
const IT_NEW_COVERAGE_METRICS = ['new_it_coverage', 'new_it_line_coverage', 'new_it_branch_coverage']; | |||
const OVERALL_COVERAGE_METRICS = ['overall_coverage', 'overall_line_coverage', 'overall_branch_coverage']; | |||
const OVERALL_NEW_COVERAGE_METRICS = ['new_overall_coverage', 'new_overall_line_coverage', | |||
'new_overall_branch_coverage']; | |||
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); | |||
export const CoverageMeasuresList = React.createClass({ | |||
renderOtherMeasures() { | |||
let metrics = filterMetricsForDomains(this.props.metrics, TEST_DOMAINS) | |||
.filter(metric => KNOWN_METRICS.indexOf(metric.key) === -1) | |||
.map(metric => metric.key); | |||
return this.renderListOfMeasures(metrics); | |||
}, | |||
renderCoverage (metrics) { | |||
let measures = metrics.map(metric => { | |||
return <CoverageMeasure key={metric} | |||
metric={metric} | |||
measure={this.props.measures[metric]} | |||
leak={this.props.leak[metric]} | |||
component={this.props.component}/>; | |||
}); | |||
return <div className="overview-detailed-measures-list overview-detailed-measures-list-inline"> | |||
{measures} | |||
</div>; | |||
}, | |||
shouldRenderTypedCoverage () { | |||
return this.props.measures['coverage'] != null && this.props.measures['it_coverage'] != null; | |||
}, | |||
renderTypedCoverage (metrics) { | |||
return this.shouldRenderTypedCoverage() ? this.renderCoverage(metrics) : null; | |||
}, | |||
renderNewCoverage (metrics) { | |||
let measures = metrics.map(metric => { | |||
return <CoverageMeasure key={metric} | |||
metric={metric} | |||
measure={this.props.leak[metric]} | |||
component={this.props.component} | |||
period={this.props.leakPeriodIndex}/>; | |||
}); | |||
return <div className="overview-detailed-measures-list overview-detailed-measures-list-inline"> | |||
{measures} | |||
</div>; | |||
}, | |||
renderTypedNewCoverage (metrics) { | |||
return this.shouldRenderTypedCoverage() ? this.renderNewCoverage(metrics) : null; | |||
}, | |||
renderUTCoverage () { | |||
return this.renderTypedCoverage(UT_COVERAGE_METRICS); | |||
}, | |||
renderUTNewCoverage () { | |||
return this.renderTypedNewCoverage(UT_NEW_COVERAGE_METRICS); | |||
}, | |||
renderITCoverage () { | |||
return this.renderTypedCoverage(IT_COVERAGE_METRICS); | |||
}, | |||
renderITNewCoverage () { | |||
return this.renderTypedNewCoverage(IT_NEW_COVERAGE_METRICS); | |||
}, | |||
renderOverallCoverage () { | |||
return this.renderCoverage(OVERALL_COVERAGE_METRICS); | |||
}, | |||
renderOverallNewCoverage () { | |||
return this.renderNewCoverage(OVERALL_NEW_COVERAGE_METRICS); | |||
}, | |||
renderListOfMeasures(list) { | |||
let metrics = list | |||
.map(key => _.findWhere(this.props.metrics, { key })) | |||
.map(metric => { | |||
return <DetailedMeasure key={metric.key} {...this.props} {...this.props} metric={metric.key} | |||
type={metric.type}/>; | |||
}); | |||
return <div className="overview-detailed-measures-list">{metrics}</div>; | |||
}, | |||
render () { | |||
return <div> | |||
{this.renderOverallCoverage()} | |||
{this.renderOverallNewCoverage()} | |||
{this.renderUTCoverage()} | |||
{this.renderUTNewCoverage()} | |||
{this.renderITCoverage()} | |||
{this.renderITNewCoverage()} | |||
{this.renderListOfMeasures(TEST_METRICS)} | |||
{this.renderOtherMeasures()} | |||
</div>; | |||
} | |||
}); |
@@ -1,9 +1,8 @@ | |||
import React from 'react'; | |||
import { formatMeasure, formatMeasureVariation, localizeMetric } from '../../helpers/measures'; | |||
import DrilldownLink from './helpers/drilldown-link'; | |||
import { getShortType } from './helpers/metrics'; | |||
import { DomainLeakTitle } from './main/components'; | |||
import { formatMeasure, formatMeasureVariation, localizeMetric } from '../../../helpers/measures'; | |||
import { DrilldownLink } from '../../../components/shared/drilldown-link'; | |||
import { getShortType } from '../helpers/metrics'; | |||
export const DetailedMeasure = React.createClass({ | |||
@@ -39,16 +38,3 @@ export const DetailedMeasure = React.createClass({ | |||
</div>; | |||
} | |||
}); | |||
export const Legend = React.createClass({ | |||
render() { | |||
if (!this.props.leakPeriodDate) { | |||
return null; | |||
} | |||
return <div className="overview-legend"> | |||
<span className="overview-legend-leak"/> | |||
<DomainLeakTitle label={this.props.leakPeriodLabel} date={this.props.leakPeriodDate}/> | |||
</div>; | |||
} | |||
}); |
@@ -1,5 +1,6 @@ | |||
import _ from 'underscore'; | |||
import React from 'react'; | |||
import { BubbleChart } from '../../../components/charts/bubble-chart'; | |||
import { getComponentUrl } from '../../../helpers/urls'; | |||
import { getFiles } from '../../../api/components'; | |||
@@ -101,8 +102,8 @@ export class DomainBubbleChart extends React.Component { | |||
} | |||
render () { | |||
return <div className="overview-domain overview-domain-chart"> | |||
<div className="overview-domain-header"> | |||
return <div className="overview-domain-chart"> | |||
<div className="overview-card-header"> | |||
<h2 className="overview-title">Project Files</h2> | |||
<ul className="list-inline small"> | |||
<li>X: {this.state.xMetric.name}</li> |
@@ -11,6 +11,7 @@ import { Timeline } from './timeline-chart'; | |||
const HEIGHT = 280; | |||
function parseValue (value, type) { | |||
return type === 'RATING' && typeof value === 'string' ? value.charCodeAt(0) - 'A'.charCodeAt(0) + 1 : value; | |||
} | |||
@@ -185,8 +186,8 @@ export const DomainTimeline = React.createClass({ | |||
}, | |||
render () { | |||
return <div className="overview-domain overview-domain-chart"> | |||
<div className="overview-domain-header"> | |||
return <div className="overview-domain-chart"> | |||
<div className="overview-card-header"> | |||
<div> | |||
<h2 className="overview-title">Timeline</h2> | |||
{this.renderTimelineMetricSelect()} |
@@ -5,6 +5,7 @@ import { Treemap } from '../../../components/charts/treemap'; | |||
import { getChildren } from '../../../api/components'; | |||
import { formatMeasure } from '../../../helpers/measures'; | |||
const HEIGHT = 302; | |||
@@ -79,8 +80,8 @@ export class DomainTreemap extends React.Component { | |||
render () { | |||
let color = this.props.colorMetric ? <li>Color: {this.state.colorMetric.name}</li> : null; | |||
return <div className="overview-domain overview-domain-chart"> | |||
<div className="overview-domain-header"> | |||
return <div className="overview-domain-chart"> | |||
<div className="overview-card-header"> | |||
<h2 className="overview-title">Treemap</h2> | |||
<ul className="list-inline small"> | |||
<li>Size: {this.state.sizeMetric.name}</li> |
@@ -1,8 +1,9 @@ | |||
import moment from 'moment'; | |||
import React from 'react'; | |||
import { formatMeasure, formatMeasureVariation, localizeMetric } from '../../../helpers/measures'; | |||
import DrilldownLink from '../helpers/drilldown-link'; | |||
import IssuesLink from '../helpers/issues-link'; | |||
import { formatMeasure, localizeMetric } from '../../../helpers/measures'; | |||
import { DrilldownLink } from '../../../components/shared/drilldown-link'; | |||
import { IssuesLink } from '../../../components/shared/issues-link'; | |||
import { getShortType } from '../helpers/metrics'; | |||
import SeverityHelper from '../../../components/shared/severity-helper'; | |||
@@ -109,6 +110,59 @@ export const AddedRemovedMeasure = React.createClass({ | |||
}); | |||
export const AddedRemovedDebt = React.createClass({ | |||
renderLeak () { | |||
if (!this.props.leakPeriodDate) { | |||
return null; | |||
} | |||
let leak = this.props.leak[this.props.metric]; | |||
let added = this.props.leak[this.props.leakMetric]; | |||
let removed = added - leak; | |||
return <div className="overview-detailed-measure-leak"> | |||
<ul> | |||
<li style={{ display: 'flex', alignItems: 'baseline' }}> | |||
<small className="flex-1 text-left">Added</small> | |||
<DrilldownLink className="text-danger" component={this.props.component.key} metric={this.props.leakMetric} | |||
period={this.props.leakPeriodIndex}> | |||
<span className="overview-detailed-measure-value"> | |||
{formatMeasure(added, getShortType(this.props.type))} | |||
</span> | |||
</DrilldownLink> | |||
</li> | |||
<li className="little-spacer-top" style={{ display: 'flex', alignItems: 'baseline' }}> | |||
<small className="flex-1 text-left">Removed</small> | |||
<span className="text-success"> | |||
{formatMeasure(removed, getShortType(this.props.type))} | |||
</span> | |||
</li> | |||
</ul> | |||
</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> | |||
<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()} | |||
</div>; | |||
} | |||
}); | |||
export const OnNewCodeMeasure = React.createClass({ | |||
renderLeak () { | |||
if (!this.props.leakPeriodDate) { | |||
@@ -121,11 +175,12 @@ export const OnNewCodeMeasure = React.createClass({ | |||
<ul> | |||
<li className="little-spacer-top" style={{ display: 'flex', alignItems: 'center' }}> | |||
<small className="flex-1 text-left">On New Code</small> | |||
<IssuesLink component={this.props.component.key} params={{ resolved: 'false' }}> | |||
<DrilldownLink component={this.props.component.key} metric={this.props.leakMetric} | |||
period={this.props.leakPeriodIndex}> | |||
<span className="overview-detailed-measure-value"> | |||
{formatMeasure(onNewCode, getShortType(this.props.type))} | |||
</span> | |||
</IssuesLink> | |||
</DrilldownLink> | |||
</li> | |||
</ul> | |||
</div>; | |||
@@ -172,12 +227,15 @@ export const SeverityMeasure = React.createClass({ | |||
let added = this.props.leak[this.getNewMetric()]; | |||
let removed = added - leak; | |||
let createdAfter = moment(this.props.leakPeriodDate).format('YYYY-MM-DDTHH:mm:ssZZ'); | |||
return <div className="overview-detailed-measure-leak"> | |||
<ul> | |||
<li style={{ display: 'flex', alignItems: 'baseline' }}> | |||
<small className="flex-1 text-left">Added</small> | |||
<IssuesLink className="text-danger" | |||
component={this.props.component.key} params={{ resolved: 'false' }}> | |||
component={this.props.component.key} | |||
params={{ resolved: 'false', severities: this.props.severity, createdAfter: createdAfter }}> | |||
<span className="overview-detailed-measure-value"> | |||
{formatMeasure(added, 'SHORT_INT')} | |||
</span> | |||
@@ -205,9 +263,10 @@ export const SeverityMeasure = React.createClass({ | |||
<SeverityHelper severity={this.props.severity}/> | |||
</span> | |||
<span className="overview-detailed-measure-value"> | |||
<DrilldownLink component={this.props.component.key} metric={this.getMetric()}> | |||
<IssuesLink component={this.props.component.key} | |||
params={{ resolved: 'false', severities: this.props.severity }}> | |||
{formatMeasure(measure, 'SHORT_INT')} | |||
</DrilldownLink> | |||
</IssuesLink> | |||
</span> | |||
{this.props.children} | |||
</div> |
@@ -1,6 +1,5 @@ | |||
import React from 'react'; | |||
import Assignee from '../../../components/shared/assignee-helper'; | |||
import { DomainHeader } from '../domain/header'; | |||
import { getComponentIssuesUrl } from '../../../helpers/urls'; | |||
import { formatMeasure } from '../../../helpers/measures'; | |||
@@ -8,7 +7,13 @@ import { formatMeasure } from '../../../helpers/measures'; | |||
export default class extends React.Component { | |||
render () { | |||
let rows = this.props.assignees.map(s => { | |||
let href = getComponentIssuesUrl(this.props.component.key, { statuses: 'OPEN,REOPENED', assignees: s.val }); | |||
let params = { statuses: 'OPEN,REOPENED' }; | |||
if (s.val) { | |||
params.assignees = s.val; | |||
} else { | |||
params.assigned = 'false'; | |||
} | |||
let href = getComponentIssuesUrl(this.props.component.key, params); | |||
return <tr key={s.val}> | |||
<td> | |||
<Assignee user={s.user}/> |
@@ -1,12 +1,12 @@ | |||
import React from 'react'; | |||
import { DomainHeader } from '../domain/header'; | |||
import { WordCloud } from '../../../components/charts/word-cloud'; | |||
import { getComponentIssuesUrl } from '../../../helpers/urls'; | |||
import { formatMeasure } from '../../../helpers/measures'; | |||
export default class extends React.Component { | |||
renderWordCloud () { | |||
export const IssuesTags = React.createClass({ | |||
render () { | |||
let tags = this.props.tags.map(tag => { | |||
let link = getComponentIssuesUrl(this.props.component.key, { resolved: 'false', tags: tag.val }); | |||
let tooltip = `Issues: ${formatMeasure(tag.count, 'SHORT_INT')}`; | |||
@@ -14,8 +14,4 @@ export default class extends React.Component { | |||
}); | |||
return <WordCloud items={tags}/>; | |||
} | |||
render () { | |||
return this.renderWordCloud(); | |||
} | |||
} | |||
}); |
@@ -6,14 +6,19 @@ import { formatMeasure } from '../../../helpers/measures'; | |||
import { getLanguages } from '../../../api/languages'; | |||
export class LanguageDistribution extends React.Component { | |||
export const LanguageDistribution = React.createClass({ | |||
propTypes: { | |||
distribution: React.PropTypes.string.isRequired, | |||
lines: React.PropTypes.number.isRequired | |||
}, | |||
componentDidMount () { | |||
this.requestLanguages(); | |||
} | |||
}, | |||
requestLanguages () { | |||
getLanguages().then(languages => this.setState({ languages })); | |||
} | |||
}, | |||
getLanguageName (langKey) { | |||
if (this.state && this.state.languages) { | |||
@@ -22,7 +27,7 @@ export class LanguageDistribution extends React.Component { | |||
} else { | |||
return langKey; | |||
} | |||
} | |||
}, | |||
renderBarChart () { | |||
let data = this.props.distribution.split(';').map((point, index) => { | |||
@@ -42,11 +47,11 @@ export class LanguageDistribution extends React.Component { | |||
height={data.length * 25} | |||
barsWidth={10} | |||
padding={[0, 50, 0, 80]}/>; | |||
} | |||
}, | |||
render () { | |||
return <div className="overview-bar-chart"> | |||
{this.renderBarChart()} | |||
</div>; | |||
} | |||
} | |||
}); |
@@ -0,0 +1,16 @@ | |||
import React from 'react'; | |||
import { DomainLeakTitle } from '../main/components'; | |||
export const Legend = React.createClass({ | |||
render() { | |||
if (!this.props.leakPeriodDate) { | |||
return null; | |||
} | |||
return <div className="overview-legend"> | |||
<span className="overview-legend-leak"/> | |||
<DomainLeakTitle label={this.props.leakPeriodLabel} date={this.props.leakPeriodDate}/> | |||
</div>; | |||
} | |||
}); |
@@ -1,85 +0,0 @@ | |||
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.type !== '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>; | |||
} | |||
}); |
@@ -1,184 +0,0 @@ | |||
import _ from 'underscore'; | |||
import d3 from 'd3'; | |||
import React from 'react'; | |||
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'; | |||
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); | |||
export const CoverageMain = 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 => ['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 overview-domain-fixed-width"> | |||
<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> | |||
<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>; | |||
} | |||
}); |
@@ -1,7 +0,0 @@ | |||
import React from 'react'; | |||
export class DomainHeader extends React.Component { | |||
render () { | |||
return <h2 className="overview-title">{this.props.title}</h2>; | |||
} | |||
} |
@@ -1,57 +0,0 @@ | |||
import _ from 'underscore'; | |||
import React from 'react'; | |||
import DrilldownLink from '../helpers/drilldown-link'; | |||
import { getMeasures } from '../../../api/measures'; | |||
import { formatMeasure } from '../../../helpers/measures'; | |||
export class DomainMeasuresList extends React.Component { | |||
constructor () { | |||
super(); | |||
this.state = { measures: {} }; | |||
} | |||
componentDidMount () { | |||
this.requestDetails(); | |||
} | |||
requestDetails () { | |||
return getMeasures(this.props.component.key, this.props.metricsToDisplay).then(measures => { | |||
this.setState({ measures }); | |||
}); | |||
} | |||
getMetricObject (metricKey) { | |||
return _.findWhere(this.props.metrics, { key: metricKey }); | |||
} | |||
renderValue (value, metricKey, metricType) { | |||
if (value != null) { | |||
return <DrilldownLink component={this.props.component.key} metric={metricKey}> | |||
{formatMeasure(value, metricType)} | |||
</DrilldownLink>; | |||
} else { | |||
return '—'; | |||
} | |||
} | |||
render () { | |||
let rows = this.props.metricsToDisplay.map(metric => { | |||
let metricObject = this.getMetricObject(metric); | |||
return <tr key={metric}> | |||
<td>{metricObject.name}</td> | |||
<td className="thin nowrap text-right"> | |||
{this.renderValue(this.state.measures[metric], metric, metricObject.type)} | |||
</td> | |||
</tr>; | |||
}); | |||
return <table className="data zebra"> | |||
<tbody>{rows}</tbody> | |||
</table>; | |||
} | |||
} | |||
DomainMeasuresList.propTypes = { | |||
metricsToDisplay: React.PropTypes.arrayOf(React.PropTypes.string).isRequired | |||
}; |
@@ -1,7 +0,0 @@ | |||
@@ -0,0 +1,119 @@ | |||
import d3 from 'd3'; | |||
import React from 'react'; | |||
import { getMeasuresAndVariations } from '../../../api/measures'; | |||
import { DomainTimeline } from '../components/domain-timeline'; | |||
import { DomainTreemap } from '../components/domain-treemap'; | |||
import { DomainBubbleChart } from '../components/domain-bubble-chart'; | |||
import { getPeriodLabel, getPeriodDate } from './../helpers/periods'; | |||
import { TooltipsMixin } from '../../../components/mixins/tooltips-mixin'; | |||
import { filterMetrics, filterMetricsForDomains } from '../helpers/metrics'; | |||
import { Legend } from '../components/legend'; | |||
import { CHART_COLORS_RANGE_PERCENT } from '../../../helpers/constants'; | |||
import { CoverageMeasuresList } from '../components/coverage-measures-list'; | |||
const TEST_DOMAINS = ['Tests', 'Tests (Integration)', 'Tests (Overall)']; | |||
export const CoverageMain = 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 => TEST_DOMAINS.indexOf(metric.domain) !== -1) | |||
.map(metric => metric.key); | |||
}, | |||
getMetricsForTimeline() { | |||
return filterMetricsForDomains(this.props.metrics, TEST_DOMAINS); | |||
}, | |||
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}/>; | |||
}, | |||
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-cards-list"> | |||
<div className="overview-card overview-card-fixed-width"> | |||
<div className="overview-card-header"> | |||
<div className="overview-title">Coverage Overview</div> | |||
{this.renderLegend()} | |||
</div> | |||
<CoverageMeasuresList {...this.props} {...this.state}/> | |||
</div> | |||
<div className="overview-card"> | |||
<DomainBubbleChart {...this.props} | |||
xMetric="complexity" | |||
yMetric="overall_coverage" | |||
sizeMetrics={['overall_uncovered_lines']}/> | |||
</div> | |||
</div> | |||
<div className="overview-cards-list"> | |||
<div className="overview-card"> | |||
<DomainTimeline {...this.props} {...this.state} | |||
initialMetric="overall_coverage" | |||
metrics={this.getMetricsForTimeline()} | |||
allMetrics={this.getAllMetricsForTimeline()}/> | |||
</div> | |||
<div className="overview-card"> | |||
<DomainTreemap {...this.props} | |||
sizeMetric="ncloc" | |||
colorMetric="overall_coverage" | |||
scale={treemapScale}/> | |||
</div> | |||
</div> | |||
</div>; | |||
} | |||
}); |
@@ -3,26 +3,22 @@ import d3 from 'd3'; | |||
import React from 'react'; | |||
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 { DetailedMeasure } from '../components/detailed-measure'; | |||
import { DomainTimeline } from '../components/domain-timeline'; | |||
import { DomainTreemap } from '../components/domain-treemap'; | |||
import { DomainBubbleChart } from '../components/domain-bubble-chart'; | |||
import { getPeriodLabel, getPeriodDate } from './../helpers/periods'; | |||
import { TooltipsMixin } from '../../../components/mixins/tooltips-mixin'; | |||
import { filterMetrics, filterMetricsForDomains } from '../helpers/metrics'; | |||
import { Legend } from '../common-components'; | |||
import { Legend } from '../components/legend'; | |||
import { CHART_COLORS_RANGE_PERCENT } from '../../../helpers/constants'; | |||
import { IssueMeasure, AddedRemovedMeasure, OnNewCodeMeasure, SeverityMeasure } from './issue-measure'; | |||
import { formatMeasure, formatMeasureVariation, localizeMetric } from '../../../helpers/measures'; | |||
import IssuesLink from '../helpers/issues-link'; | |||
import { Measure } from '../main/components'; | |||
import { getMetricName } from '../helpers/metrics'; | |||
import Tags from './tags'; | |||
import Assignees from './assignees'; | |||
import { AddedRemovedMeasure, AddedRemovedDebt, OnNewCodeMeasure, SeverityMeasure } from './../components/issue-measure'; | |||
import { IssuesTags } from './../components/issues-tags'; | |||
import Assignees from './../components/issues-assignees'; | |||
import { getFacets, extractAssignees } from '../../../api/issues'; | |||
import StatusHelper from '../../../components/shared/status-helper'; | |||
import Rating from '../helpers/rating'; | |||
import DrilldownLink from '../helpers/drilldown-link'; | |||
import { Rating } from '../../../components/shared/rating'; | |||
import { DrilldownLink } from '../../../components/shared/drilldown-link'; | |||
const KNOWN_METRICS = ['violations', 'sqale_index', 'sqale_rating', 'sqale_debt_ratio', 'blocker_violations', | |||
@@ -119,20 +115,17 @@ export const IssuesMain = React.createClass({ | |||
.domain([1, 2, 3, 4, 5]) | |||
.range(CHART_COLORS_RANGE_PERCENT); | |||
let rating = formatMeasure(this.state.measures['sqale_rating'], 'RATING'); | |||
return <div className="overview-detailed-page"> | |||
<div className="overview-domain-charts"> | |||
<div className="overview-domain overview-domain-fixed-width"> | |||
<div className="overview-domain-header"> | |||
<div className="overview-cards-list"> | |||
<div className="overview-card overview-card-fixed-width"> | |||
<div className="overview-card-header"> | |||
<div className="overview-title">Technical Debt Overview</div> | |||
{this.renderLegend()} | |||
</div> | |||
<div className="overview-detailed-measures-list overview-detailed-measures-list-inline"> | |||
<div className="overview-detailed-measure"> | |||
<div className="overview-detailed-measure overview-detailed-measure-rating"> | |||
<div className="overview-detailed-measure-nutshell"> | |||
<span className="overview-detailed-measure-name">SQALE Rating</span> | |||
<span className="overview-detailed-measure-value"> | |||
<DrilldownLink component={this.props.component.key} metric="sqale_rating"> | |||
<Rating value={this.state.measures['sqale_rating']}/> | |||
@@ -142,7 +135,7 @@ export const IssuesMain = React.createClass({ | |||
</div> | |||
<AddedRemovedMeasure {...this.props} {...this.state} | |||
metric="violations" leakMetric="new_violations" type="INT"/> | |||
<AddedRemovedMeasure {...this.props} {...this.state} | |||
<AddedRemovedDebt {...this.props} {...this.state} | |||
metric="sqale_index" leakMetric="new_technical_debt" type="WORK_DUR"/> | |||
<OnNewCodeMeasure {...this.props} {...this.state} | |||
metric="sqale_debt_ratio" leakMetric="new_sqale_debt_ratio" type="PERCENT"/> | |||
@@ -159,7 +152,7 @@ export const IssuesMain = React.createClass({ | |||
<div className="overview-detailed-measures-list overview-detailed-measures-list-inline"> | |||
<div className="overview-detailed-measure"> | |||
<div className="overview-detailed-measure-nutshell"> | |||
<Tags {...this.props} tags={this.state.tags}/> | |||
<IssuesTags {...this.props} tags={this.state.tags}/> | |||
</div> | |||
</div> | |||
<div className="overview-detailed-measure"> | |||
@@ -178,21 +171,28 @@ export const IssuesMain = React.createClass({ | |||
{this.renderOtherMeasures()} | |||
</div> | |||
</div> | |||
<DomainBubbleChart {...this.props} | |||
xMetric="violations" | |||
yMetric="sqale_index" | |||
sizeMetrics={['blocker_violations', 'critical_violations']}/> | |||
<div className="overview-card"> | |||
<DomainBubbleChart {...this.props} | |||
xMetric="violations" | |||
yMetric="sqale_index" | |||
sizeMetrics={['blocker_violations', 'critical_violations']}/> | |||
</div> | |||
</div> | |||
<div className="overview-domain-charts"> | |||
<DomainTimeline {...this.props} {...this.state} | |||
initialMetric="sqale_index" | |||
metrics={this.getMetricsForTimeline()} | |||
allMetrics={this.getAllMetricsForTimeline()}/> | |||
<DomainTreemap {...this.props} | |||
sizeMetric="ncloc" | |||
colorMetric="sqale_rating" | |||
scale={treemapScale}/> | |||
<div className="overview-cards-list"> | |||
<div className="overview-card"> | |||
<DomainTimeline {...this.props} {...this.state} | |||
initialMetric="sqale_index" | |||
metrics={this.getMetricsForTimeline()} | |||
allMetrics={this.getAllMetricsForTimeline()}/> | |||
</div> | |||
<div className="overview-card"> | |||
<DomainTreemap {...this.props} | |||
sizeMetric="ncloc" | |||
colorMetric="sqale_rating" | |||
scale={treemapScale}/> | |||
</div> | |||
</div> | |||
</div>; | |||
@@ -2,14 +2,14 @@ import d3 from 'd3'; | |||
import React from 'react'; | |||
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 { DetailedMeasure } from '../components/detailed-measure'; | |||
import { DomainTimeline } from '../components/domain-timeline'; | |||
import { DomainTreemap } from '../components/domain-treemap'; | |||
import { DomainBubbleChart } from '../components/domain-bubble-chart'; | |||
import { getPeriodLabel, getPeriodDate } from './../helpers/periods'; | |||
import { TooltipsMixin } from '../../../components/mixins/tooltips-mixin'; | |||
import { filterMetrics, filterMetricsForDomains } from '../helpers/metrics'; | |||
import { Legend } from '../common-components'; | |||
import { Legend } from '../components/legend'; | |||
import { CHART_COLORS_RANGE_PERCENT } from '../../../helpers/constants'; | |||
@@ -85,9 +85,9 @@ export const DuplicationsMain = React.createClass({ | |||
.domain([0, 100]) | |||
.range(CHART_COLORS_RANGE_PERCENT); | |||
return <div className="overview-detailed-page"> | |||
<div className="overview-domain-charts"> | |||
<div className="overview-domain overview-domain-fixed-width"> | |||
<div className="overview-domain-header"> | |||
<div className="overview-cards-list"> | |||
<div className="overview-card overview-card-fixed-width"> | |||
<div className="overview-card-header"> | |||
<div className="overview-title">Duplications Overview</div> | |||
{this.renderLegend()} | |||
</div> | |||
@@ -95,21 +95,27 @@ export const DuplicationsMain = React.createClass({ | |||
{this.renderMeasures()} | |||
</div> | |||
</div> | |||
<DomainBubbleChart {...this.props} | |||
xMetric="ncloc" | |||
yMetric="duplicated_lines" | |||
sizeMetrics={['duplicated_blocks']}/> | |||
<div className="overview-card"> | |||
<DomainBubbleChart {...this.props} | |||
xMetric="ncloc" | |||
yMetric="duplicated_lines" | |||
sizeMetrics={['duplicated_blocks']}/> | |||
</div> | |||
</div> | |||
<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 className="overview-cards-list"> | |||
<div className="overview-card"> | |||
<DomainTimeline {...this.props} {...this.state} | |||
initialMetric="duplicated_lines_density" | |||
metrics={this.getMetricsForTimeline()} | |||
allMetrics={this.getAllMetricsForTimeline()}/> | |||
</div> | |||
<div className="overview-card"> | |||
<DomainTreemap {...this.props} | |||
sizeMetric="ncloc" | |||
colorMetric="duplicated_lines_density" | |||
scale={treemapScale}/> | |||
</div> | |||
</div> | |||
</div>; | |||
@@ -1,16 +1,15 @@ | |||
import React from 'react'; | |||
import { DomainLeakTitle } from '../main/components'; | |||
import { LanguageDistribution } from './language-distribution'; | |||
import { ComplexityDistribution } from './complexity-distribution'; | |||
import { LanguageDistribution } from './../components/language-distribution'; | |||
import { ComplexityDistribution } from './../components/complexity-distribution'; | |||
import { getMeasuresAndVariations } from '../../../api/measures'; | |||
import { DetailedMeasure } from '../common-components'; | |||
import { DomainTimeline } from '../timeline/domain-timeline'; | |||
import { DomainTreemap } from '../domain/treemap'; | |||
import { getPeriodLabel, getPeriodDate } from './../helpers/period-label'; | |||
import { DetailedMeasure } from '../components/detailed-measure'; | |||
import { DomainTimeline } from '../components/domain-timeline'; | |||
import { DomainTreemap } from '../components/domain-treemap'; | |||
import { getPeriodLabel, getPeriodDate } from './../helpers/periods'; | |||
import { TooltipsMixin } from '../../../components/mixins/tooltips-mixin'; | |||
import { filterMetrics, filterMetricsForDomains } from '../helpers/metrics'; | |||
import { Legend } from '../common-components'; | |||
import { Legend } from '../components/legend'; | |||
export const SizeMain = React.createClass({ | |||
@@ -96,8 +95,8 @@ export const SizeMain = React.createClass({ | |||
return this.renderLoading(); | |||
} | |||
return <div className="overview-detailed-page"> | |||
<div className="overview-domain"> | |||
<div className="overview-domain-header"> | |||
<div className="overview-card"> | |||
<div className="overview-card-header"> | |||
<div className="overview-title">Size Overview</div> | |||
{this.renderLegend()} | |||
</div> | |||
@@ -135,12 +134,16 @@ export const SizeMain = React.createClass({ | |||
</div> | |||
</div> | |||
<div className="overview-domain-charts"> | |||
<DomainTimeline {...this.props} {...this.state} | |||
initialMetric="ncloc" | |||
metrics={this.getMetricsForTimeline()} | |||
allMetrics={this.getAllMetricsForTimeline()}/> | |||
<DomainTreemap {...this.props} sizeMetric="ncloc"/> | |||
<div className="overview-cards-list"> | |||
<div className="overview-card"> | |||
<DomainTimeline {...this.props} {...this.state} | |||
initialMetric="ncloc" | |||
metrics={this.getMetricsForTimeline()} | |||
allMetrics={this.getAllMetricsForTimeline()}/> | |||
</div> | |||
<div className="overview-card"> | |||
<DomainTreemap {...this.props} sizeMetric="ncloc"/> | |||
</div> | |||
</div> | |||
</div>; | |||
@@ -1,8 +1,19 @@ | |||
import React from 'react'; | |||
import Measure from '../../helpers/measure'; | |||
import { getPeriodLabel, getPeriodDate } from '../../helpers/period-label'; | |||
import DrilldownLink from '../../helpers/drilldown-link'; | |||
import { getPeriodLabel, getPeriodDate } from '../helpers/periods'; | |||
import { DrilldownLink } from '../../../components/shared/drilldown-link'; | |||
import { formatMeasure } from '../../../helpers/measures'; | |||
const Measure = React.createClass({ | |||
render() { | |||
if (this.props.value == null || isNaN(this.props.value)) { | |||
return null; | |||
} | |||
let formatted = formatMeasure(this.props.value, this.props.type); | |||
return <span>{formatted}</span>; | |||
} | |||
}); | |||
export default React.createClass({ |
@@ -1,34 +0,0 @@ | |||
import d3 from 'd3'; | |||
import React from 'react'; | |||
let 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 default React.createClass({ | |||
getDefaultProps() { | |||
return { | |||
size: 30, | |||
thickness: 6 | |||
}; | |||
}, | |||
render() { | |||
let radius = this.props.size / 2; | |||
let pie = d3.layout.pie() | |||
.sort(null) | |||
.value(d => d.value); | |||
let data = this.props.data; | |||
let sectors = pie(data).map((d, i) => { | |||
return <Sector key={i} data={d} fill={data[i].fill} radius={radius} thickness={this.props.thickness}/>; | |||
}); | |||
return <svg width={this.props.size} height={this.props.size}> | |||
<g transform={`translate(${radius}, ${radius})`}>{sectors}</g> | |||
</svg>; | |||
} | |||
}); |
@@ -1,13 +0,0 @@ | |||
import React from 'react'; | |||
import { formatMeasureVariation } from '../../../helpers/measures'; | |||
export default React.createClass({ | |||
render() { | |||
if (this.props.value == null || isNaN(this.props.value)) { | |||
return null; | |||
} | |||
let formatted = formatMeasureVariation(this.props.value, this.props.type); | |||
return <span>{formatted}</span>; | |||
} | |||
}); |
@@ -1,12 +0,0 @@ | |||
import React from 'react'; | |||
import { formatMeasure } from '../../../helpers/measures'; | |||
export default React.createClass({ | |||
render() { | |||
if (this.props.value == null || isNaN(this.props.value)) { | |||
return null; | |||
} | |||
let formatted = formatMeasure(this.props.value, this.props.type); | |||
return <span>{formatted}</span>; | |||
} | |||
}); |
@@ -1,7 +1,8 @@ | |||
import _ from 'underscore'; | |||
import moment from 'moment'; | |||
export let getPeriodLabel = (periods, periodIndex) => { | |||
export function getPeriodLabel (periods, periodIndex) { | |||
let period = _.findWhere(periods, { index: periodIndex }); | |||
if (!period) { | |||
return null; | |||
@@ -10,12 +11,13 @@ export let getPeriodLabel = (periods, periodIndex) => { | |||
return window.t('overview.period.previous_version_only_date'); | |||
} | |||
return window.tp(`overview.period.${period.mode}`, period.modeParam); | |||
}; | |||
} | |||
export let getPeriodDate = (periods, periodIndex) => { | |||
export function getPeriodDate (periods, periodIndex) { | |||
let period = _.findWhere(periods, { index: periodIndex }); | |||
if (!period) { | |||
return null; | |||
} | |||
return moment(period.date).toDate(); | |||
}; | |||
} |
@@ -1,36 +0,0 @@ | |||
import _ from 'underscore'; | |||
import React from 'react'; | |||
import SeverityHelper from '../../../components/shared/severity-helper'; | |||
import { DomainHeader } from '../domain/header'; | |||
import { getComponentIssuesUrl } from '../../../helpers/urls'; | |||
import { formatMeasure } from '../../../helpers/measures'; | |||
export default class extends React.Component { | |||
sortedSeverities () { | |||
return _.sortBy(this.props.severities, s => window.severityComparator(s.val)); | |||
} | |||
render () { | |||
let rows = this.sortedSeverities().map(s => { | |||
let href = getComponentIssuesUrl(this.props.component.key, { resolved: 'false', severities: s.val }); | |||
return <tr key={s.val}> | |||
<td> | |||
<SeverityHelper severity={s.val}/> | |||
</td> | |||
<td className="thin text-right"> | |||
<a className="cell-link" href={href}> | |||
{formatMeasure(s.count, 'SHORT_INT')} | |||
</a> | |||
</td> | |||
</tr>; | |||
}); | |||
return <div className="overview-domain-section"> | |||
<DomainHeader title="Prioritized Issues"/> | |||
<table className="data zebra"> | |||
<tbody>{rows}</tbody> | |||
</table> | |||
</div>; | |||
} | |||
} |
@@ -3,12 +3,12 @@ import React from 'react'; | |||
import { Timeline } from './timeline'; | |||
import { navigate } from '../../../components/router/router'; | |||
import { Legend } from '../common-components'; | |||
import { Legend } from '../components/legend'; | |||
export const Domain = React.createClass({ | |||
render () { | |||
return <div className="overview-domain">{this.props.children}</div>; | |||
return <div className="overview-card">{this.props.children}</div>; | |||
} | |||
}); | |||
@@ -50,7 +50,7 @@ export const DomainLeakTitle = React.createClass({ | |||
export const DomainHeader = React.createClass({ | |||
render () { | |||
return <div className="overview-domain-header"> | |||
return <div className="overview-card-header"> | |||
<DomainTitle linkTo={this.props.linkTo}>{this.props.title}</DomainTitle> | |||
<Legend leakPeriodLabel={this.props.leakPeriodLabel} leakPeriodDate={this.props.leakPeriodDate}/> | |||
</div>; |
@@ -1,7 +1,7 @@ | |||
import React from 'react'; | |||
import { Domain, DomainHeader, DomainPanel, DomainNutshell, DomainLeak, MeasuresList, Measure, DomainMixin } from './components'; | |||
import DrilldownLink from '../helpers/drilldown-link'; | |||
import { DrilldownLink } from '../../../components/shared/drilldown-link'; | |||
import { TooltipsMixin } from '../../../components/mixins/tooltips-mixin'; | |||
import { getMetricName } from '../helpers/metrics'; | |||
import { formatMeasure } from '../../../helpers/measures'; |
@@ -1,7 +1,7 @@ | |||
import React from 'react'; | |||
import { Domain, DomainHeader, DomainPanel, DomainNutshell, DomainLeak, MeasuresList, Measure, DomainMixin } from './components'; | |||
import DrilldownLink from '../helpers/drilldown-link'; | |||
import { DrilldownLink } from '../../../components/shared/drilldown-link'; | |||
import { TooltipsMixin } from '../../../components/mixins/tooltips-mixin'; | |||
import { getMetricName } from '../helpers/metrics'; | |||
import { formatMeasure, formatMeasureVariation } from '../../../helpers/measures'; |
@@ -2,16 +2,13 @@ import moment from 'moment'; | |||
import React from 'react'; | |||
import { Domain, DomainHeader, DomainPanel, DomainNutshell, DomainLeak, MeasuresList, Measure, DomainMixin } from './components'; | |||
import Rating from './../helpers/rating'; | |||
import IssuesLink from '../helpers/issues-link'; | |||
import DrilldownLink from '../helpers/drilldown-link'; | |||
import SeverityHelper from '../../../components/shared/severity-helper'; | |||
import { Rating } from './../../../components/shared/rating'; | |||
import { IssuesLink } from '../../../components/shared/issues-link'; | |||
import { DrilldownLink } from '../../../components/shared/drilldown-link'; | |||
import SeverityIcon from '../../../components/shared/severity-icon'; | |||
import StatusIcon from '../../../components/shared/status-icon'; | |||
import { TooltipsMixin } from '../../../components/mixins/tooltips-mixin'; | |||
import { getMetricName } from '../helpers/metrics'; | |||
import { SEVERITIES } from '../../../helpers/constants'; | |||
import { formatMeasure, formatMeasureVariation } from '../../../helpers/measures'; | |||
import { formatMeasure } from '../../../helpers/measures'; | |||
export const GeneralIssues = React.createClass({ |
@@ -6,7 +6,7 @@ import { GeneralIssues } from './issues'; | |||
import { GeneralCoverage } from './coverage'; | |||
import { GeneralDuplications } from './duplications'; | |||
import { GeneralSize } from './size'; | |||
import { getPeriodLabel, getPeriodDate } from './../helpers/period-label'; | |||
import { getPeriodLabel, getPeriodDate } from './../helpers/periods'; | |||
import { getMeasuresAndVariations } from '../../../api/measures'; | |||
import { getFacet, getIssuesCount } from '../../../api/issues'; | |||
import { getTimeMachineData } from '../../../api/time-machine'; | |||
@@ -157,7 +157,7 @@ export default React.createClass({ | |||
let props = _.extend({}, this.props, this.state); | |||
return <div className="overview-domains"> | |||
return <div className="overview-domains-list"> | |||
<GeneralIssues {...props} history={this.state.history['sqale_index']}/> | |||
<GeneralCoverage {...props} history={this.state.history['overall_coverage']}/> | |||
<GeneralDuplications {...props} history={this.state.history['duplicated_lines_density']}/> |
@@ -1,11 +1,11 @@ | |||
import React from 'react'; | |||
import { Domain, DomainHeader, DomainPanel, DomainNutshell, DomainLeak, MeasuresList, Measure, DomainMixin } from './components'; | |||
import DrilldownLink from '../helpers/drilldown-link'; | |||
import { DrilldownLink } from '../../../components/shared/drilldown-link'; | |||
import { TooltipsMixin } from '../../../components/mixins/tooltips-mixin'; | |||
import { getMetricName } from '../helpers/metrics'; | |||
import { formatMeasure, formatMeasureVariation } from '../../../helpers/measures'; | |||
import { LanguageDistribution } from '../size/language-distribution'; | |||
import { LanguageDistribution } from '../components/language-distribution'; | |||
export const GeneralSize = React.createClass({ |
@@ -1,7 +1,7 @@ | |||
import _ from 'underscore'; | |||
import React from 'react'; | |||
import ProfileLink from './helpers/profile-link'; | |||
import GateLink from './helpers/gate-link'; | |||
import { QualityProfileLink } from './../../components/shared/quality-profile-link'; | |||
import { QualityGateLink } from './../../components/shared/quality-gate-link'; | |||
export default React.createClass({ | |||
render() { | |||
@@ -10,7 +10,7 @@ export default React.createClass({ | |||
return ( | |||
<li key={profile.key}> | |||
<span className="note spacer-right">({profile.language})</span> | |||
<ProfileLink profile={profile.key}>{profile.name}</ProfileLink> | |||
<QualityProfileLink profile={profile.key}>{profile.name}</QualityProfileLink> | |||
</li> | |||
); | |||
}), | |||
@@ -50,7 +50,7 @@ export default React.createClass({ | |||
<li> | |||
{this.props.component.gate.isDefault ? | |||
<span className="note spacer-right">(Default)</span> : null} | |||
<GateLink gate={this.props.component.gate.key}>{this.props.component.gate.name}</GateLink> | |||
<QualityGateLink gate={this.props.component.gate.key}>{this.props.component.gate.name}</QualityGateLink> | |||
</li> | |||
</ul> | |||
</div> |
@@ -1,12 +1,12 @@ | |||
import React from 'react'; | |||
import Gate from './main/gate/gate'; | |||
import Gate from './gate/gate'; | |||
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 { IssuesMain } from './issues/main'; | |||
import { SizeMain } from './domains/size-domain'; | |||
import { DuplicationsMain } from './domains/duplications-domain'; | |||
import { CoverageMain } from './domains/coverage-domain'; | |||
import { IssuesMain } from './domains/debt-domain'; | |||
import { getMetrics } from '../../api/metrics'; | |||
import { RouterMixin } from '../../components/router/router'; |
@@ -1,29 +1,32 @@ | |||
import _ from 'underscore'; | |||
import moment from 'moment'; | |||
import React from 'react'; | |||
import IssuesLink from './issues-link'; | |||
import { getComponentDrilldownUrl } from '../../../helpers/urls'; | |||
import { IssuesLink } from './issues-link'; | |||
import { getComponentDrilldownUrl } from '../../helpers/urls'; | |||
export default React.createClass({ | |||
const ISSUE_MEASURES = [ | |||
'violations', | |||
'blocker_violations', | |||
'critical_violations', | |||
'major_violations', | |||
'minor_violations', | |||
'info_violations', | |||
'new_blocker_violations', | |||
'new_critical_violations', | |||
'new_major_violations', | |||
'new_minor_violations', | |||
'new_info_violations', | |||
'open_issues', | |||
'reopened_issues', | |||
'confirmed_issues', | |||
'false_positive_issues' | |||
]; | |||
export const DrilldownLink = React.createClass({ | |||
isIssueMeasure() { | |||
const ISSUE_MEASURES = [ | |||
'violations', | |||
'blocker_violations', | |||
'critical_violations', | |||
'major_violations', | |||
'minor_violations', | |||
'info_violations', | |||
'new_blocker_violations', | |||
'new_critical_violations', | |||
'new_major_violations', | |||
'new_minor_violations', | |||
'new_info_violations', | |||
'open_issues', | |||
'reopened_issues', | |||
'confirmed_issues', | |||
'false_positive_issues' | |||
]; | |||
return ISSUE_MEASURES.indexOf(this.props.metric) !== -1; | |||
}, | |||
@@ -83,6 +86,6 @@ export default React.createClass({ | |||
} | |||
let url = getComponentDrilldownUrl(this.props.component, this.props.metric, this.props.period); | |||
return <a href={url}>{this.props.children}</a>; | |||
return <a className={this.props.className} href={url}>{this.props.children}</a>; | |||
} | |||
}); |
@@ -1,8 +1,9 @@ | |||
import React from 'react'; | |||
import { getComponentIssuesUrl } from '../../../helpers/urls'; | |||
import { getComponentIssuesUrl } from '../../helpers/urls'; | |||
export default React.createClass({ | |||
export const IssuesLink = React.createClass({ | |||
render() { | |||
let url = getComponentIssuesUrl(this.props.component, this.props.params); | |||
return <a className={this.props.className} href={url}>{this.props.children}</a>; |
@@ -1,6 +1,7 @@ | |||
import React from 'react'; | |||
export default React.createClass({ | |||
export const QualityGateLink = React.createClass({ | |||
render() { | |||
let url = `${baseUrl}/quality_gates/show/${this.props.gate}`; | |||
return <a href={url}>{this.props.children}</a>; |
@@ -1,6 +1,7 @@ | |||
import React from 'react'; | |||
export default React.createClass({ | |||
export const QualityProfileLink = React.createClass({ | |||
render() { | |||
let url = `${baseUrl}/profiles/show?key=${encodeURIComponent(this.props.profile)}`; | |||
return <a href={url}>{this.props.children}</a>; |
@@ -1,7 +1,9 @@ | |||
import React from 'react'; | |||
import { formatMeasure } from '../../../helpers/measures'; | |||
export default React.createClass({ | |||
import { formatMeasure } from '../../helpers/measures'; | |||
export const Rating = React.createClass({ | |||
render() { | |||
if (this.props.value == null || isNaN(this.props.value)) { | |||
return null; |
@@ -100,6 +100,10 @@ td.big-spacer-top { padding-top: 16px; } | |||
flex: 1; | |||
} | |||
.space-between { | |||
justify-content: space-between !important; | |||
} | |||
// Background Color | |||
@@ -3,8 +3,6 @@ | |||
@import (reference) "../init/type"; | |||
@import (reference) "../init/links"; | |||
@side-padding: 20px; | |||
.overview { | |||
display: flex; | |||
flex-wrap: wrap; | |||
@@ -32,7 +30,7 @@ | |||
background-color: @barBackgroundColor; | |||
.overview-title { | |||
margin: 0 @side-padding; | |||
margin: 0 20px; | |||
} | |||
} | |||
@@ -42,11 +40,7 @@ | |||
} | |||
.overview-gate-condition { | |||
padding: 10px @side-padding; | |||
} | |||
.overview-gate-condition-metric { | |||
padding: 10px 20px; | |||
} | |||
.overview-gate-condition-value { | |||
@@ -56,7 +50,7 @@ | |||
} | |||
.overview-gate-warning { | |||
margin: 15px @side-padding 0; | |||
margin: 15px 20px 0; | |||
} | |||
/* | |||
@@ -77,16 +71,6 @@ | |||
} | |||
} | |||
/* | |||
* Cards | |||
* TODO drop it | |||
*/ | |||
.overview-cards { | |||
display: flex; | |||
flex-wrap: wrap; | |||
} | |||
/* | |||
* Meta | |||
*/ | |||
@@ -99,7 +83,7 @@ | |||
.overview-meta-card { | |||
min-width: 200px; | |||
padding: @side-padding; | |||
padding: 20px; | |||
box-sizing: border-box; | |||
} | |||
@@ -122,19 +106,28 @@ | |||
* Domain | |||
*/ | |||
.overview-domains { | |||
.overview-domains-list { | |||
animation: fadeIn 0.5s forwards; | |||
} | |||
.overview-domain { | |||
margin: 30px @side-padding; | |||
.overview-cards-list { | |||
display: flex; | |||
& > .overview-card, | |||
& > .overview-domain-chart { | |||
flex: 1; | |||
} | |||
} | |||
.overview-card { | |||
margin: 30px 20px; | |||
} | |||
.overview-domain-fixed-width { | |||
.overview-card-fixed-width { | |||
max-width: 560px; | |||
} | |||
.overview-domain-header { | |||
.overview-card-header { | |||
display: flex; | |||
align-items: baseline; | |||
justify-content: space-between; | |||
@@ -205,7 +198,6 @@ | |||
} | |||
.overview-domain-measure { | |||
} | |||
.overview-domain-measure-value { | |||
@@ -268,6 +260,7 @@ | |||
margin-bottom: 8px; | |||
flex: 0 1 auto; | |||
font-weight: 500; | |||
text-align: center; | |||
} | |||
.overview-detailed-measure + .overview-detailed-measure { | |||
@@ -297,6 +290,13 @@ | |||
background-color: #fff; | |||
} | |||
.overview-detailed-measure-rating { | |||
border: none !important; | |||
background: none; | |||
.overview-detailed-measure-value { font-size: 30px; } | |||
} | |||
.overview-detailed-measure-nutshell, | |||
.overview-detailed-measure-leak, | |||
.overview-detailed-measure-chart { | |||
@@ -364,15 +364,6 @@ | |||
* Charts | |||
*/ | |||
.overview-domain-charts { | |||
display: flex; | |||
& > .overview-domain, | |||
& > .overview-domain-chart { | |||
flex: 1; | |||
} | |||
} | |||
.overview-domain-chart { | |||
.overview-title { | |||
display: inline-block; | |||
@@ -380,28 +371,32 @@ | |||
} | |||
} | |||
.overview-domain-chart + .overview-domain-chart { | |||
margin-top: 60px; | |||
} | |||
.overview-bar-chart { | |||
width: 100%; | |||
padding-top: 10px; | |||
padding-bottom: 15px; | |||
} | |||
.bar-chart-bar { | |||
fill: @blue; | |||
} | |||
.bar-chart-bar { | |||
fill: @blue; | |||
} | |||
.bar-chart-tick { | |||
fill: @secondFontColor; | |||
font-size: 11px; | |||
text-anchor: middle; | |||
} | |||
.bar-chart-tick { | |||
fill: @secondFontColor; | |||
font-size: 11px; | |||
text-anchor: middle; | |||
} | |||
.histogram-tick { | |||
text-anchor: end; | |||
} | |||
.histogram-tick { | |||
text-anchor: end; | |||
} | |||
.histogram-value { | |||
text-anchor: start; | |||
.histogram-value { | |||
text-anchor: start; | |||
} | |||
} | |||
.overview-timeline { | |||
@@ -510,7 +505,9 @@ | |||
cursor: pointer; | |||
transition: all 0.2s ease; | |||
&:hover { fill-opacity: 0.8; } | |||
&:hover { | |||
fill-opacity: 0.8; | |||
} | |||
} | |||
.bubble-chart-grid { | |||
@@ -530,12 +527,29 @@ | |||
} | |||
.overview-donut-chart { | |||
display: inline-block; | |||
vertical-align: top; | |||
margin-right: 8px; | |||
position: relative; | |||
text-align: center; | |||
.overview-detailed-measure-value { | |||
position: absolute; | |||
top: 0; | |||
bottom: 0; | |||
left: 0; | |||
right: 0; | |||
} | |||
} | |||
/* | |||
* Misc | |||
*/ | |||
.overview-nutshell { | |||
background-color: #fff; | |||
} | |||
.overview-leak { | |||
background-color: #fffae7; | |||
} | |||
/* | |||
* Responsive Stuff |
@@ -0,0 +1,41 @@ | |||
import { expect } from 'chai'; | |||
import React from 'react'; | |||
import TestUtils from 'react-addons-test-utils'; | |||
import { ComplexityDistribution } from '../../../../src/main/js/apps/overview/components/complexity-distribution'; | |||
const DISTRIBUTION = '1=11950;2=86;4=77;6=43;8=17;10=12;12=3'; | |||
describe('ComplexityDistribution', function () { | |||
let props; | |||
beforeEach(function () { | |||
let renderer = TestUtils.createRenderer(); | |||
renderer.render(<ComplexityDistribution distribution={DISTRIBUTION}/>); | |||
let output = renderer.getRenderOutput(); | |||
let child = React.Children.only(output.props.children); | |||
props = child.props; | |||
}); | |||
it('should pass right data', function () { | |||
expect(props.data).to.deep.equal([ | |||
{ x: 0, y: 11950, value: 1 }, | |||
{ x: 1, y: 86, value: 2 }, | |||
{ x: 2, y: 77, value: 4 }, | |||
{ x: 3, y: 43, value: 6 }, | |||
{ x: 4, y: 17, value: 8 }, | |||
{ x: 5, y: 12, value: 10 }, | |||
{ x: 6, y: 3, value: 12 } | |||
]); | |||
}); | |||
it('should pass right xTicks', function () { | |||
expect(props.xTicks).to.deep.equal([1, 2, 4, 6, 8, 10, 12]); | |||
}); | |||
it('should pass right xValues', function () { | |||
expect(props.xValues).to.deep.equal(['11,950', '86', '77', '43', '17', '12', '3']); | |||
}); | |||
}); |
@@ -0,0 +1,45 @@ | |||
import { expect } from 'chai'; | |||
import React from 'react'; | |||
import TestUtils from 'react-addons-test-utils'; | |||
import { IssuesTags } from '../../../../src/main/js/apps/overview/components/issues-tags'; | |||
import { WordCloud } from '../../../../src/main/js/components/charts/word-cloud'; | |||
const COMPONENT = { key: 'component-key' }; | |||
const TAGS = [ | |||
{ val: 'first', count: 3 }, | |||
{ val: 'second', count: 7000 }, | |||
{ val: 'third', count: 2 } | |||
]; | |||
describe('IssuesTags', function () { | |||
it('should pass right data', function () { | |||
let renderer = TestUtils.createRenderer(); | |||
renderer.render(<IssuesTags tags={TAGS} component={COMPONENT}/>); | |||
let output = renderer.getRenderOutput(); | |||
expect(output.type).to.equal(WordCloud); | |||
expect(output.props.items).to.deep.equal([ | |||
{ | |||
"link": '/component_issues?id=component-key#resolved=false|tags=first', | |||
"size": 3, | |||
"text": 'first', | |||
"tooltip": 'Issues: 3' | |||
}, | |||
{ | |||
"link": '/component_issues?id=component-key#resolved=false|tags=second', | |||
"size": 7000, | |||
"text": 'second', | |||
"tooltip": 'Issues: 7k' | |||
}, | |||
{ | |||
"link": '/component_issues?id=component-key#resolved=false|tags=third', | |||
"size": 2, | |||
"text": 'third', | |||
"tooltip": 'Issues: 2' | |||
} | |||
]); | |||
}); | |||
}); |
@@ -0,0 +1,38 @@ | |||
import { expect } from 'chai'; | |||
import React from 'react'; | |||
import TestUtils from 'react-addons-test-utils'; | |||
import { LanguageDistribution } from '../../../../src/main/js/apps/overview/components/language-distribution'; | |||
const DISTRIBUTION = '<null>=17345;java=194342;js=20984'; | |||
const LINES = 1000000; | |||
describe('LanguageDistribution', function () { | |||
let props; | |||
beforeEach(function () { | |||
let renderer = TestUtils.createRenderer(); | |||
renderer.render(<LanguageDistribution distribution={DISTRIBUTION} lines={LINES}/>); | |||
let output = renderer.getRenderOutput(); | |||
let child = React.Children.only(output.props.children); | |||
props = child.props; | |||
}); | |||
it('should pass right data', function () { | |||
expect(props.data).to.deep.equal([ | |||
{ x: 194342, y: 1, value: 'java' }, | |||
{ x: 20984, y: 2, value: 'js' }, | |||
{ x: 17345, y: 0, value: '<null>' } | |||
]); | |||
}); | |||
it('should pass right yTicks', function () { | |||
expect(props.yTicks).to.deep.equal(['java', 'js', '<null>']); | |||
}); | |||
it('should pass right yValues', function () { | |||
expect(props.yValues).to.deep.equal(['19.4%', '2.1%', '1.7%']); | |||
}); | |||
}); |
@@ -0,0 +1,26 @@ | |||
import { expect } from 'chai'; | |||
import React from 'react'; | |||
import TestUtils from 'react-addons-test-utils'; | |||
import { Legend } from '../../../../src/main/js/apps/overview/components/legend'; | |||
const DATE = new Date(2015, 3, 7); | |||
const LABEL = 'since 1.0'; | |||
describe('Legend', function () { | |||
it('should not render', function () { | |||
let renderer = TestUtils.createRenderer(); | |||
renderer.render(<Legend/>); | |||
let output = renderer.getRenderOutput(); | |||
expect(output).to.be.null; | |||
}); | |||
it('should render', function () { | |||
let renderer = TestUtils.createRenderer(); | |||
renderer.render(<Legend leakPeriodDate={DATE} leakPeriodLabel={LABEL}/>); | |||
let output = renderer.getRenderOutput(); | |||
expect(output).to.not.be.null; | |||
}); | |||
}); |
@@ -0,0 +1,110 @@ | |||
import { expect } from 'chai'; | |||
import { filterMetrics, filterMetricsForDomains, getShortType, getMetricName } from | |||
'../../../../src/main/js/apps/overview/helpers/metrics'; | |||
const METRICS = [ | |||
{ key: 'normal_metric', type: 'INT', hidden: false }, | |||
{ key: 'hidden_metric', type: 'INT', hidden: true }, | |||
{ key: 'DATA_metric', type: 'DATA', hidden: false }, | |||
{ key: 'DISTRIB_metric', type: 'DISTRIB', hidden: false }, | |||
{ key: 'new_metric', type: 'FLOAT', hidden: false } | |||
]; | |||
describe('Overview Helpers', function () { | |||
describe('Metrics', function () { | |||
describe('#filterMetrics', function () { | |||
it('should filter out hidden metrics', function () { | |||
let metrics = [ | |||
{ key: 'normal_metric', type: 'INT', hidden: false }, | |||
{ key: 'hidden_metric', type: 'INT', hidden: true } | |||
]; | |||
expect(filterMetrics(metrics)).to.have.length(1); | |||
}); | |||
it('should filter out DATA and DISTRIB metrics', function () { | |||
let metrics = [ | |||
{ key: 'normal_metric', type: 'INT', hidden: false }, | |||
{ key: 'DATA_metric', type: 'DATA', hidden: false }, | |||
{ key: 'DISTRIB_metric', type: 'DISTRIB', hidden: false } | |||
]; | |||
expect(filterMetrics(metrics)).to.have.length(1); | |||
}); | |||
it('should filter out differential metrics', function () { | |||
let metrics = [ | |||
{ key: 'normal_metric', type: 'INT', hidden: false }, | |||
{ key: 'new_metric', type: 'FLOAT', hidden: false } | |||
]; | |||
expect(filterMetrics(metrics)).to.have.length(1); | |||
}); | |||
}); | |||
describe('#filterMetricsForDomains', function () { | |||
it('should filter out hidden metrics', function () { | |||
let metrics = [ | |||
{ key: 'normal_metric', type: 'INT', hidden: false, domain: 'first' }, | |||
{ key: 'hidden_metric', type: 'INT', hidden: true, domain: 'first' } | |||
]; | |||
expect(filterMetricsForDomains(metrics, ['first'])).to.have.length(1); | |||
}); | |||
it('should filter out DATA and DISTRIB metrics', function () { | |||
let metrics = [ | |||
{ key: 'normal_metric', type: 'INT', hidden: false, domain: 'first' }, | |||
{ key: 'DATA_metric', type: 'DATA', hidden: false, domain: 'first' }, | |||
{ key: 'DISTRIB_metric', type: 'DISTRIB', hidden: false, domain: 'first' } | |||
]; | |||
expect(filterMetricsForDomains(metrics, ['first'])).to.have.length(1); | |||
}); | |||
it('should filter out differential metrics', function () { | |||
let metrics = [ | |||
{ key: 'normal_metric', type: 'INT', hidden: false, domain: 'first' }, | |||
{ key: 'new_metric', type: 'FLOAT', hidden: false, domain: 'first' } | |||
]; | |||
expect(filterMetricsForDomains(metrics, ['first'])).to.have.length(1); | |||
}); | |||
it('should filter metrics by domains', function () { | |||
let metrics = [ | |||
{ key: 'normal_metric', type: 'INT', hidden: false, domain: 'first' }, | |||
{ key: 'normal_metric1', type: 'INT', hidden: false, domain: 'second' }, | |||
{ key: 'normal_metric2', type: 'INT', hidden: false, domain: 'third' }, | |||
{ key: 'normal_metric3', type: 'INT', hidden: false, domain: 'second' } | |||
]; | |||
expect(filterMetricsForDomains(metrics, ['first', 'second'])).to.have.length(3); | |||
}); | |||
}); | |||
describe('#getShortType', function () { | |||
it('should shorten INT', function () { | |||
expect(getShortType('INT')).to.equal('SHORT_INT'); | |||
}); | |||
it('should shorten WORK_DUR', function () { | |||
expect(getShortType('WORK_DUR')).to.equal('SHORT_WORK_DUR'); | |||
}); | |||
it('should not shorten FLOAT', function () { | |||
expect(getShortType('FLOAT')).to.equal('FLOAT'); | |||
}); | |||
it('should not shorten PERCENT', function () { | |||
expect(getShortType('PERCENT')).to.equal('PERCENT'); | |||
}); | |||
}); | |||
describe('#getMetricName', function () { | |||
it('should return metric name', function () { | |||
expect(getMetricName('metric_name')).to.equal('overview.metric.metric_name'); | |||
}); | |||
}); | |||
}); | |||
}); |
@@ -0,0 +1,57 @@ | |||
import chai from 'chai'; | |||
import { expect } from 'chai'; | |||
chai.use(require('chai-datetime')); | |||
import { getPeriodDate, getPeriodLabel } from '../../../../src/main/js/apps/overview/helpers/periods'; | |||
const PERIOD = { | |||
date: '2015-09-09T00:00:00+0200', | |||
index: '1', | |||
mode: 'previous_version', | |||
modeParam: '1.7' | |||
}; | |||
const PERIOD_WITHOUT_VERSION = { | |||
date: '2015-09-09T00:00:00+0200', | |||
index: '1', | |||
mode: 'previous_version', | |||
modeParam: '' | |||
}; | |||
describe('Overview Helpers', function () { | |||
describe('Periods', function () { | |||
describe('#getPeriodDate', function () { | |||
it('should return date', function () { | |||
let result = getPeriodDate([PERIOD], PERIOD.index); | |||
expect(result).to.equalDate(new Date(2015, 8, 9)); | |||
}); | |||
it('should return null', function () { | |||
let result = getPeriodDate([], '1'); | |||
expect(result).to.be.null; | |||
}); | |||
}); | |||
describe('#getPeriodLabel', function () { | |||
it('should return label', function () { | |||
let result = getPeriodLabel([PERIOD], PERIOD.index); | |||
expect(result).to.equal('overview.period.previous_version.1.7'); | |||
}); | |||
it('should return "since previous version"', function () { | |||
let result = getPeriodLabel([PERIOD_WITHOUT_VERSION], PERIOD_WITHOUT_VERSION.index); | |||
expect(result).to.equal('overview.period.previous_version_only_date'); | |||
}); | |||
it('should return null', function () { | |||
let result = getPeriodLabel([], '1'); | |||
expect(result).to.be.null; | |||
}); | |||
}); | |||
}); | |||
}); |
@@ -3,9 +3,9 @@ import ReactDOM from 'react-dom'; | |||
import TestUtils from 'react-addons-test-utils'; | |||
import { expect } from 'chai'; | |||
import Gate from '../../src/main/js/apps/overview/main/gate/gate'; | |||
import GateConditions from '../../src/main/js/apps/overview/main/gate/gate-conditions'; | |||
import GateCondition from '../../src/main/js/apps/overview/main/gate/gate-condition'; | |||
import Gate from '../../../src/main/js/apps/overview/gate/gate'; | |||
import GateConditions from '../../../src/main/js/apps/overview/gate/gate-conditions'; | |||
import GateCondition from '../../../src/main/js/apps/overview/gate/gate-condition'; | |||
describe('Overview', function () { | |||
@@ -41,4 +41,11 @@ describe('Overview', function () { | |||
}); | |||
}); | |||
describe('Helpers', function () { | |||
describe('Periods', function () { | |||
}); | |||
}); | |||
}); |