Browse Source

SONAR-6331 refactor overview code

tags/5.3-RC1
Stas Vilchik 8 years ago
parent
commit
27c49eda28
54 changed files with 979 additions and 675 deletions
  1. 1
    0
      server/sonar-web/package.json
  2. 7
    3
      server/sonar-web/src/main/js/apps/overview/components/complexity-distribution.js
  3. 53
    0
      server/sonar-web/src/main/js/apps/overview/components/coverage-measure.js
  4. 123
    0
      server/sonar-web/src/main/js/apps/overview/components/coverage-measures-list.js
  5. 3
    17
      server/sonar-web/src/main/js/apps/overview/components/detailed-measure.js
  6. 3
    2
      server/sonar-web/src/main/js/apps/overview/components/domain-bubble-chart.js
  7. 3
    2
      server/sonar-web/src/main/js/apps/overview/components/domain-timeline.js
  8. 3
    2
      server/sonar-web/src/main/js/apps/overview/components/domain-treemap.js
  9. 67
    8
      server/sonar-web/src/main/js/apps/overview/components/issue-measure.js
  10. 7
    2
      server/sonar-web/src/main/js/apps/overview/components/issues-assignees.js
  11. 4
    8
      server/sonar-web/src/main/js/apps/overview/components/issues-tags.js
  12. 11
    6
      server/sonar-web/src/main/js/apps/overview/components/language-distribution.js
  13. 16
    0
      server/sonar-web/src/main/js/apps/overview/components/legend.js
  14. 0
    0
      server/sonar-web/src/main/js/apps/overview/components/timeline-chart.js
  15. 0
    85
      server/sonar-web/src/main/js/apps/overview/coverage/coverage-measure.js
  16. 0
    184
      server/sonar-web/src/main/js/apps/overview/coverage/main.js
  17. 0
    7
      server/sonar-web/src/main/js/apps/overview/domain/header.js
  18. 0
    57
      server/sonar-web/src/main/js/apps/overview/domain/measures-list.js
  19. 0
    7
      server/sonar-web/src/main/js/apps/overview/domain/timeline.js
  20. 119
    0
      server/sonar-web/src/main/js/apps/overview/domains/coverage-domain.js
  21. 37
    37
      server/sonar-web/src/main/js/apps/overview/domains/debt-domain.js
  22. 28
    22
      server/sonar-web/src/main/js/apps/overview/domains/duplications-domain.js
  23. 19
    16
      server/sonar-web/src/main/js/apps/overview/domains/size-domain.js
  24. 14
    3
      server/sonar-web/src/main/js/apps/overview/gate/gate-condition.js
  25. 0
    0
      server/sonar-web/src/main/js/apps/overview/gate/gate-conditions.js
  26. 0
    0
      server/sonar-web/src/main/js/apps/overview/gate/gate-empty.js
  27. 0
    0
      server/sonar-web/src/main/js/apps/overview/gate/gate.js
  28. 0
    34
      server/sonar-web/src/main/js/apps/overview/helpers/donut.js
  29. 0
    13
      server/sonar-web/src/main/js/apps/overview/helpers/measure-variation.js
  30. 0
    12
      server/sonar-web/src/main/js/apps/overview/helpers/measure.js
  31. 6
    4
      server/sonar-web/src/main/js/apps/overview/helpers/periods.js
  32. 0
    36
      server/sonar-web/src/main/js/apps/overview/issues/severities.js
  33. 3
    3
      server/sonar-web/src/main/js/apps/overview/main/components.js
  34. 1
    1
      server/sonar-web/src/main/js/apps/overview/main/coverage.js
  35. 1
    1
      server/sonar-web/src/main/js/apps/overview/main/duplications.js
  36. 4
    7
      server/sonar-web/src/main/js/apps/overview/main/issues.js
  37. 2
    2
      server/sonar-web/src/main/js/apps/overview/main/main.js
  38. 2
    2
      server/sonar-web/src/main/js/apps/overview/main/size.js
  39. 4
    4
      server/sonar-web/src/main/js/apps/overview/meta.js
  40. 5
    5
      server/sonar-web/src/main/js/apps/overview/overview.js
  41. 24
    21
      server/sonar-web/src/main/js/components/shared/drilldown-link.js
  42. 3
    2
      server/sonar-web/src/main/js/components/shared/issues-link.js
  43. 2
    1
      server/sonar-web/src/main/js/components/shared/quality-gate-link.js
  44. 2
    1
      server/sonar-web/src/main/js/components/shared/quality-profile-link.js
  45. 4
    2
      server/sonar-web/src/main/js/components/shared/rating.js
  46. 4
    0
      server/sonar-web/src/main/less/init/misc.less
  47. 67
    53
      server/sonar-web/src/main/less/pages/overview.less
  48. 41
    0
      server/sonar-web/tests/apps/overview/components/complexity-distribution-test.js
  49. 45
    0
      server/sonar-web/tests/apps/overview/components/issues-tags-test.js
  50. 38
    0
      server/sonar-web/tests/apps/overview/components/language-distribution-test.js
  51. 26
    0
      server/sonar-web/tests/apps/overview/components/legend-test.js
  52. 110
    0
      server/sonar-web/tests/apps/overview/helpers/metrics-test.js
  53. 57
    0
      server/sonar-web/tests/apps/overview/helpers/periods-test.js
  54. 10
    3
      server/sonar-web/tests/apps/overview/overview-test.js

+ 1
- 0
server/sonar-web/package.json View File

@@ -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",

server/sonar-web/src/main/js/apps/overview/size/complexity-distribution.js → server/sonar-web/src/main/js/apps/overview/components/complexity-distribution.js View File

@@ -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>;
}
}
});

+ 53
- 0
server/sonar-web/src/main/js/apps/overview/components/coverage-measure.js View File

@@ -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>;
}
});

+ 123
- 0
server/sonar-web/src/main/js/apps/overview/components/coverage-measures-list.js View File

@@ -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>;
}
});

server/sonar-web/src/main/js/apps/overview/common-components.js → server/sonar-web/src/main/js/apps/overview/components/detailed-measure.js View File

@@ -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>;
}
});

server/sonar-web/src/main/js/apps/overview/domain/bubble-chart.js → server/sonar-web/src/main/js/apps/overview/components/domain-bubble-chart.js View File

@@ -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>

server/sonar-web/src/main/js/apps/overview/timeline/domain-timeline.js → server/sonar-web/src/main/js/apps/overview/components/domain-timeline.js View File

@@ -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()}

server/sonar-web/src/main/js/apps/overview/domain/treemap.js → server/sonar-web/src/main/js/apps/overview/components/domain-treemap.js View File

@@ -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>

server/sonar-web/src/main/js/apps/overview/issues/issue-measure.js → server/sonar-web/src/main/js/apps/overview/components/issue-measure.js View File

@@ -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>

server/sonar-web/src/main/js/apps/overview/issues/assignees.js → server/sonar-web/src/main/js/apps/overview/components/issues-assignees.js View File

@@ -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}/>

server/sonar-web/src/main/js/apps/overview/issues/tags.js → server/sonar-web/src/main/js/apps/overview/components/issues-tags.js View File

@@ -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();
}
}
});

server/sonar-web/src/main/js/apps/overview/size/language-distribution.js → server/sonar-web/src/main/js/apps/overview/components/language-distribution.js View File

@@ -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>;
}
}
});

+ 16
- 0
server/sonar-web/src/main/js/apps/overview/components/legend.js View File

@@ -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>;
}
});

server/sonar-web/src/main/js/apps/overview/timeline/timeline-chart.js → server/sonar-web/src/main/js/apps/overview/components/timeline-chart.js View File


+ 0
- 85
server/sonar-web/src/main/js/apps/overview/coverage/coverage-measure.js View File

@@ -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">&nbsp;</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>;
}
});

+ 0
- 184
server/sonar-web/src/main/js/apps/overview/coverage/main.js View File

@@ -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>;

}
});

+ 0
- 7
server/sonar-web/src/main/js/apps/overview/domain/header.js View File

@@ -1,7 +0,0 @@
import React from 'react';

export class DomainHeader extends React.Component {
render () {
return <h2 className="overview-title">{this.props.title}</h2>;
}
}

+ 0
- 57
server/sonar-web/src/main/js/apps/overview/domain/measures-list.js View File

@@ -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
};

+ 0
- 7
server/sonar-web/src/main/js/apps/overview/domain/timeline.js View File

@@ -1,7 +0,0 @@








+ 119
- 0
server/sonar-web/src/main/js/apps/overview/domains/coverage-domain.js View File

@@ -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>;

}
});

server/sonar-web/src/main/js/apps/overview/issues/main.js → server/sonar-web/src/main/js/apps/overview/domains/debt-domain.js View File

@@ -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>;


server/sonar-web/src/main/js/apps/overview/duplications/main.js → server/sonar-web/src/main/js/apps/overview/domains/duplications-domain.js View File

@@ -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>;


server/sonar-web/src/main/js/apps/overview/size/main.js → server/sonar-web/src/main/js/apps/overview/domains/size-domain.js View File

@@ -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>;


server/sonar-web/src/main/js/apps/overview/main/gate/gate-condition.js → server/sonar-web/src/main/js/apps/overview/gate/gate-condition.js View File

@@ -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({

server/sonar-web/src/main/js/apps/overview/main/gate/gate-conditions.js → server/sonar-web/src/main/js/apps/overview/gate/gate-conditions.js View File


server/sonar-web/src/main/js/apps/overview/main/gate/gate-empty.js → server/sonar-web/src/main/js/apps/overview/gate/gate-empty.js View File


server/sonar-web/src/main/js/apps/overview/main/gate/gate.js → server/sonar-web/src/main/js/apps/overview/gate/gate.js View File


+ 0
- 34
server/sonar-web/src/main/js/apps/overview/helpers/donut.js View File

@@ -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>;
}
});

+ 0
- 13
server/sonar-web/src/main/js/apps/overview/helpers/measure-variation.js View File

@@ -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>;
}
});

+ 0
- 12
server/sonar-web/src/main/js/apps/overview/helpers/measure.js View File

@@ -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>;
}
});

server/sonar-web/src/main/js/apps/overview/helpers/period-label.js → server/sonar-web/src/main/js/apps/overview/helpers/periods.js View File

@@ -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();
};
}

+ 0
- 36
server/sonar-web/src/main/js/apps/overview/issues/severities.js View File

@@ -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
- 3
server/sonar-web/src/main/js/apps/overview/main/components.js View File

@@ -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
- 1
server/sonar-web/src/main/js/apps/overview/main/coverage.js View File

@@ -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
- 1
server/sonar-web/src/main/js/apps/overview/main/duplications.js View File

@@ -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';

+ 4
- 7
server/sonar-web/src/main/js/apps/overview/main/issues.js View File

@@ -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({

+ 2
- 2
server/sonar-web/src/main/js/apps/overview/main/main.js View File

@@ -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']}/>

+ 2
- 2
server/sonar-web/src/main/js/apps/overview/main/size.js View File

@@ -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({

+ 4
- 4
server/sonar-web/src/main/js/apps/overview/meta.js View File

@@ -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>

+ 5
- 5
server/sonar-web/src/main/js/apps/overview/overview.js View File

@@ -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';

server/sonar-web/src/main/js/apps/overview/helpers/drilldown-link.js → server/sonar-web/src/main/js/components/shared/drilldown-link.js View File

@@ -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>;
}
});

server/sonar-web/src/main/js/apps/overview/helpers/issues-link.js → server/sonar-web/src/main/js/components/shared/issues-link.js View File

@@ -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>;

server/sonar-web/src/main/js/apps/overview/helpers/gate-link.js → server/sonar-web/src/main/js/components/shared/quality-gate-link.js View File

@@ -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>;

server/sonar-web/src/main/js/apps/overview/helpers/profile-link.js → server/sonar-web/src/main/js/components/shared/quality-profile-link.js View File

@@ -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>;

server/sonar-web/src/main/js/apps/overview/helpers/rating.js → server/sonar-web/src/main/js/components/shared/rating.js View File

@@ -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;

+ 4
- 0
server/sonar-web/src/main/less/init/misc.less View File

@@ -100,6 +100,10 @@ td.big-spacer-top { padding-top: 16px; }
flex: 1;
}

.space-between {
justify-content: space-between !important;
}


// Background Color


+ 67
- 53
server/sonar-web/src/main/less/pages/overview.less View File

@@ -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

+ 41
- 0
server/sonar-web/tests/apps/overview/components/complexity-distribution-test.js View File

@@ -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']);
});
});

+ 45
- 0
server/sonar-web/tests/apps/overview/components/issues-tags-test.js View File

@@ -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'
}
]);
});
});

+ 38
- 0
server/sonar-web/tests/apps/overview/components/language-distribution-test.js View File

@@ -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%']);
});
});

+ 26
- 0
server/sonar-web/tests/apps/overview/components/legend-test.js View File

@@ -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;
});
});

+ 110
- 0
server/sonar-web/tests/apps/overview/helpers/metrics-test.js View File

@@ -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');
});
});

});
});

+ 57
- 0
server/sonar-web/tests/apps/overview/helpers/periods-test.js View File

@@ -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;
});
});

});
});

server/sonar-web/tests/apps/overview-test.js → server/sonar-web/tests/apps/overview/overview-test.js View File

@@ -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 () {

});
});

});

Loading…
Cancel
Save