diff options
author | Stas Vilchik <vilchiks@gmail.com> | 2015-11-12 18:05:08 +0100 |
---|---|---|
committer | Stas Vilchik <vilchiks@gmail.com> | 2015-11-13 14:25:00 +0100 |
commit | 27c49eda28940ca7600587b75fcbc9223268ee8b (patch) | |
tree | b296526a4426370c20cff34aa55cc0097ee87d01 | |
parent | 3348f196c986cfc9db1cfeb2a57ae847c2f4df63 (diff) | |
download | sonarqube-27c49eda28940ca7600587b75fcbc9223268ee8b.tar.gz sonarqube-27c49eda28940ca7600587b75fcbc9223268ee8b.zip |
SONAR-6331 refactor overview code
54 files changed, 979 insertions, 675 deletions
diff --git a/server/sonar-web/package.json b/server/sonar-web/package.json index 87a6ac744b1..10b162c4ce7 100644 --- a/server/sonar-web/package.json +++ b/server/sonar-web/package.json @@ -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", diff --git a/server/sonar-web/src/main/js/apps/overview/size/complexity-distribution.js b/server/sonar-web/src/main/js/apps/overview/components/complexity-distribution.js index 2c82df4d6a3..390273da2e1 100644 --- a/server/sonar-web/src/main/js/apps/overview/size/complexity-distribution.js +++ b/server/sonar-web/src/main/js/apps/overview/components/complexity-distribution.js @@ -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>; } -} +}); diff --git a/server/sonar-web/src/main/js/apps/overview/components/coverage-measure.js b/server/sonar-web/src/main/js/apps/overview/components/coverage-measure.js new file mode 100644 index 00000000000..df6206f03de --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/components/coverage-measure.js @@ -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>; + } +}); diff --git a/server/sonar-web/src/main/js/apps/overview/components/coverage-measures-list.js b/server/sonar-web/src/main/js/apps/overview/components/coverage-measures-list.js new file mode 100644 index 00000000000..898da4b1cd0 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/components/coverage-measures-list.js @@ -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>; + } +}); diff --git a/server/sonar-web/src/main/js/apps/overview/common-components.js b/server/sonar-web/src/main/js/apps/overview/components/detailed-measure.js index d1299d539e8..df0b8e2630c 100644 --- a/server/sonar-web/src/main/js/apps/overview/common-components.js +++ b/server/sonar-web/src/main/js/apps/overview/components/detailed-measure.js @@ -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>; - } -}); diff --git a/server/sonar-web/src/main/js/apps/overview/domain/bubble-chart.js b/server/sonar-web/src/main/js/apps/overview/components/domain-bubble-chart.js index e112d8a230d..056863ecdb4 100644 --- a/server/sonar-web/src/main/js/apps/overview/domain/bubble-chart.js +++ b/server/sonar-web/src/main/js/apps/overview/components/domain-bubble-chart.js @@ -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> diff --git a/server/sonar-web/src/main/js/apps/overview/timeline/domain-timeline.js b/server/sonar-web/src/main/js/apps/overview/components/domain-timeline.js index 59e5280bd48..62d4ab1f9f9 100644 --- a/server/sonar-web/src/main/js/apps/overview/timeline/domain-timeline.js +++ b/server/sonar-web/src/main/js/apps/overview/components/domain-timeline.js @@ -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()} diff --git a/server/sonar-web/src/main/js/apps/overview/domain/treemap.js b/server/sonar-web/src/main/js/apps/overview/components/domain-treemap.js index e22345f78d5..59ee367e312 100644 --- a/server/sonar-web/src/main/js/apps/overview/domain/treemap.js +++ b/server/sonar-web/src/main/js/apps/overview/components/domain-treemap.js @@ -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> diff --git a/server/sonar-web/src/main/js/apps/overview/issues/issue-measure.js b/server/sonar-web/src/main/js/apps/overview/components/issue-measure.js index 7234696c229..8597f15eb72 100644 --- a/server/sonar-web/src/main/js/apps/overview/issues/issue-measure.js +++ b/server/sonar-web/src/main/js/apps/overview/components/issue-measure.js @@ -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> diff --git a/server/sonar-web/src/main/js/apps/overview/issues/assignees.js b/server/sonar-web/src/main/js/apps/overview/components/issues-assignees.js index 1d143a8ad52..126830c9eb8 100644 --- a/server/sonar-web/src/main/js/apps/overview/issues/assignees.js +++ b/server/sonar-web/src/main/js/apps/overview/components/issues-assignees.js @@ -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}/> diff --git a/server/sonar-web/src/main/js/apps/overview/issues/tags.js b/server/sonar-web/src/main/js/apps/overview/components/issues-tags.js index 8d1644d11c4..733ac50f2c6 100644 --- a/server/sonar-web/src/main/js/apps/overview/issues/tags.js +++ b/server/sonar-web/src/main/js/apps/overview/components/issues-tags.js @@ -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(); - } -} +}); diff --git a/server/sonar-web/src/main/js/apps/overview/size/language-distribution.js b/server/sonar-web/src/main/js/apps/overview/components/language-distribution.js index d3bde4d64ae..732db2ee1f8 100644 --- a/server/sonar-web/src/main/js/apps/overview/size/language-distribution.js +++ b/server/sonar-web/src/main/js/apps/overview/components/language-distribution.js @@ -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>; } -} +}); diff --git a/server/sonar-web/src/main/js/apps/overview/components/legend.js b/server/sonar-web/src/main/js/apps/overview/components/legend.js new file mode 100644 index 00000000000..1c38b7d17b1 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/components/legend.js @@ -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>; + } +}); diff --git a/server/sonar-web/src/main/js/apps/overview/timeline/timeline-chart.js b/server/sonar-web/src/main/js/apps/overview/components/timeline-chart.js index fc61b1912ff..fc61b1912ff 100644 --- a/server/sonar-web/src/main/js/apps/overview/timeline/timeline-chart.js +++ b/server/sonar-web/src/main/js/apps/overview/components/timeline-chart.js diff --git a/server/sonar-web/src/main/js/apps/overview/coverage/coverage-measure.js b/server/sonar-web/src/main/js/apps/overview/coverage/coverage-measure.js deleted file mode 100644 index 00821bd35de..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/coverage/coverage-measure.js +++ /dev/null @@ -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>; - } -}); diff --git a/server/sonar-web/src/main/js/apps/overview/coverage/main.js b/server/sonar-web/src/main/js/apps/overview/coverage/main.js deleted file mode 100644 index 1c5c62d518a..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/coverage/main.js +++ /dev/null @@ -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>; - - } -}); diff --git a/server/sonar-web/src/main/js/apps/overview/domain/header.js b/server/sonar-web/src/main/js/apps/overview/domain/header.js deleted file mode 100644 index 16af08d9d13..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/domain/header.js +++ /dev/null @@ -1,7 +0,0 @@ -import React from 'react'; - -export class DomainHeader extends React.Component { - render () { - return <h2 className="overview-title">{this.props.title}</h2>; - } -} diff --git a/server/sonar-web/src/main/js/apps/overview/domain/measures-list.js b/server/sonar-web/src/main/js/apps/overview/domain/measures-list.js deleted file mode 100644 index d37d4b97dd9..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/domain/measures-list.js +++ /dev/null @@ -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 -}; diff --git a/server/sonar-web/src/main/js/apps/overview/domain/timeline.js b/server/sonar-web/src/main/js/apps/overview/domain/timeline.js deleted file mode 100644 index 9e5f5f8a47c..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/domain/timeline.js +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/server/sonar-web/src/main/js/apps/overview/domains/coverage-domain.js b/server/sonar-web/src/main/js/apps/overview/domains/coverage-domain.js new file mode 100644 index 00000000000..6df04be61bb --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/domains/coverage-domain.js @@ -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>; + + } +}); diff --git a/server/sonar-web/src/main/js/apps/overview/issues/main.js b/server/sonar-web/src/main/js/apps/overview/domains/debt-domain.js index d5fd68c110a..bf7a73b1779 100644 --- a/server/sonar-web/src/main/js/apps/overview/issues/main.js +++ b/server/sonar-web/src/main/js/apps/overview/domains/debt-domain.js @@ -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>; diff --git a/server/sonar-web/src/main/js/apps/overview/duplications/main.js b/server/sonar-web/src/main/js/apps/overview/domains/duplications-domain.js index d85d3790f87..e1e3977e264 100644 --- a/server/sonar-web/src/main/js/apps/overview/duplications/main.js +++ b/server/sonar-web/src/main/js/apps/overview/domains/duplications-domain.js @@ -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>; diff --git a/server/sonar-web/src/main/js/apps/overview/size/main.js b/server/sonar-web/src/main/js/apps/overview/domains/size-domain.js index d54f3894926..e65f0123578 100644 --- a/server/sonar-web/src/main/js/apps/overview/size/main.js +++ b/server/sonar-web/src/main/js/apps/overview/domains/size-domain.js @@ -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>; diff --git a/server/sonar-web/src/main/js/apps/overview/main/gate/gate-condition.js b/server/sonar-web/src/main/js/apps/overview/gate/gate-condition.js index 0819d6002c8..6d88348ed1a 100644 --- a/server/sonar-web/src/main/js/apps/overview/main/gate/gate-condition.js +++ b/server/sonar-web/src/main/js/apps/overview/gate/gate-condition.js @@ -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({ diff --git a/server/sonar-web/src/main/js/apps/overview/main/gate/gate-conditions.js b/server/sonar-web/src/main/js/apps/overview/gate/gate-conditions.js index adefecbded4..adefecbded4 100644 --- a/server/sonar-web/src/main/js/apps/overview/main/gate/gate-conditions.js +++ b/server/sonar-web/src/main/js/apps/overview/gate/gate-conditions.js diff --git a/server/sonar-web/src/main/js/apps/overview/main/gate/gate-empty.js b/server/sonar-web/src/main/js/apps/overview/gate/gate-empty.js index 61347185593..61347185593 100644 --- a/server/sonar-web/src/main/js/apps/overview/main/gate/gate-empty.js +++ b/server/sonar-web/src/main/js/apps/overview/gate/gate-empty.js diff --git a/server/sonar-web/src/main/js/apps/overview/main/gate/gate.js b/server/sonar-web/src/main/js/apps/overview/gate/gate.js index 076cbcd376a..076cbcd376a 100644 --- a/server/sonar-web/src/main/js/apps/overview/main/gate/gate.js +++ b/server/sonar-web/src/main/js/apps/overview/gate/gate.js diff --git a/server/sonar-web/src/main/js/apps/overview/helpers/donut.js b/server/sonar-web/src/main/js/apps/overview/helpers/donut.js deleted file mode 100644 index 368a5222c64..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/helpers/donut.js +++ /dev/null @@ -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>; - } -}); diff --git a/server/sonar-web/src/main/js/apps/overview/helpers/measure-variation.js b/server/sonar-web/src/main/js/apps/overview/helpers/measure-variation.js deleted file mode 100644 index e6ea091ce87..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/helpers/measure-variation.js +++ /dev/null @@ -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>; - } -}); diff --git a/server/sonar-web/src/main/js/apps/overview/helpers/measure.js b/server/sonar-web/src/main/js/apps/overview/helpers/measure.js deleted file mode 100644 index 3d4eb5ec549..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/helpers/measure.js +++ /dev/null @@ -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>; - } -}); diff --git a/server/sonar-web/src/main/js/apps/overview/helpers/period-label.js b/server/sonar-web/src/main/js/apps/overview/helpers/periods.js index 5b859894cd8..f4e38dc2098 100644 --- a/server/sonar-web/src/main/js/apps/overview/helpers/period-label.js +++ b/server/sonar-web/src/main/js/apps/overview/helpers/periods.js @@ -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(); -}; +} diff --git a/server/sonar-web/src/main/js/apps/overview/issues/severities.js b/server/sonar-web/src/main/js/apps/overview/issues/severities.js deleted file mode 100644 index 26008e4648f..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/issues/severities.js +++ /dev/null @@ -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>; - } -} diff --git a/server/sonar-web/src/main/js/apps/overview/main/components.js b/server/sonar-web/src/main/js/apps/overview/main/components.js index b8bdf111c53..68a5cd0670d 100644 --- a/server/sonar-web/src/main/js/apps/overview/main/components.js +++ b/server/sonar-web/src/main/js/apps/overview/main/components.js @@ -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>; diff --git a/server/sonar-web/src/main/js/apps/overview/main/coverage.js b/server/sonar-web/src/main/js/apps/overview/main/coverage.js index 056876f3e54..09752ad62a8 100644 --- a/server/sonar-web/src/main/js/apps/overview/main/coverage.js +++ b/server/sonar-web/src/main/js/apps/overview/main/coverage.js @@ -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'; diff --git a/server/sonar-web/src/main/js/apps/overview/main/duplications.js b/server/sonar-web/src/main/js/apps/overview/main/duplications.js index db4c6e6c320..06583cf5f8e 100644 --- a/server/sonar-web/src/main/js/apps/overview/main/duplications.js +++ b/server/sonar-web/src/main/js/apps/overview/main/duplications.js @@ -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'; diff --git a/server/sonar-web/src/main/js/apps/overview/main/issues.js b/server/sonar-web/src/main/js/apps/overview/main/issues.js index f281f85d08d..5a500cda8e4 100644 --- a/server/sonar-web/src/main/js/apps/overview/main/issues.js +++ b/server/sonar-web/src/main/js/apps/overview/main/issues.js @@ -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({ diff --git a/server/sonar-web/src/main/js/apps/overview/main/main.js b/server/sonar-web/src/main/js/apps/overview/main/main.js index d23555cb0e0..993876b600c 100644 --- a/server/sonar-web/src/main/js/apps/overview/main/main.js +++ b/server/sonar-web/src/main/js/apps/overview/main/main.js @@ -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']}/> diff --git a/server/sonar-web/src/main/js/apps/overview/main/size.js b/server/sonar-web/src/main/js/apps/overview/main/size.js index 1c2f9fd6536..627a8569757 100644 --- a/server/sonar-web/src/main/js/apps/overview/main/size.js +++ b/server/sonar-web/src/main/js/apps/overview/main/size.js @@ -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({ diff --git a/server/sonar-web/src/main/js/apps/overview/meta.js b/server/sonar-web/src/main/js/apps/overview/meta.js index e3e632a30fa..e6a48e71e09 100644 --- a/server/sonar-web/src/main/js/apps/overview/meta.js +++ b/server/sonar-web/src/main/js/apps/overview/meta.js @@ -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> diff --git a/server/sonar-web/src/main/js/apps/overview/overview.js b/server/sonar-web/src/main/js/apps/overview/overview.js index 8b36967694d..8301fd6add4 100644 --- a/server/sonar-web/src/main/js/apps/overview/overview.js +++ b/server/sonar-web/src/main/js/apps/overview/overview.js @@ -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'; diff --git a/server/sonar-web/src/main/js/apps/overview/helpers/drilldown-link.js b/server/sonar-web/src/main/js/components/shared/drilldown-link.js index 51f18ea39f5..d149a3db83c 100644 --- a/server/sonar-web/src/main/js/apps/overview/helpers/drilldown-link.js +++ b/server/sonar-web/src/main/js/components/shared/drilldown-link.js @@ -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>; } }); diff --git a/server/sonar-web/src/main/js/apps/overview/helpers/issues-link.js b/server/sonar-web/src/main/js/components/shared/issues-link.js index c3543f597e1..230ead7c83b 100644 --- a/server/sonar-web/src/main/js/apps/overview/helpers/issues-link.js +++ b/server/sonar-web/src/main/js/components/shared/issues-link.js @@ -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>; diff --git a/server/sonar-web/src/main/js/apps/overview/helpers/gate-link.js b/server/sonar-web/src/main/js/components/shared/quality-gate-link.js index 79878ac9fd6..5b05c8cd4af 100644 --- a/server/sonar-web/src/main/js/apps/overview/helpers/gate-link.js +++ b/server/sonar-web/src/main/js/components/shared/quality-gate-link.js @@ -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>; diff --git a/server/sonar-web/src/main/js/apps/overview/helpers/profile-link.js b/server/sonar-web/src/main/js/components/shared/quality-profile-link.js index 22065abad56..c7c296bce47 100644 --- a/server/sonar-web/src/main/js/apps/overview/helpers/profile-link.js +++ b/server/sonar-web/src/main/js/components/shared/quality-profile-link.js @@ -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>; diff --git a/server/sonar-web/src/main/js/apps/overview/helpers/rating.js b/server/sonar-web/src/main/js/components/shared/rating.js index 5568dc5d2dc..791466fdfc4 100644 --- a/server/sonar-web/src/main/js/apps/overview/helpers/rating.js +++ b/server/sonar-web/src/main/js/components/shared/rating.js @@ -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; diff --git a/server/sonar-web/src/main/less/init/misc.less b/server/sonar-web/src/main/less/init/misc.less index bcd74fe3882..803bc92954c 100644 --- a/server/sonar-web/src/main/less/init/misc.less +++ b/server/sonar-web/src/main/less/init/misc.less @@ -100,6 +100,10 @@ td.big-spacer-top { padding-top: 16px; } flex: 1; } +.space-between { + justify-content: space-between !important; +} + // Background Color diff --git a/server/sonar-web/src/main/less/pages/overview.less b/server/sonar-web/src/main/less/pages/overview.less index 99a414b5448..972b04cbee0 100644 --- a/server/sonar-web/src/main/less/pages/overview.less +++ b/server/sonar-web/src/main/less/pages/overview.less @@ -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; } /* @@ -78,16 +72,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 diff --git a/server/sonar-web/tests/apps/overview/components/complexity-distribution-test.js b/server/sonar-web/tests/apps/overview/components/complexity-distribution-test.js new file mode 100644 index 00000000000..a79cdbf34bc --- /dev/null +++ b/server/sonar-web/tests/apps/overview/components/complexity-distribution-test.js @@ -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']); + }); +}); diff --git a/server/sonar-web/tests/apps/overview/components/issues-tags-test.js b/server/sonar-web/tests/apps/overview/components/issues-tags-test.js new file mode 100644 index 00000000000..67d90c7c11f --- /dev/null +++ b/server/sonar-web/tests/apps/overview/components/issues-tags-test.js @@ -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' + } + ]); + }); +}); diff --git a/server/sonar-web/tests/apps/overview/components/language-distribution-test.js b/server/sonar-web/tests/apps/overview/components/language-distribution-test.js new file mode 100644 index 00000000000..a54ba2b1260 --- /dev/null +++ b/server/sonar-web/tests/apps/overview/components/language-distribution-test.js @@ -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%']); + }); +}); diff --git a/server/sonar-web/tests/apps/overview/components/legend-test.js b/server/sonar-web/tests/apps/overview/components/legend-test.js new file mode 100644 index 00000000000..d4c877eafd5 --- /dev/null +++ b/server/sonar-web/tests/apps/overview/components/legend-test.js @@ -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; + }); +}); diff --git a/server/sonar-web/tests/apps/overview/helpers/metrics-test.js b/server/sonar-web/tests/apps/overview/helpers/metrics-test.js new file mode 100644 index 00000000000..e090ea54ed3 --- /dev/null +++ b/server/sonar-web/tests/apps/overview/helpers/metrics-test.js @@ -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'); + }); + }); + + }); +}); diff --git a/server/sonar-web/tests/apps/overview/helpers/periods-test.js b/server/sonar-web/tests/apps/overview/helpers/periods-test.js new file mode 100644 index 00000000000..6dcd1ff3f96 --- /dev/null +++ b/server/sonar-web/tests/apps/overview/helpers/periods-test.js @@ -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; + }); + }); + + }); +}); diff --git a/server/sonar-web/tests/apps/overview-test.js b/server/sonar-web/tests/apps/overview/overview-test.js index 2bfea7f419a..2b7ae01d8be 100644 --- a/server/sonar-web/tests/apps/overview-test.js +++ b/server/sonar-web/tests/apps/overview/overview-test.js @@ -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 () { + + }); + }); + }); |