"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",
+++ /dev/null
-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';
-
-
-export const DetailedMeasure = React.createClass({
- renderLeak () {
- 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>;
- },
-
- 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 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>;
- }
-});
--- /dev/null
+import React from 'react';
+
+import { BarChart } from '../../../components/charts/bar-chart';
+import { formatMeasure } from '../../../helpers/measures';
+
+
+const HEIGHT = 80;
+
+
+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('=');
+ return { x: index, y: parseInt(tokens[1], 10), value: parseInt(tokens[0], 10) };
+ });
+
+ let xTicks = data.map(point => point.value);
+
+ let xValues = data.map(point => formatMeasure(point.y, 'INT'));
+
+ return <BarChart data={data}
+ xTicks={xTicks}
+ xValues={xValues}
+ height={HEIGHT}
+ barsWidth={10}
+ padding={[25, 0, 25, 0]}/>;
+ },
+
+ render () {
+ return <div className="overview-bar-chart">
+ {this.renderBarChart()}
+ </div>;
+ }
+});
--- /dev/null
+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>;
+ }
+});
--- /dev/null
+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>;
+ }
+});
--- /dev/null
+import React from 'react';
+
+import { formatMeasure, formatMeasureVariation, localizeMetric } from '../../../helpers/measures';
+import { DrilldownLink } from '../../../components/shared/drilldown-link';
+import { getShortType } from '../helpers/metrics';
+
+
+export const DetailedMeasure = React.createClass({
+ renderLeak () {
+ 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>;
+ },
+
+ 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>;
+ }
+});
--- /dev/null
+import _ from 'underscore';
+import React from 'react';
+
+import { BubbleChart } from '../../../components/charts/bubble-chart';
+import { getComponentUrl } from '../../../helpers/urls';
+import { getFiles } from '../../../api/components';
+import { formatMeasure } from '../../../helpers/measures';
+
+
+const HEIGHT = 360;
+
+
+function getMeasure (component, metric) {
+ return component.measures[metric] || 0;
+}
+
+
+export class DomainBubbleChart extends React.Component {
+ constructor (props) {
+ super(props);
+ this.state = {
+ loading: true,
+ files: [],
+ xMetric: this.getMetricObject(props.metrics, props.xMetric),
+ yMetric: this.getMetricObject(props.metrics, props.yMetric),
+ sizeMetrics: props.sizeMetrics.map(this.getMetricObject.bind(null, props.metrics))
+ };
+ }
+
+ componentDidMount () {
+ this.requestFiles();
+ }
+
+ requestFiles () {
+ let metrics = [].concat(this.props.xMetric, this.props.yMetric, this.props.sizeMetrics);
+ return getFiles(this.props.component.key, metrics).then(r => {
+ let files = r.map(file => {
+ let measures = {};
+ (file.msr || []).forEach(measure => {
+ measures[measure.key] = measure.val;
+ });
+ return _.extend(file, { measures });
+ });
+ this.setState({ loading: false, files });
+ });
+ }
+
+ getMetricObject (metrics, metricKey) {
+ return _.findWhere(metrics, { key: metricKey });
+ }
+
+ getSizeMetricsValue (component) {
+ return this.props.sizeMetrics.reduce((previousValue, currentValue) => {
+ return previousValue + getMeasure(component, currentValue);
+ }, 0);
+ }
+
+ getSizeMetricsTitle () {
+ return this.state.sizeMetrics.map(metric => metric.name).join(' & ');
+ }
+
+ getTooltip (component) {
+ let sizeMetricsTitle = this.getSizeMetricsTitle();
+ let sizeMetricsType = this.state.sizeMetrics[0].type;
+
+ let inner = [
+ component.name,
+ `${this.state.xMetric.name}: ${formatMeasure(getMeasure(component, this.props.xMetric), this.state.xMetric.type)}`,
+ `${this.state.yMetric.name}: ${formatMeasure(getMeasure(component, this.props.yMetric), this.state.yMetric.type)}`,
+ `${sizeMetricsTitle}: ${formatMeasure(this.getSizeMetricsValue(component), sizeMetricsType)}`
+ ].join('<br>');
+ return `<div class="text-left">${inner}</div>`;
+ }
+
+ renderLoading () {
+ return <div className="overview-chart-placeholder" style={{ height: HEIGHT }}>
+ <i className="spinner"/>
+ </div>;
+ }
+
+ renderBubbleChart () {
+ if (this.state.loading) {
+ return this.renderLoading();
+ }
+
+ let items = this.state.files.map(component => {
+ return {
+ x: getMeasure(component, this.props.xMetric),
+ y: getMeasure(component, this.props.yMetric),
+ size: this.getSizeMetricsValue(component),
+ link: getComponentUrl(component.key),
+ tooltip: this.getTooltip(component)
+ };
+ });
+ let formatXTick = (tick) => formatMeasure(tick, this.state.xMetric.type);
+ let formatYTick = (tick) => formatMeasure(tick, this.state.yMetric.type);
+ return <BubbleChart items={items}
+ height={HEIGHT}
+ padding={[25, 30, 50, 60]}
+ formatXTick={formatXTick}
+ formatYTick={formatYTick}/>;
+ }
+
+ render () {
+ 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>
+ <li>Y: {this.state.yMetric.name}</li>
+ <li>Size: {this.getSizeMetricsTitle()}</li>
+ </ul>
+ </div>
+ <div className="overview-bubble-chart">
+ {this.renderBubbleChart()}
+ </div>
+ </div>;
+ }
+}
+
+DomainBubbleChart.propTypes = {
+ xMetric: React.PropTypes.string.isRequired,
+ yMetric: React.PropTypes.string.isRequired,
+ sizeMetrics: React.PropTypes.arrayOf(React.PropTypes.string).isRequired
+};
--- /dev/null
+import _ from 'underscore';
+import moment from 'moment';
+import React from 'react';
+
+import { getTimeMachineData } from '../../../api/time-machine';
+import { getEvents } from '../../../api/events';
+import { formatMeasure, groupByDomain } from '../../../helpers/measures';
+import { getShortType } from '../helpers/metrics';
+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;
+}
+
+
+export const DomainTimeline = React.createClass({
+ propTypes: {
+ allMetrics: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
+ metrics: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
+ initialMetric: React.PropTypes.string.isRequired
+ },
+
+ getInitialState() {
+ return {
+ loading: true,
+ currentMetric: this.props.initialMetric,
+ comparisonMetric: ''
+ };
+ },
+
+ componentDidMount () {
+ Promise.all([
+ this.requestTimeMachineData(this.state.currentMetric, this.state.comparisonMetric),
+ this.requestEvents()
+ ]).then(responses => {
+ this.setState({
+ loading: false,
+ snapshots: responses[0],
+ events: responses[1]
+ });
+ });
+ },
+
+ requestTimeMachineData (currentMetric, comparisonMetric) {
+ let metricsToRequest = [currentMetric];
+ if (comparisonMetric) {
+ metricsToRequest.push(comparisonMetric);
+ }
+ return getTimeMachineData(this.props.component.key, metricsToRequest.join()).then(r => {
+ return r[0].cells.map(cell => {
+ return { date: moment(cell.d).toDate(), values: cell.v };
+ });
+ });
+ },
+
+ requestEvents () {
+ return getEvents(this.props.component.key, 'Version').then(r => {
+ let events = r.map(event => {
+ return { version: event.n, date: moment(event.dt).toDate() };
+ });
+ return _.sortBy(events, 'date');
+ });
+ },
+
+ handleMetricChange (e) {
+ let newMetric = e.target.value,
+ comparisonMetric = this.state.comparisonMetric;
+ if (newMetric === comparisonMetric) {
+ comparisonMetric = '';
+ }
+ this.requestTimeMachineData(newMetric, comparisonMetric).then(snapshots => {
+ this.setState({ currentMetric: newMetric, comparisonMetric: comparisonMetric, snapshots });
+ });
+ },
+
+ handleComparisonMetricChange (e) {
+ let newMetric = e.target.value;
+ this.requestTimeMachineData(this.state.currentMetric, newMetric).then(snapshots => {
+ this.setState({ comparisonMetric: newMetric, snapshots });
+ });
+ },
+
+ groupMetricsByDomain () {
+ return groupByDomain(this.props.metrics);
+ },
+
+ renderLoading () {
+ return <div className="overview-chart-placeholder" style={{ height: HEIGHT }}>
+ <i className="spinner"/>
+ </div>;
+ },
+
+ renderWhenNoHistoricalData () {
+ return <div className="overview-chart-placeholder" style={{ height: HEIGHT }}>
+ There is no historical data.
+ </div>;
+ },
+
+ renderLineCharts () {
+ if (this.state.loading) {
+ return this.renderLoading();
+ }
+ return <div>
+ {this.renderLineChart(this.state.snapshots, this.state.currentMetric, 0)}
+ {this.renderLineChart(this.state.snapshots, this.state.comparisonMetric, 1)}
+ </div>;
+ },
+
+ renderLineChart (snapshots, metric, index) {
+ if (!metric) {
+ return null;
+ }
+
+ if (snapshots.length < 2) {
+ return this.renderWhenNoHistoricalData();
+ }
+
+ let metricType = _.findWhere(this.props.allMetrics, { key: metric }).type;
+ let data = snapshots.map(snapshot => {
+ return {
+ x: snapshot.date,
+ y: parseValue(snapshot.values[index], metricType)
+ };
+ });
+
+ let formatValue = (value) => formatMeasure(value, metricType);
+ let formatYTick = (tick) => formatMeasure(tick, getShortType(metricType));
+
+ return <div className={'overview-timeline-' + index}>
+ <Timeline key={metric}
+ data={data}
+ events={this.state.events}
+ height={HEIGHT}
+ interpolate="linear"
+ formatValue={formatValue}
+ formatYTick={formatYTick}
+ leakPeriodDate={this.props.leakPeriodDate}
+ padding={[25, 25, 25, 60]}/>
+ </div>;
+ },
+
+ renderMetricOption (metric) {
+ return <option key={metric.key} value={metric.key}>{metric.name}</option>;
+ },
+
+ renderMetricOptions (metrics) {
+ let groupedMetrics = groupByDomain(metrics);
+ return groupedMetrics.map(metricGroup => {
+ let options = metricGroup.metrics.map(this.renderMetricOption);
+ return <optgroup key={metricGroup.domain} label={metricGroup.domain}>{options}</optgroup>;
+ });
+ },
+
+ renderTimelineMetricSelect () {
+ if (this.state.loading) {
+ return null;
+ }
+ return <span>
+ <span className="overview-timeline-sample overview-timeline-sample-0"/>
+ <select ref="metricSelect"
+ className="overview-timeline-select"
+ onChange={this.handleMetricChange}
+ value={this.state.currentMetric}>{this.renderMetricOptions(this.props.metrics)}</select>
+ </span>;
+ },
+
+ renderComparisonMetricSelect () {
+ if (this.state.loading) {
+ return null;
+ }
+ let metrics = this.props.allMetrics.filter(metric => metric.key !== this.state.currentMetric);
+ return <span>
+ {this.state.comparisonMetric ? <span className="overview-timeline-sample overview-timeline-sample-1"/> : null}
+ <select ref="comparisonMetricSelect"
+ className="overview-timeline-select"
+ onChange={this.handleComparisonMetricChange}
+ value={this.state.comparisonMetric}>
+ <option value="">Compare with...</option>
+ {this.renderMetricOptions(metrics)}
+ </select>
+ </span>;
+ },
+
+ render () {
+ return <div className="overview-domain-chart">
+ <div className="overview-card-header">
+ <div>
+ <h2 className="overview-title">Timeline</h2>
+ {this.renderTimelineMetricSelect()}
+ </div>
+ {this.renderComparisonMetricSelect()}
+ </div>
+ <div className="overview-timeline">
+ {this.renderLineCharts()}
+ </div>
+ </div>;
+ }
+});
--- /dev/null
+import _ from 'underscore';
+import React from 'react';
+
+import { Treemap } from '../../../components/charts/treemap';
+import { getChildren } from '../../../api/components';
+import { formatMeasure } from '../../../helpers/measures';
+
+
+const HEIGHT = 302;
+
+
+export class DomainTreemap extends React.Component {
+ constructor (props) {
+ super(props);
+ this.state = {
+ loading: true,
+ files: [],
+ sizeMetric: this.getMetricObject(props.metrics, props.sizeMetric),
+ colorMetric: props.colorMetric ? this.getMetricObject(props.metrics, props.colorMetric) : null
+ };
+ }
+
+ componentDidMount () {
+ this.requestComponents();
+ }
+
+ requestComponents () {
+ let metrics = [this.props.sizeMetric, this.props.colorMetric];
+ return getChildren(this.props.component.key, metrics).then(r => {
+ let components = r.map(component => {
+ let measures = {};
+ (component.msr || []).forEach(measure => {
+ measures[measure.key] = measure.val;
+ });
+ return _.extend(component, { measures });
+ });
+ this.setState({ loading: false, components });
+ });
+ }
+
+ getMetricObject (metrics, metricKey) {
+ return _.findWhere(metrics, { key: metricKey });
+ }
+
+ getTooltip (component) {
+ let inner = [
+ component.name,
+ `${this.state.sizeMetric.name}: ${formatMeasure(component.measures[this.props.sizeMetric], this.state.sizeMetric.type)}`
+ ];
+ if (this.state.colorMetric) {
+ inner.push(`${this.state.colorMetric.name}: ${formatMeasure(component.measures[this.props.colorMetric], this.state.colorMetric.type)}`);
+ }
+ inner = inner.join('<br>');
+ return `<div class="text-left">${inner}</div>`;
+ }
+
+ renderLoading () {
+ return <div className="overview-chart-placeholder" style={{ height: HEIGHT }}>
+ <i className="spinner"/>
+ </div>;
+ }
+
+ renderTreemap () {
+ if (this.state.loading) {
+ return this.renderLoading();
+ }
+
+ // TODO filter out zero sized components
+ let items = this.state.components.map(component => {
+ let colorMeasure = this.props.colorMetric ? component.measures[this.props.colorMetric] : null;
+ return {
+ size: component.measures[this.props.sizeMetric],
+ color: colorMeasure != null ? this.props.scale(colorMeasure) : '#777',
+ tooltip: this.getTooltip(component),
+ label: component.name
+ };
+ });
+ return <Treemap items={items} height={HEIGHT}/>;
+ }
+
+ render () {
+ let color = this.props.colorMetric ? <li>Color: {this.state.colorMetric.name}</li> : null;
+ 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>
+ {color}
+ </ul>
+ </div>
+ <div className="overview-treemap">
+ {this.renderTreemap()}
+ </div>
+ </div>;
+ }
+}
+
+DomainTreemap.propTypes = {
+ sizeMetric: React.PropTypes.string.isRequired,
+ colorMetric: React.PropTypes.string,
+ scale: React.PropTypes.func
+};
--- /dev/null
+import moment from 'moment';
+import React from 'react';
+
+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';
+
+
+export const IssueMeasure = 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 className="list-inline">
+ <li className="text-danger">
+ <IssuesLink className="text-danger overview-detailed-measure-value"
+ component={this.props.component.key} params={{ resolved: 'false' }}>
+ +{formatMeasure(added, getShortType(this.props.type))}
+ </IssuesLink>
+ </li>
+ <li className="text-success">
+ <span className="text-success overview-detailed-measure-value">
+ -{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 AddedRemovedMeasure = 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>
+ <IssuesLink className="text-danger"
+ component={this.props.component.key} params={{ resolved: 'false' }}>
+ <span className="overview-detailed-measure-value">
+ {formatMeasure(added, getShortType(this.props.type))}
+ </span>
+ </IssuesLink>
+ </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 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) {
+ return null;
+ }
+
+ let onNewCode = this.props.leak[this.props.leakMetric];
+
+ return <div className="overview-detailed-measure-leak">
+ <ul>
+ <li className="little-spacer-top" style={{ display: 'flex', alignItems: 'center' }}>
+ <small className="flex-1 text-left">On New Code</small>
+ <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>
+ </DrilldownLink>
+ </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 SeverityMeasure = React.createClass({
+ getMetric () {
+ return this.props.severity.toLowerCase() + '_violations';
+ },
+
+ getNewMetric () {
+ return 'new_' + this.props.severity.toLowerCase() + '_violations';
+ },
+
+
+ renderLeak () {
+ if (!this.props.leakPeriodDate) {
+ return null;
+ }
+
+ let leak = this.props.leak[this.getMetric()];
+ 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', severities: this.props.severity, createdAfter: createdAfter }}>
+ <span className="overview-detailed-measure-value">
+ {formatMeasure(added, 'SHORT_INT')}
+ </span>
+ </IssuesLink>
+ </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, 'SHORT_INT')}
+ </span>
+ </li>
+ </ul>
+ </div>;
+ },
+
+ render () {
+ let measure = this.props.measures[this.getMetric()];
+ if (measure == null) {
+ return null;
+ }
+
+ return <div className="overview-detailed-measure">
+ <div className="overview-detailed-measure-nutshell">
+ <span className="overview-detailed-measure-name">
+ <SeverityHelper severity={this.props.severity}/>
+ </span>
+ <span className="overview-detailed-measure-value">
+ <IssuesLink component={this.props.component.key}
+ params={{ resolved: 'false', severities: this.props.severity }}>
+ {formatMeasure(measure, 'SHORT_INT')}
+ </IssuesLink>
+ </span>
+ {this.props.children}
+ </div>
+ {this.renderLeak()}
+ </div>;
+ }
+});
--- /dev/null
+import React from 'react';
+import Assignee from '../../../components/shared/assignee-helper';
+import { getComponentIssuesUrl } from '../../../helpers/urls';
+import { formatMeasure } from '../../../helpers/measures';
+
+
+export default class extends React.Component {
+ render () {
+ let rows = this.props.assignees.map(s => {
+ 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}/>
+ </td>
+ <td className="thin text-right">
+ <a href={href}>{formatMeasure(s.count, 'SHORT_INT')}</a>
+ </td>
+ </tr>;
+ });
+
+ return <table className="data zebra">
+ <tbody>{rows}</tbody>
+ </table>;
+ }
+}
--- /dev/null
+import React from 'react';
+
+import { WordCloud } from '../../../components/charts/word-cloud';
+import { getComponentIssuesUrl } from '../../../helpers/urls';
+import { formatMeasure } from '../../../helpers/measures';
+
+
+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')}`;
+ return { text: tag.val, size: tag.count, link, tooltip };
+ });
+ return <WordCloud items={tags}/>;
+ }
+});
--- /dev/null
+import _ from 'underscore';
+import React from 'react';
+
+import { Histogram } from '../../../components/charts/histogram';
+import { formatMeasure } from '../../../helpers/measures';
+import { getLanguages } from '../../../api/languages';
+
+
+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) {
+ let lang = _.findWhere(this.state.languages, { key: langKey });
+ return lang ? lang.name : window.t('unknown');
+ } else {
+ return langKey;
+ }
+ },
+
+ renderBarChart () {
+ let data = this.props.distribution.split(';').map((point, index) => {
+ let tokens = point.split('=');
+ return { x: parseInt(tokens[1], 10), y: index, value: tokens[0] };
+ });
+
+ data = _.sortBy(data, d => -d.x);
+
+ let yTicks = data.map(point => this.getLanguageName(point.value));
+
+ let yValues = data.map(point => formatMeasure(point.x / this.props.lines * 100, 'PERCENT'));
+
+ return <Histogram data={data}
+ yTicks={yTicks}
+ yValues={yValues}
+ height={data.length * 25}
+ barsWidth={10}
+ padding={[0, 50, 0, 80]}/>;
+ },
+
+ render () {
+ return <div className="overview-bar-chart">
+ {this.renderBarChart()}
+ </div>;
+ }
+});
--- /dev/null
+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>;
+ }
+});
--- /dev/null
+import _ from 'underscore';
+import moment from 'moment';
+import React from 'react';
+
+import { ResizeMixin } from '../../../components/mixins/resize-mixin';
+import { TooltipsMixin } from '../../../components/mixins/tooltips-mixin';
+
+
+export const Timeline = React.createClass({
+ mixins: [ResizeMixin, TooltipsMixin],
+
+ propTypes: {
+ data: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
+ padding: React.PropTypes.arrayOf(React.PropTypes.number),
+ height: React.PropTypes.number,
+ interpolate: React.PropTypes.string
+ },
+
+ getDefaultProps() {
+ return {
+ padding: [10, 10, 10, 10],
+ interpolate: 'basis'
+ };
+ },
+
+ getInitialState() {
+ return { width: this.props.width, height: this.props.height };
+ },
+
+ renderHorizontalGrid (xScale, yScale) {
+ let ticks = yScale.ticks(4);
+ let grid = ticks.map(tick => {
+ let opts = {
+ x: xScale.range()[0],
+ y: yScale(tick)
+ };
+ return <g key={tick}>
+ <text className="line-chart-tick line-chart-tick-x" dx="-1em" dy="0.3em"
+ textAnchor="end" {...opts}>{this.props.formatYTick(tick)}</text>
+ <line className="line-chart-grid"
+ x1={xScale.range()[0]}
+ x2={xScale.range()[1]}
+ y1={yScale(tick)}
+ y2={yScale(tick)}/>
+ </g>;
+ });
+ return <g>{grid}</g>;
+ },
+
+ renderTicks (xScale, yScale) {
+ let format = xScale.tickFormat(7);
+ let ticks = xScale.ticks(7);
+ ticks = _.initial(ticks).map((tick, index) => {
+ let nextTick = index + 1 < ticks.length ? ticks[index + 1] : xScale.domain()[1];
+ let x = (xScale(tick) + xScale(nextTick)) / 2;
+ let y = yScale.range()[0];
+ return <text key={index} className="line-chart-tick" x={x} y={y} dy="1.5em">{format(tick)}</text>;
+ });
+ return <g>{ticks}</g>;
+ },
+
+ renderLeak (xScale, yScale) {
+ if (!this.props.leakPeriodDate) {
+ return null;
+ }
+ let opts = {
+ x: xScale(this.props.leakPeriodDate),
+ y: yScale.range()[1],
+ width: xScale.range()[1] - xScale(this.props.leakPeriodDate),
+ height: yScale.range()[0] - yScale.range()[1],
+ fill: '#fffae7'
+ };
+ return <rect {...opts}/>;
+ },
+
+ renderLine (xScale, yScale) {
+ let path = d3.svg.line()
+ .x(d => xScale(d.x))
+ .y(d => yScale(d.y))
+ .interpolate(this.props.interpolate);
+ return <path className="line-chart-path" d={path(this.props.data)}/>;
+ },
+
+ renderEvents(xScale, yScale) {
+ let points = this.props.events
+ .map(event => {
+ let snapshot = this.props.data.find(d => d.x.getTime() === event.date.getTime());
+ return _.extend(event, { snapshot });
+ })
+ .filter(event => event.snapshot)
+ .map(event => {
+ let key = `${event.date.getTime()}-${event.snapshot.y}`;
+ let tooltip = [
+ `<span class="nowrap">${event.version}</span>`,
+ `<span class="nowrap">${moment(event.date).format('LL')}</span>`,
+ `<span class="nowrap">${event.snapshot.y ? this.props.formatValue(event.snapshot.y) : '—'}</span>`
+ ].join('<br>');
+ return <circle key={key} className="line-chart-point"
+ r="4" cx={xScale(event.snapshot.x)} cy={yScale(event.snapshot.y)}
+ data-toggle="tooltip" data-title={tooltip}/>;
+ });
+ return <g>{points}</g>;
+ },
+
+ render () {
+ if (!this.state.width || !this.state.height) {
+ return <div/>;
+ }
+
+ let availableWidth = this.state.width - this.props.padding[1] - this.props.padding[3];
+ let availableHeight = this.state.height - this.props.padding[0] - this.props.padding[2];
+
+ let xScale = d3.time.scale()
+ .domain(d3.extent(this.props.data, d => d.x))
+ .range([0, availableWidth])
+ .clamp(true);
+ let yScale = d3.scale.linear()
+ .range([availableHeight, 0])
+ .domain([0, d3.max(this.props.data, d => d.y)])
+ .nice();
+
+ return <svg className="line-chart" width={this.state.width} height={this.state.height}>
+ <g transform={`translate(${this.props.padding[3]}, ${this.props.padding[0]})`}>
+ {this.renderLeak(xScale, yScale)}
+ {this.renderHorizontalGrid(xScale, yScale)}
+ {this.renderTicks(xScale, yScale)}
+ {this.renderLine(xScale, yScale)}
+ {this.renderEvents(xScale, yScale)}
+ </g>
+ </svg>;
+ }
+});
+++ /dev/null
-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>;
- }
-});
+++ /dev/null
-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>;
-
- }
-});
+++ /dev/null
-import _ from 'underscore';
-import React from 'react';
-import { BubbleChart } from '../../../components/charts/bubble-chart';
-import { getComponentUrl } from '../../../helpers/urls';
-import { getFiles } from '../../../api/components';
-import { formatMeasure } from '../../../helpers/measures';
-
-
-const HEIGHT = 360;
-
-
-function getMeasure (component, metric) {
- return component.measures[metric] || 0;
-}
-
-
-export class DomainBubbleChart extends React.Component {
- constructor (props) {
- super(props);
- this.state = {
- loading: true,
- files: [],
- xMetric: this.getMetricObject(props.metrics, props.xMetric),
- yMetric: this.getMetricObject(props.metrics, props.yMetric),
- sizeMetrics: props.sizeMetrics.map(this.getMetricObject.bind(null, props.metrics))
- };
- }
-
- componentDidMount () {
- this.requestFiles();
- }
-
- requestFiles () {
- let metrics = [].concat(this.props.xMetric, this.props.yMetric, this.props.sizeMetrics);
- return getFiles(this.props.component.key, metrics).then(r => {
- let files = r.map(file => {
- let measures = {};
- (file.msr || []).forEach(measure => {
- measures[measure.key] = measure.val;
- });
- return _.extend(file, { measures });
- });
- this.setState({ loading: false, files });
- });
- }
-
- getMetricObject (metrics, metricKey) {
- return _.findWhere(metrics, { key: metricKey });
- }
-
- getSizeMetricsValue (component) {
- return this.props.sizeMetrics.reduce((previousValue, currentValue) => {
- return previousValue + getMeasure(component, currentValue);
- }, 0);
- }
-
- getSizeMetricsTitle () {
- return this.state.sizeMetrics.map(metric => metric.name).join(' & ');
- }
-
- getTooltip (component) {
- let sizeMetricsTitle = this.getSizeMetricsTitle();
- let sizeMetricsType = this.state.sizeMetrics[0].type;
-
- let inner = [
- component.name,
- `${this.state.xMetric.name}: ${formatMeasure(getMeasure(component, this.props.xMetric), this.state.xMetric.type)}`,
- `${this.state.yMetric.name}: ${formatMeasure(getMeasure(component, this.props.yMetric), this.state.yMetric.type)}`,
- `${sizeMetricsTitle}: ${formatMeasure(this.getSizeMetricsValue(component), sizeMetricsType)}`
- ].join('<br>');
- return `<div class="text-left">${inner}</div>`;
- }
-
- renderLoading () {
- return <div className="overview-chart-placeholder" style={{ height: HEIGHT }}>
- <i className="spinner"/>
- </div>;
- }
-
- renderBubbleChart () {
- if (this.state.loading) {
- return this.renderLoading();
- }
-
- let items = this.state.files.map(component => {
- return {
- x: getMeasure(component, this.props.xMetric),
- y: getMeasure(component, this.props.yMetric),
- size: this.getSizeMetricsValue(component),
- link: getComponentUrl(component.key),
- tooltip: this.getTooltip(component)
- };
- });
- let formatXTick = (tick) => formatMeasure(tick, this.state.xMetric.type);
- let formatYTick = (tick) => formatMeasure(tick, this.state.yMetric.type);
- return <BubbleChart items={items}
- height={HEIGHT}
- padding={[25, 30, 50, 60]}
- formatXTick={formatXTick}
- formatYTick={formatYTick}/>;
- }
-
- render () {
- return <div className="overview-domain overview-domain-chart">
- <div className="overview-domain-header">
- <h2 className="overview-title">Project Files</h2>
- <ul className="list-inline small">
- <li>X: {this.state.xMetric.name}</li>
- <li>Y: {this.state.yMetric.name}</li>
- <li>Size: {this.getSizeMetricsTitle()}</li>
- </ul>
- </div>
- <div className="overview-bubble-chart">
- {this.renderBubbleChart()}
- </div>
- </div>;
- }
-}
-
-DomainBubbleChart.propTypes = {
- xMetric: React.PropTypes.string.isRequired,
- yMetric: React.PropTypes.string.isRequired,
- sizeMetrics: React.PropTypes.arrayOf(React.PropTypes.string).isRequired
-};
+++ /dev/null
-import React from 'react';
-
-export class DomainHeader extends React.Component {
- render () {
- return <h2 className="overview-title">{this.props.title}</h2>;
- }
-}
+++ /dev/null
-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
-};
+++ /dev/null
-
-
-
-
-
-
-
+++ /dev/null
-import _ from 'underscore';
-import React from 'react';
-
-import { Treemap } from '../../../components/charts/treemap';
-import { getChildren } from '../../../api/components';
-import { formatMeasure } from '../../../helpers/measures';
-
-const HEIGHT = 302;
-
-
-export class DomainTreemap extends React.Component {
- constructor (props) {
- super(props);
- this.state = {
- loading: true,
- files: [],
- sizeMetric: this.getMetricObject(props.metrics, props.sizeMetric),
- colorMetric: props.colorMetric ? this.getMetricObject(props.metrics, props.colorMetric) : null
- };
- }
-
- componentDidMount () {
- this.requestComponents();
- }
-
- requestComponents () {
- let metrics = [this.props.sizeMetric, this.props.colorMetric];
- return getChildren(this.props.component.key, metrics).then(r => {
- let components = r.map(component => {
- let measures = {};
- (component.msr || []).forEach(measure => {
- measures[measure.key] = measure.val;
- });
- return _.extend(component, { measures });
- });
- this.setState({ loading: false, components });
- });
- }
-
- getMetricObject (metrics, metricKey) {
- return _.findWhere(metrics, { key: metricKey });
- }
-
- getTooltip (component) {
- let inner = [
- component.name,
- `${this.state.sizeMetric.name}: ${formatMeasure(component.measures[this.props.sizeMetric], this.state.sizeMetric.type)}`
- ];
- if (this.state.colorMetric) {
- inner.push(`${this.state.colorMetric.name}: ${formatMeasure(component.measures[this.props.colorMetric], this.state.colorMetric.type)}`);
- }
- inner = inner.join('<br>');
- return `<div class="text-left">${inner}</div>`;
- }
-
- renderLoading () {
- return <div className="overview-chart-placeholder" style={{ height: HEIGHT }}>
- <i className="spinner"/>
- </div>;
- }
-
- renderTreemap () {
- if (this.state.loading) {
- return this.renderLoading();
- }
-
- // TODO filter out zero sized components
- let items = this.state.components.map(component => {
- let colorMeasure = this.props.colorMetric ? component.measures[this.props.colorMetric] : null;
- return {
- size: component.measures[this.props.sizeMetric],
- color: colorMeasure != null ? this.props.scale(colorMeasure) : '#777',
- tooltip: this.getTooltip(component),
- label: component.name
- };
- });
- return <Treemap items={items} height={HEIGHT}/>;
- }
-
- 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">
- <h2 className="overview-title">Treemap</h2>
- <ul className="list-inline small">
- <li>Size: {this.state.sizeMetric.name}</li>
- {color}
- </ul>
- </div>
- <div className="overview-treemap">
- {this.renderTreemap()}
- </div>
- </div>;
- }
-}
-
-DomainTreemap.propTypes = {
- sizeMetric: React.PropTypes.string.isRequired,
- colorMetric: React.PropTypes.string,
- scale: React.PropTypes.func
-};
--- /dev/null
+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>;
+
+ }
+});
--- /dev/null
+import _ from 'underscore';
+import d3 from 'd3';
+import React from 'react';
+
+import { getMeasuresAndVariations } from '../../../api/measures';
+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 '../components/legend';
+import { CHART_COLORS_RANGE_PERCENT } from '../../../helpers/constants';
+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 '../../../components/shared/rating';
+import { DrilldownLink } from '../../../components/shared/drilldown-link';
+
+
+const KNOWN_METRICS = ['violations', 'sqale_index', 'sqale_rating', 'sqale_debt_ratio', 'blocker_violations',
+ 'critical_violations', 'major_violations', 'minor_violations', 'info_violations', 'confirmed_issues'];
+
+
+export const IssuesMain = 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() {
+ Promise.all([
+ this.requestMeasures(),
+ this.requestIssues()
+ ]).then(responses => {
+ let measures = this.getMeasuresValues(responses[0], 'value');
+ let leak = this.getMeasuresValues(responses[0], 'var' + this.props.leakPeriodIndex);
+ let tags = this.getFacet(responses[1].facets, 'tags');
+ let assignees = extractAssignees(this.getFacet(responses[1].facets, 'assignees'), responses[1].response);
+ this.setState({ ready: true, measures, leak, tags, assignees });
+ });
+ },
+
+ getMeasuresValues (measures, fieldKey) {
+ let values = {};
+ Object.keys(measures).forEach(measureKey => {
+ values[measureKey] = measures[measureKey][fieldKey];
+ });
+ return values;
+ },
+
+ getMetricsForDomain() {
+ return this.props.metrics
+ .filter(metric => ['Issues', 'Technical Debt'].indexOf(metric.domain) !== -1)
+ .map(metric => metric.key);
+ },
+
+ getMetricsForTimeline() {
+ return filterMetricsForDomains(this.props.metrics, ['Issues', 'Technical Debt']);
+ },
+
+ getAllMetricsForTimeline() {
+ return filterMetrics(this.props.metrics);
+ },
+
+ requestMeasures () {
+ return getMeasuresAndVariations(this.props.component.key, this.getMetricsForDomain());
+ },
+
+ getFacet (facets, facetKey) {
+ return _.findWhere(facets, { property: facetKey }).values;
+ },
+
+ requestIssues () {
+ return getFacets({
+ componentUuids: this.props.component.id,
+ resolved: 'false'
+ }, ['tags', 'assignees']);
+ },
+
+ 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, ['Issues', 'Technical Debt'])
+ .filter(metric => KNOWN_METRICS.indexOf(metric.key) === -1)
+ .map(metric => {
+ return <DetailedMeasure key={metric.key} {...this.props} {...this.state} metric={metric.key}
+ type={metric.type}/>;
+ });
+ return <div>{metrics}</div>;
+ },
+
+ render () {
+ if (!this.state.ready) {
+ return this.renderLoading();
+ }
+
+ let treemapScale = d3.scale.ordinal()
+ .domain([1, 2, 3, 4, 5])
+ .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">Technical Debt Overview</div>
+ {this.renderLegend()}
+ </div>
+
+ <div className="overview-detailed-measures-list overview-detailed-measures-list-inline">
+ <div className="overview-detailed-measure overview-detailed-measure-rating">
+ <div className="overview-detailed-measure-nutshell">
+ <span className="overview-detailed-measure-value">
+ <DrilldownLink component={this.props.component.key} metric="sqale_rating">
+ <Rating value={this.state.measures['sqale_rating']}/>
+ </DrilldownLink>
+ </span>
+ </div>
+ </div>
+ <AddedRemovedMeasure {...this.props} {...this.state}
+ metric="violations" leakMetric="new_violations" type="INT"/>
+ <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"/>
+ </div>
+
+ <div className="overview-detailed-measures-list overview-detailed-measures-list-inline">
+ <SeverityMeasure {...this.props} {...this.state} severity="BLOCKER"/>
+ <SeverityMeasure {...this.props} {...this.state} severity="CRITICAL"/>
+ <SeverityMeasure {...this.props} {...this.state} severity="MAJOR"/>
+ <SeverityMeasure {...this.props} {...this.state} severity="MINOR"/>
+ <SeverityMeasure {...this.props} {...this.state} severity="INFO"/>
+ </div>
+
+ <div className="overview-detailed-measures-list overview-detailed-measures-list-inline">
+ <div className="overview-detailed-measure">
+ <div className="overview-detailed-measure-nutshell">
+ <IssuesTags {...this.props} tags={this.state.tags}/>
+ </div>
+ </div>
+ <div className="overview-detailed-measure">
+ <div className="overview-detailed-measure-nutshell">
+ <div className="overview-detailed-measure-name">
+ <StatusHelper status="OPEN"/> & <StatusHelper status="REOPENED"/> Issues
+ </div>
+ <div className="spacer-top">
+ <Assignees {...this.props} assignees={this.state.assignees}/>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div className="overview-detailed-measures-list">
+ {this.renderOtherMeasures()}
+ </div>
+ </div>
+
+ <div className="overview-card">
+ <DomainBubbleChart {...this.props}
+ xMetric="violations"
+ yMetric="sqale_index"
+ sizeMetrics={['blocker_violations', 'critical_violations']}/>
+ </div>
+ </div>
+
+ <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>;
+
+ }
+});
--- /dev/null
+import d3 from 'd3';
+import React from 'react';
+
+import { getMeasuresAndVariations } from '../../../api/measures';
+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 '../components/legend';
+import { CHART_COLORS_RANGE_PERCENT } from '../../../helpers/constants';
+
+
+export const DuplicationsMain = React.createClass({
+ mixins: [TooltipsMixin],
+
+ getInitialState() {
+ return {
+ ready: false,
+ leakPeriodLabel: getPeriodLabel(this.props.component.periods, this.props.leakPeriodIndex),
+ leakPeriodDate: getPeriodDate(this.props.component.periods, this.props.leakPeriodIndex)
+ };
+ },
+
+ componentDidMount() {
+ this.requestMeasures().then(r => {
+ let measures = this.getMeasuresValues(r, 'value');
+ let leak = this.getMeasuresValues(r, 'var' + this.props.leakPeriodIndex);
+ this.setState({ ready: true, measures, leak });
+ });
+ },
+
+ getMeasuresValues (measures, fieldKey) {
+ let values = {};
+ Object.keys(measures).forEach(measureKey => {
+ values[measureKey] = measures[measureKey][fieldKey];
+ });
+ return values;
+ },
+
+ getMetricsForDomain() {
+ return this.props.metrics
+ .filter(metric => ['Duplication'].indexOf(metric.domain) !== -1)
+ .map(metric => metric.key);
+ },
+
+ getMetricsForTimeline() {
+ return filterMetricsForDomains(this.props.metrics, ['Duplication']);
+ },
+
+ getAllMetricsForTimeline() {
+ return filterMetrics(this.props.metrics);
+ },
+
+ 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}/>;
+ },
+
+ renderMeasures() {
+ let metrics = filterMetricsForDomains(this.props.metrics, ['Duplication'])
+ .map(metric => {
+ return <DetailedMeasure key={metric.key} {...this.props} {...this.state} metric={metric.key}
+ type={metric.type}/>;
+ });
+ return <div>{metrics}</div>;
+ },
+
+ render () {
+ if (!this.state.ready) {
+ return this.renderLoading();
+ }
+ let treemapScale = d3.scale.linear()
+ .domain([0, 100])
+ .range(CHART_COLORS_RANGE_PERCENT);
+ return <div className="overview-detailed-page">
+ <div className="overview-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>
+ <div className="overview-detailed-measures-list">
+ {this.renderMeasures()}
+ </div>
+ </div>
+ <div className="overview-card">
+ <DomainBubbleChart {...this.props}
+ xMetric="ncloc"
+ yMetric="duplicated_lines"
+ sizeMetrics={['duplicated_blocks']}/>
+ </div>
+ </div>
+
+ <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>;
+
+ }
+});
--- /dev/null
+import React from 'react';
+
+import { LanguageDistribution } from './../components/language-distribution';
+import { ComplexityDistribution } from './../components/complexity-distribution';
+import { getMeasuresAndVariations } from '../../../api/measures';
+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 '../components/legend';
+
+
+export const SizeMain = 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 => ['Size', 'Complexity', 'Documentation'].indexOf(metric.domain) !== -1)
+ .map(metric => metric.key);
+ },
+
+ getMetricsForTimeline() {
+ return filterMetricsForDomains(this.props.metrics, ['Size', 'Complexity', 'Documentation']);
+ },
+
+ 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(domain, hiddenMetrics) {
+ let metrics = filterMetricsForDomains(this.props.metrics, [domain])
+ .filter(metric => hiddenMetrics.indexOf(metric.key) === -1)
+ .map(metric => {
+ return <DetailedMeasure key={metric.key} {...this.props} {...this.state} metric={metric.key}
+ type={metric.type}/>;
+ });
+ return <div>{metrics}</div>;
+ },
+
+ renderOtherSizeMeasures() {
+ return this.renderOtherMeasures('Size', ['ncloc']);
+ },
+
+ renderOtherComplexityMeasures() {
+ return this.renderOtherMeasures('Complexity',
+ ['complexity', 'function_complexity', 'file_complexity', 'class_complexity']);
+ },
+
+ renderOtherDocumentationMeasures() {
+ return this.renderOtherMeasures('Documentation', []);
+ },
+
+ render () {
+ if (!this.state.ready) {
+ return this.renderLoading();
+ }
+ return <div className="overview-detailed-page">
+ <div className="overview-card">
+ <div className="overview-card-header">
+ <div className="overview-title">Size Overview</div>
+ {this.renderLegend()}
+ </div>
+
+ <div className="overview-detailed-layout-size">
+ <div className="overview-detailed-layout-column">
+ <div className="overview-detailed-measures-list">
+ <DetailedMeasure {...this.props} {...this.state} metric="ncloc" type="INT">
+ <LanguageDistribution lines={this.state.measures['ncloc']}
+ distribution={this.state.measures['ncloc_language_distribution']}/>
+ </DetailedMeasure>
+ {this.renderOtherSizeMeasures()}
+ </div>
+ </div>
+
+ <div className="overview-detailed-layout-column">
+ <div className="overview-detailed-measures-list">
+ <DetailedMeasure {...this.props} {...this.state} metric="complexity" type="INT"/>
+ <DetailedMeasure {...this.props} {...this.state} metric="function_complexity" type="FLOAT">
+ <ComplexityDistribution distribution={this.state.measures['function_complexity_distribution']}/>
+ </DetailedMeasure>
+ <DetailedMeasure {...this.props} {...this.state} metric="file_complexity" type="FLOAT">
+ <ComplexityDistribution distribution={this.state.measures['file_complexity_distribution']}/>
+ </DetailedMeasure>
+ <DetailedMeasure {...this.props} {...this.state} metric="class_complexity" type="FLOAT"/>
+ {this.renderOtherComplexityMeasures()}
+ </div>
+ </div>
+
+ <div className="overview-detailed-layout-column">
+ <div className="overview-detailed-measures-list">
+ {this.renderOtherDocumentationMeasures()}
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <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>;
+
+ }
+});
+++ /dev/null
-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';
-
-
-export const DuplicationsMain = React.createClass({
- mixins: [TooltipsMixin],
-
- getInitialState() {
- return {
- ready: false,
- leakPeriodLabel: getPeriodLabel(this.props.component.periods, this.props.leakPeriodIndex),
- leakPeriodDate: getPeriodDate(this.props.component.periods, this.props.leakPeriodIndex)
- };
- },
-
- componentDidMount() {
- this.requestMeasures().then(r => {
- let measures = this.getMeasuresValues(r, 'value');
- let leak = this.getMeasuresValues(r, 'var' + this.props.leakPeriodIndex);
- this.setState({ ready: true, measures, leak });
- });
- },
-
- getMeasuresValues (measures, fieldKey) {
- let values = {};
- Object.keys(measures).forEach(measureKey => {
- values[measureKey] = measures[measureKey][fieldKey];
- });
- return values;
- },
-
- getMetricsForDomain() {
- return this.props.metrics
- .filter(metric => ['Duplication'].indexOf(metric.domain) !== -1)
- .map(metric => metric.key);
- },
-
- getMetricsForTimeline() {
- return filterMetricsForDomains(this.props.metrics, ['Duplication']);
- },
-
- getAllMetricsForTimeline() {
- return filterMetrics(this.props.metrics);
- },
-
- 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}/>;
- },
-
- renderMeasures() {
- let metrics = filterMetricsForDomains(this.props.metrics, ['Duplication'])
- .map(metric => {
- return <DetailedMeasure key={metric.key} {...this.props} {...this.state} metric={metric.key}
- type={metric.type}/>;
- });
- return <div>{metrics}</div>;
- },
-
- render () {
- if (!this.state.ready) {
- return this.renderLoading();
- }
- let treemapScale = d3.scale.linear()
- .domain([0, 100])
- .range(CHART_COLORS_RANGE_PERCENT);
- return <div className="overview-detailed-page">
- <div className="overview-domain-charts">
- <div className="overview-domain overview-domain-fixed-width">
- <div className="overview-domain-header">
- <div className="overview-title">Duplications Overview</div>
- {this.renderLegend()}
- </div>
- <div className="overview-detailed-measures-list">
- {this.renderMeasures()}
- </div>
- </div>
- <DomainBubbleChart {...this.props}
- xMetric="ncloc"
- yMetric="duplicated_lines"
- sizeMetrics={['duplicated_blocks']}/>
- </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>
- </div>;
-
- }
-});
--- /dev/null
+import React from 'react';
+
+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({
+ render() {
+ let metricName = window.t('metric', this.props.condition.metric.name, 'name'),
+ threshold = this.props.condition.level === 'ERROR' ?
+ this.props.condition.error : this.props.condition.warning,
+ period = this.props.condition.period ?
+ getPeriodLabel(this.props.component.periods, this.props.condition.period) : null,
+ periodDate = getPeriodDate(this.props.component.periods, this.props.condition.period);
+
+ let classes = 'alert_' + this.props.condition.level.toUpperCase();
+
+ return (
+ <li className="overview-gate-condition">
+ <div className="little-spacer-bottom">{period}</div>
+
+ <div style={{ display: 'flex', alignItems: 'center' }}>
+ <div className="overview-gate-condition-value">
+ <DrilldownLink component={this.props.component.key} metric={this.props.condition.metric.name}
+ period={this.props.condition.period} periodDate={periodDate}>
+ <span className={classes}>
+ <Measure value={this.props.condition.actual} type={this.props.condition.metric.type}/>
+ </span>
+ </DrilldownLink>
+ </div>
+
+ <div className="overview-gate-condition-metric">
+ <div>{metricName}</div>
+ <div>{window.t('quality_gates.operator', this.props.condition.op, 'short')} <Measure value={threshold} type={this.props.condition.metric.type}/></div>
+ </div>
+ </div>
+ </li>
+ );
+ }
+});
--- /dev/null
+import React from 'react';
+import GateCondition from './gate-condition';
+
+export default React.createClass({
+ propTypes: {
+ gate: React.PropTypes.object.isRequired,
+ component: React.PropTypes.object.isRequired
+ },
+
+ render() {
+ let conditions = this.props.gate.conditions
+ .filter(c => c.level !== 'OK')
+ .map(c => <GateCondition key={c.metric.name} condition={c} component={this.props.component}/>);
+ return <ul className="overview-gate-conditions-list">{conditions}</ul>;
+ }
+});
--- /dev/null
+import React from 'react';
+
+export default React.createClass({
+ render() {
+ let qualityGatesUrl = window.baseUrl + '/quality_gates';
+
+ return (
+ <div className="overview-gate">
+ <h2 className="overview-title">{window.t('overview.quality_gate')}</h2>
+ <p className="overview-gate-warning">
+ You should <a href={qualityGatesUrl}>define</a> a quality gate on this project.</p>
+ </div>
+ );
+ }
+});
--- /dev/null
+import React from 'react';
+
+import GateConditions from './gate-conditions';
+import GateEmpty from './gate-empty';
+
+
+export default React.createClass({
+ render() {
+ if (!this.props.gate || !this.props.gate.level) {
+ return this.props.component.qualifier === 'TRK' ? <GateEmpty/> : null;
+ }
+
+ let level = this.props.gate.level.toLowerCase(),
+ badgeClassName = 'badge badge-' + level,
+ badgeText = window.t('overview.gate', this.props.gate.level);
+
+ return (
+ <div className="overview-gate">
+ <h2 className="overview-title">
+ {window.t('overview.quality_gate')}
+ <span className={badgeClassName}>{badgeText}</span>
+ </h2>
+ <GateConditions gate={this.props.gate} component={this.props.component}/>
+ </div>
+ );
+ }
+});
+++ /dev/null
-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>;
- }
-});
+++ /dev/null
-import _ from 'underscore';
-import moment from 'moment';
-import React from 'react';
-import IssuesLink from './issues-link';
-import { getComponentDrilldownUrl } from '../../../helpers/urls';
-
-
-export default 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;
- },
-
- propsToIssueParams() {
- let params = {};
- if (this.props.periodDate) {
- params.createdAfter = moment(this.props.periodDate).format('YYYY-MM-DDTHH:mm:ssZZ');
- }
- switch (this.props.metric) {
- case 'blocker_violations':
- case 'new_blocker_violations':
- _.extend(params, { resolved: 'false', severities: 'BLOCKER' });
- break;
- case 'critical_violations':
- case 'new_critical_violations':
- _.extend(params, { resolved: 'false', severities: 'CRITICAL' });
- break;
- case 'major_violations':
- case 'new_major_violations':
- _.extend(params, { resolved: 'false', severities: 'MAJOR' });
- break;
- case 'minor_violations':
- case 'new_minor_violations':
- _.extend(params, { resolved: 'false', severities: 'MINOR' });
- break;
- case 'info_violations':
- case 'new_info_violations':
- _.extend(params, { resolved: 'false', severities: 'INFO' });
- break;
- case 'open_issues':
- _.extend(params, { resolved: 'false', statuses: 'OPEN' });
- break;
- case 'reopened_issues':
- _.extend(params, { resolved: 'false', statuses: 'REOPENED' });
- break;
- case 'confirmed_issues':
- _.extend(params, { resolved: 'false', statuses: 'CONFIRMED' });
- break;
- case 'false_positive_issues':
- _.extend(params, { resolutions: 'FALSE-POSITIVE' });
- break;
- default:
- _.extend(params, { resolved: 'false' });
- }
- return params;
- },
-
- renderIssuesLink() {
- return <IssuesLink component={this.props.component} params={this.propsToIssueParams()}>
- {this.props.children}
- </IssuesLink>;
- },
-
- render() {
- if (this.isIssueMeasure()) {
- return this.renderIssuesLink();
- }
-
- let url = getComponentDrilldownUrl(this.props.component, this.props.metric, this.props.period);
- return <a href={url}>{this.props.children}</a>;
- }
-});
+++ /dev/null
-import React from 'react';
-
-export default React.createClass({
- render() {
- let url = `${baseUrl}/quality_gates/show/${this.props.gate}`;
- return <a href={url}>{this.props.children}</a>;
- }
-});
+++ /dev/null
-import React from 'react';
-import { getComponentIssuesUrl } from '../../../helpers/urls';
-
-
-export default React.createClass({
- render() {
- let url = getComponentIssuesUrl(this.props.component, this.props.params);
- return <a className={this.props.className} href={url}>{this.props.children}</a>;
- }
-});
+++ /dev/null
-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>;
- }
-});
+++ /dev/null
-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>;
- }
-});
+++ /dev/null
-import _ from 'underscore';
-import moment from 'moment';
-
-export let getPeriodLabel = (periods, periodIndex) => {
- let period = _.findWhere(periods, { index: periodIndex });
- if (!period) {
- return null;
- }
- if (period.mode === 'previous_version' && !period.modeParam) {
- return window.t('overview.period.previous_version_only_date');
- }
- return window.tp(`overview.period.${period.mode}`, period.modeParam);
-};
-
-export let getPeriodDate = (periods, periodIndex) => {
- let period = _.findWhere(periods, { index: periodIndex });
- if (!period) {
- return null;
- }
- return moment(period.date).toDate();
-};
--- /dev/null
+import _ from 'underscore';
+import moment from 'moment';
+
+
+export function getPeriodLabel (periods, periodIndex) {
+ let period = _.findWhere(periods, { index: periodIndex });
+ if (!period) {
+ return null;
+ }
+ if (period.mode === 'previous_version' && !period.modeParam) {
+ return window.t('overview.period.previous_version_only_date');
+ }
+ return window.tp(`overview.period.${period.mode}`, period.modeParam);
+}
+
+
+export function getPeriodDate (periods, periodIndex) {
+ let period = _.findWhere(periods, { index: periodIndex });
+ if (!period) {
+ return null;
+ }
+ return moment(period.date).toDate();
+}
+++ /dev/null
-import React from 'react';
-
-export default React.createClass({
- render() {
- let url = `${baseUrl}/profiles/show?key=${encodeURIComponent(this.props.profile)}`;
- return <a href={url}>{this.props.children}</a>;
- }
-});
+++ /dev/null
-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, 'RATING');
- let className = 'rating rating-' + formatted;
- return <span className={className}>{formatted}</span>;
- }
-});
+++ /dev/null
-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';
-
-
-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 });
- return <tr key={s.val}>
- <td>
- <Assignee user={s.user}/>
- </td>
- <td className="thin text-right">
- <a href={href}>{formatMeasure(s.count, 'SHORT_INT')}</a>
- </td>
- </tr>;
- });
-
- return <table className="data zebra">
- <tbody>{rows}</tbody>
- </table>;
- }
-}
+++ /dev/null
-import React from 'react';
-
-import { formatMeasure, formatMeasureVariation, localizeMetric } from '../../../helpers/measures';
-import DrilldownLink from '../helpers/drilldown-link';
-import IssuesLink from '../helpers/issues-link';
-import { getShortType } from '../helpers/metrics';
-import SeverityHelper from '../../../components/shared/severity-helper';
-
-
-export const IssueMeasure = 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 className="list-inline">
- <li className="text-danger">
- <IssuesLink className="text-danger overview-detailed-measure-value"
- component={this.props.component.key} params={{ resolved: 'false' }}>
- +{formatMeasure(added, getShortType(this.props.type))}
- </IssuesLink>
- </li>
- <li className="text-success">
- <span className="text-success overview-detailed-measure-value">
- -{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 AddedRemovedMeasure = 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>
- <IssuesLink className="text-danger"
- component={this.props.component.key} params={{ resolved: 'false' }}>
- <span className="overview-detailed-measure-value">
- {formatMeasure(added, getShortType(this.props.type))}
- </span>
- </IssuesLink>
- </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) {
- return null;
- }
-
- let onNewCode = this.props.leak[this.props.leakMetric];
-
- return <div className="overview-detailed-measure-leak">
- <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' }}>
- <span className="overview-detailed-measure-value">
- {formatMeasure(onNewCode, getShortType(this.props.type))}
- </span>
- </IssuesLink>
- </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 SeverityMeasure = React.createClass({
- getMetric () {
- return this.props.severity.toLowerCase() + '_violations';
- },
-
- getNewMetric () {
- return 'new_' + this.props.severity.toLowerCase() + '_violations';
- },
-
-
- renderLeak () {
- if (!this.props.leakPeriodDate) {
- return null;
- }
-
- let leak = this.props.leak[this.getMetric()];
- let added = this.props.leak[this.getNewMetric()];
- 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>
- <IssuesLink className="text-danger"
- component={this.props.component.key} params={{ resolved: 'false' }}>
- <span className="overview-detailed-measure-value">
- {formatMeasure(added, 'SHORT_INT')}
- </span>
- </IssuesLink>
- </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, 'SHORT_INT')}
- </span>
- </li>
- </ul>
- </div>;
- },
-
- render () {
- let measure = this.props.measures[this.getMetric()];
- if (measure == null) {
- return null;
- }
-
- return <div className="overview-detailed-measure">
- <div className="overview-detailed-measure-nutshell">
- <span className="overview-detailed-measure-name">
- <SeverityHelper severity={this.props.severity}/>
- </span>
- <span className="overview-detailed-measure-value">
- <DrilldownLink component={this.props.component.key} metric={this.getMetric()}>
- {formatMeasure(measure, 'SHORT_INT')}
- </DrilldownLink>
- </span>
- {this.props.children}
- </div>
- {this.renderLeak()}
- </div>;
- }
-});
+++ /dev/null
-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 { 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 { getFacets, extractAssignees } from '../../../api/issues';
-import StatusHelper from '../../../components/shared/status-helper';
-import Rating from '../helpers/rating';
-import DrilldownLink from '../helpers/drilldown-link';
-
-
-const KNOWN_METRICS = ['violations', 'sqale_index', 'sqale_rating', 'sqale_debt_ratio', 'blocker_violations',
- 'critical_violations', 'major_violations', 'minor_violations', 'info_violations', 'confirmed_issues'];
-
-
-export const IssuesMain = 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() {
- Promise.all([
- this.requestMeasures(),
- this.requestIssues()
- ]).then(responses => {
- let measures = this.getMeasuresValues(responses[0], 'value');
- let leak = this.getMeasuresValues(responses[0], 'var' + this.props.leakPeriodIndex);
- let tags = this.getFacet(responses[1].facets, 'tags');
- let assignees = extractAssignees(this.getFacet(responses[1].facets, 'assignees'), responses[1].response);
- this.setState({ ready: true, measures, leak, tags, assignees });
- });
- },
-
- getMeasuresValues (measures, fieldKey) {
- let values = {};
- Object.keys(measures).forEach(measureKey => {
- values[measureKey] = measures[measureKey][fieldKey];
- });
- return values;
- },
-
- getMetricsForDomain() {
- return this.props.metrics
- .filter(metric => ['Issues', 'Technical Debt'].indexOf(metric.domain) !== -1)
- .map(metric => metric.key);
- },
-
- getMetricsForTimeline() {
- return filterMetricsForDomains(this.props.metrics, ['Issues', 'Technical Debt']);
- },
-
- getAllMetricsForTimeline() {
- return filterMetrics(this.props.metrics);
- },
-
- requestMeasures () {
- return getMeasuresAndVariations(this.props.component.key, this.getMetricsForDomain());
- },
-
- getFacet (facets, facetKey) {
- return _.findWhere(facets, { property: facetKey }).values;
- },
-
- requestIssues () {
- return getFacets({
- componentUuids: this.props.component.id,
- resolved: 'false'
- }, ['tags', 'assignees']);
- },
-
- 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, ['Issues', 'Technical Debt'])
- .filter(metric => KNOWN_METRICS.indexOf(metric.key) === -1)
- .map(metric => {
- return <DetailedMeasure key={metric.key} {...this.props} {...this.state} metric={metric.key}
- type={metric.type}/>;
- });
- return <div>{metrics}</div>;
- },
-
- render () {
- if (!this.state.ready) {
- return this.renderLoading();
- }
-
- let treemapScale = d3.scale.ordinal()
- .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-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-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']}/>
- </DrilldownLink>
- </span>
- </div>
- </div>
- <AddedRemovedMeasure {...this.props} {...this.state}
- metric="violations" leakMetric="new_violations" type="INT"/>
- <AddedRemovedMeasure {...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"/>
- </div>
-
- <div className="overview-detailed-measures-list overview-detailed-measures-list-inline">
- <SeverityMeasure {...this.props} {...this.state} severity="BLOCKER"/>
- <SeverityMeasure {...this.props} {...this.state} severity="CRITICAL"/>
- <SeverityMeasure {...this.props} {...this.state} severity="MAJOR"/>
- <SeverityMeasure {...this.props} {...this.state} severity="MINOR"/>
- <SeverityMeasure {...this.props} {...this.state} severity="INFO"/>
- </div>
-
- <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}/>
- </div>
- </div>
- <div className="overview-detailed-measure">
- <div className="overview-detailed-measure-nutshell">
- <div className="overview-detailed-measure-name">
- <StatusHelper status="OPEN"/> & <StatusHelper status="REOPENED"/> Issues
- </div>
- <div className="spacer-top">
- <Assignees {...this.props} assignees={this.state.assignees}/>
- </div>
- </div>
- </div>
- </div>
-
- <div className="overview-detailed-measures-list">
- {this.renderOtherMeasures()}
- </div>
- </div>
- <DomainBubbleChart {...this.props}
- xMetric="violations"
- yMetric="sqale_index"
- sizeMetrics={['blocker_violations', 'critical_violations']}/>
- </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>
- </div>;
-
- }
-});
+++ /dev/null
-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>;
- }
-}
+++ /dev/null
-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 () {
- 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')}`;
- return { text: tag.val, size: tag.count, link, tooltip };
- });
- return <WordCloud items={tags}/>;
- }
-
- render () {
- return this.renderWordCloud();
- }
-}
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>;
}
});
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>;
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';
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';
+++ /dev/null
-import React from 'react';
-
-import Measure from '../../helpers/measure';
-import { getPeriodLabel, getPeriodDate } from '../../helpers/period-label';
-import DrilldownLink from '../../helpers/drilldown-link';
-
-
-export default React.createClass({
- render() {
- let metricName = window.t('metric', this.props.condition.metric.name, 'name'),
- threshold = this.props.condition.level === 'ERROR' ?
- this.props.condition.error : this.props.condition.warning,
- period = this.props.condition.period ?
- getPeriodLabel(this.props.component.periods, this.props.condition.period) : null,
- periodDate = getPeriodDate(this.props.component.periods, this.props.condition.period);
-
- let classes = 'alert_' + this.props.condition.level.toUpperCase();
-
- return (
- <li className="overview-gate-condition">
- <div className="little-spacer-bottom">{period}</div>
-
- <div style={{ display: 'flex', alignItems: 'center' }}>
- <div className="overview-gate-condition-value">
- <DrilldownLink component={this.props.component.key} metric={this.props.condition.metric.name}
- period={this.props.condition.period} periodDate={periodDate}>
- <span className={classes}>
- <Measure value={this.props.condition.actual} type={this.props.condition.metric.type}/>
- </span>
- </DrilldownLink>
- </div>
-
- <div className="overview-gate-condition-metric">
- <div>{metricName}</div>
- <div>{window.t('quality_gates.operator', this.props.condition.op, 'short')} <Measure value={threshold} type={this.props.condition.metric.type}/></div>
- </div>
- </div>
- </li>
- );
- }
-});
+++ /dev/null
-import React from 'react';
-import GateCondition from './gate-condition';
-
-export default React.createClass({
- propTypes: {
- gate: React.PropTypes.object.isRequired,
- component: React.PropTypes.object.isRequired
- },
-
- render() {
- let conditions = this.props.gate.conditions
- .filter(c => c.level !== 'OK')
- .map(c => <GateCondition key={c.metric.name} condition={c} component={this.props.component}/>);
- return <ul className="overview-gate-conditions-list">{conditions}</ul>;
- }
-});
+++ /dev/null
-import React from 'react';
-
-export default React.createClass({
- render() {
- let qualityGatesUrl = window.baseUrl + '/quality_gates';
-
- return (
- <div className="overview-gate">
- <h2 className="overview-title">{window.t('overview.quality_gate')}</h2>
- <p className="overview-gate-warning">
- You should <a href={qualityGatesUrl}>define</a> a quality gate on this project.</p>
- </div>
- );
- }
-});
+++ /dev/null
-import React from 'react';
-
-import GateConditions from './gate-conditions';
-import GateEmpty from './gate-empty';
-
-
-export default React.createClass({
- render() {
- if (!this.props.gate || !this.props.gate.level) {
- return this.props.component.qualifier === 'TRK' ? <GateEmpty/> : null;
- }
-
- let level = this.props.gate.level.toLowerCase(),
- badgeClassName = 'badge badge-' + level,
- badgeText = window.t('overview.gate', this.props.gate.level);
-
- return (
- <div className="overview-gate">
- <h2 className="overview-title">
- {window.t('overview.quality_gate')}
- <span className={badgeClassName}>{badgeText}</span>
- </h2>
- <GateConditions gate={this.props.gate} component={this.props.component}/>
- </div>
- );
- }
-});
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({
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';
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']}/>
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({
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() {
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>
);
}),
<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>
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';
+++ /dev/null
-import React from 'react';
-
-import { BarChart } from '../../../components/charts/bar-chart';
-import { formatMeasure } from '../../../helpers/measures';
-
-
-const HEIGHT = 80;
-
-
-export class ComplexityDistribution extends React.Component {
- renderBarChart () {
- let data = this.props.distribution.split(';').map((point, index) => {
- let tokens = point.split('=');
- return { x: index, y: parseInt(tokens[1], 10), value: parseInt(tokens[0], 10) };
- });
-
- let xTicks = data.map(point => point.value);
-
- let xValues = data.map(point => formatMeasure(point.y, 'INT'));
-
- return <BarChart data={data}
- xTicks={xTicks}
- xValues={xValues}
- height={HEIGHT}
- barsWidth={10}
- padding={[25, 0, 25, 0]}/>;
- }
-
- render () {
- return <div className="overview-bar-chart">
- {this.renderBarChart()}
- </div>;
- }
-}
+++ /dev/null
-import _ from 'underscore';
-import React from 'react';
-
-import { Histogram } from '../../../components/charts/histogram';
-import { formatMeasure } from '../../../helpers/measures';
-import { getLanguages } from '../../../api/languages';
-
-
-export class LanguageDistribution extends React.Component {
- componentDidMount () {
- this.requestLanguages();
- }
-
- requestLanguages () {
- getLanguages().then(languages => this.setState({ languages }));
- }
-
- getLanguageName (langKey) {
- if (this.state && this.state.languages) {
- let lang = _.findWhere(this.state.languages, { key: langKey });
- return lang ? lang.name : window.t('unknown');
- } else {
- return langKey;
- }
- }
-
- renderBarChart () {
- let data = this.props.distribution.split(';').map((point, index) => {
- let tokens = point.split('=');
- return { x: parseInt(tokens[1], 10), y: index, value: tokens[0] };
- });
-
- data = _.sortBy(data, d => -d.x);
-
- let yTicks = data.map(point => this.getLanguageName(point.value));
-
- let yValues = data.map(point => formatMeasure(point.x / this.props.lines * 100, 'PERCENT'));
-
- return <Histogram data={data}
- yTicks={yTicks}
- yValues={yValues}
- height={data.length * 25}
- barsWidth={10}
- padding={[0, 50, 0, 80]}/>;
- }
-
- render () {
- return <div className="overview-bar-chart">
- {this.renderBarChart()}
- </div>;
- }
-}
+++ /dev/null
-import React from 'react';
-
-import { DomainLeakTitle } from '../main/components';
-import { LanguageDistribution } from './language-distribution';
-import { ComplexityDistribution } from './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 { TooltipsMixin } from '../../../components/mixins/tooltips-mixin';
-import { filterMetrics, filterMetricsForDomains } from '../helpers/metrics';
-import { Legend } from '../common-components';
-
-
-export const SizeMain = 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 => ['Size', 'Complexity', 'Documentation'].indexOf(metric.domain) !== -1)
- .map(metric => metric.key);
- },
-
- getMetricsForTimeline() {
- return filterMetricsForDomains(this.props.metrics, ['Size', 'Complexity', 'Documentation']);
- },
-
- 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(domain, hiddenMetrics) {
- let metrics = filterMetricsForDomains(this.props.metrics, [domain])
- .filter(metric => hiddenMetrics.indexOf(metric.key) === -1)
- .map(metric => {
- return <DetailedMeasure key={metric.key} {...this.props} {...this.state} metric={metric.key}
- type={metric.type}/>;
- });
- return <div>{metrics}</div>;
- },
-
- renderOtherSizeMeasures() {
- return this.renderOtherMeasures('Size', ['ncloc']);
- },
-
- renderOtherComplexityMeasures() {
- return this.renderOtherMeasures('Complexity',
- ['complexity', 'function_complexity', 'file_complexity', 'class_complexity']);
- },
-
- renderOtherDocumentationMeasures() {
- return this.renderOtherMeasures('Documentation', []);
- },
-
- render () {
- if (!this.state.ready) {
- return this.renderLoading();
- }
- return <div className="overview-detailed-page">
- <div className="overview-domain">
- <div className="overview-domain-header">
- <div className="overview-title">Size Overview</div>
- {this.renderLegend()}
- </div>
-
- <div className="overview-detailed-layout-size">
- <div className="overview-detailed-layout-column">
- <div className="overview-detailed-measures-list">
- <DetailedMeasure {...this.props} {...this.state} metric="ncloc" type="INT">
- <LanguageDistribution lines={this.state.measures['ncloc']}
- distribution={this.state.measures['ncloc_language_distribution']}/>
- </DetailedMeasure>
- {this.renderOtherSizeMeasures()}
- </div>
- </div>
-
- <div className="overview-detailed-layout-column">
- <div className="overview-detailed-measures-list">
- <DetailedMeasure {...this.props} {...this.state} metric="complexity" type="INT"/>
- <DetailedMeasure {...this.props} {...this.state} metric="function_complexity" type="FLOAT">
- <ComplexityDistribution distribution={this.state.measures['function_complexity_distribution']}/>
- </DetailedMeasure>
- <DetailedMeasure {...this.props} {...this.state} metric="file_complexity" type="FLOAT">
- <ComplexityDistribution distribution={this.state.measures['file_complexity_distribution']}/>
- </DetailedMeasure>
- <DetailedMeasure {...this.props} {...this.state} metric="class_complexity" type="FLOAT"/>
- {this.renderOtherComplexityMeasures()}
- </div>
- </div>
-
- <div className="overview-detailed-layout-column">
- <div className="overview-detailed-measures-list">
- {this.renderOtherDocumentationMeasures()}
- </div>
- </div>
- </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>
- </div>;
-
- }
-});
+++ /dev/null
-import _ from 'underscore';
-import moment from 'moment';
-import React from 'react';
-
-import { getTimeMachineData } from '../../../api/time-machine';
-import { getEvents } from '../../../api/events';
-import { formatMeasure, groupByDomain } from '../../../helpers/measures';
-import { getShortType } from '../helpers/metrics';
-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;
-}
-
-
-export const DomainTimeline = React.createClass({
- propTypes: {
- allMetrics: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
- metrics: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
- initialMetric: React.PropTypes.string.isRequired
- },
-
- getInitialState() {
- return {
- loading: true,
- currentMetric: this.props.initialMetric,
- comparisonMetric: ''
- };
- },
-
- componentDidMount () {
- Promise.all([
- this.requestTimeMachineData(this.state.currentMetric, this.state.comparisonMetric),
- this.requestEvents()
- ]).then(responses => {
- this.setState({
- loading: false,
- snapshots: responses[0],
- events: responses[1]
- });
- });
- },
-
- requestTimeMachineData (currentMetric, comparisonMetric) {
- let metricsToRequest = [currentMetric];
- if (comparisonMetric) {
- metricsToRequest.push(comparisonMetric);
- }
- return getTimeMachineData(this.props.component.key, metricsToRequest.join()).then(r => {
- return r[0].cells.map(cell => {
- return { date: moment(cell.d).toDate(), values: cell.v };
- });
- });
- },
-
- requestEvents () {
- return getEvents(this.props.component.key, 'Version').then(r => {
- let events = r.map(event => {
- return { version: event.n, date: moment(event.dt).toDate() };
- });
- return _.sortBy(events, 'date');
- });
- },
-
- handleMetricChange (e) {
- let newMetric = e.target.value,
- comparisonMetric = this.state.comparisonMetric;
- if (newMetric === comparisonMetric) {
- comparisonMetric = '';
- }
- this.requestTimeMachineData(newMetric, comparisonMetric).then(snapshots => {
- this.setState({ currentMetric: newMetric, comparisonMetric: comparisonMetric, snapshots });
- });
- },
-
- handleComparisonMetricChange (e) {
- let newMetric = e.target.value;
- this.requestTimeMachineData(this.state.currentMetric, newMetric).then(snapshots => {
- this.setState({ comparisonMetric: newMetric, snapshots });
- });
- },
-
- groupMetricsByDomain () {
- return groupByDomain(this.props.metrics);
- },
-
- renderLoading () {
- return <div className="overview-chart-placeholder" style={{ height: HEIGHT }}>
- <i className="spinner"/>
- </div>;
- },
-
- renderWhenNoHistoricalData () {
- return <div className="overview-chart-placeholder" style={{ height: HEIGHT }}>
- There is no historical data.
- </div>;
- },
-
- renderLineCharts () {
- if (this.state.loading) {
- return this.renderLoading();
- }
- return <div>
- {this.renderLineChart(this.state.snapshots, this.state.currentMetric, 0)}
- {this.renderLineChart(this.state.snapshots, this.state.comparisonMetric, 1)}
- </div>;
- },
-
- renderLineChart (snapshots, metric, index) {
- if (!metric) {
- return null;
- }
-
- if (snapshots.length < 2) {
- return this.renderWhenNoHistoricalData();
- }
-
- let metricType = _.findWhere(this.props.allMetrics, { key: metric }).type;
- let data = snapshots.map(snapshot => {
- return {
- x: snapshot.date,
- y: parseValue(snapshot.values[index], metricType)
- };
- });
-
- let formatValue = (value) => formatMeasure(value, metricType);
- let formatYTick = (tick) => formatMeasure(tick, getShortType(metricType));
-
- return <div className={'overview-timeline-' + index}>
- <Timeline key={metric}
- data={data}
- events={this.state.events}
- height={HEIGHT}
- interpolate="linear"
- formatValue={formatValue}
- formatYTick={formatYTick}
- leakPeriodDate={this.props.leakPeriodDate}
- padding={[25, 25, 25, 60]}/>
- </div>;
- },
-
- renderMetricOption (metric) {
- return <option key={metric.key} value={metric.key}>{metric.name}</option>;
- },
-
- renderMetricOptions (metrics) {
- let groupedMetrics = groupByDomain(metrics);
- return groupedMetrics.map(metricGroup => {
- let options = metricGroup.metrics.map(this.renderMetricOption);
- return <optgroup key={metricGroup.domain} label={metricGroup.domain}>{options}</optgroup>;
- });
- },
-
- renderTimelineMetricSelect () {
- if (this.state.loading) {
- return null;
- }
- return <span>
- <span className="overview-timeline-sample overview-timeline-sample-0"/>
- <select ref="metricSelect"
- className="overview-timeline-select"
- onChange={this.handleMetricChange}
- value={this.state.currentMetric}>{this.renderMetricOptions(this.props.metrics)}</select>
- </span>;
- },
-
- renderComparisonMetricSelect () {
- if (this.state.loading) {
- return null;
- }
- let metrics = this.props.allMetrics.filter(metric => metric.key !== this.state.currentMetric);
- return <span>
- {this.state.comparisonMetric ? <span className="overview-timeline-sample overview-timeline-sample-1"/> : null}
- <select ref="comparisonMetricSelect"
- className="overview-timeline-select"
- onChange={this.handleComparisonMetricChange}
- value={this.state.comparisonMetric}>
- <option value="">Compare with...</option>
- {this.renderMetricOptions(metrics)}
- </select>
- </span>;
- },
-
- render () {
- return <div className="overview-domain overview-domain-chart">
- <div className="overview-domain-header">
- <div>
- <h2 className="overview-title">Timeline</h2>
- {this.renderTimelineMetricSelect()}
- </div>
- {this.renderComparisonMetricSelect()}
- </div>
- <div className="overview-timeline">
- {this.renderLineCharts()}
- </div>
- </div>;
- }
-});
+++ /dev/null
-import _ from 'underscore';
-import moment from 'moment';
-import React from 'react';
-
-import { ResizeMixin } from '../../../components/mixins/resize-mixin';
-import { TooltipsMixin } from '../../../components/mixins/tooltips-mixin';
-
-
-export const Timeline = React.createClass({
- mixins: [ResizeMixin, TooltipsMixin],
-
- propTypes: {
- data: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
- padding: React.PropTypes.arrayOf(React.PropTypes.number),
- height: React.PropTypes.number,
- interpolate: React.PropTypes.string
- },
-
- getDefaultProps() {
- return {
- padding: [10, 10, 10, 10],
- interpolate: 'basis'
- };
- },
-
- getInitialState() {
- return { width: this.props.width, height: this.props.height };
- },
-
- renderHorizontalGrid (xScale, yScale) {
- let ticks = yScale.ticks(4);
- let grid = ticks.map(tick => {
- let opts = {
- x: xScale.range()[0],
- y: yScale(tick)
- };
- return <g key={tick}>
- <text className="line-chart-tick line-chart-tick-x" dx="-1em" dy="0.3em"
- textAnchor="end" {...opts}>{this.props.formatYTick(tick)}</text>
- <line className="line-chart-grid"
- x1={xScale.range()[0]}
- x2={xScale.range()[1]}
- y1={yScale(tick)}
- y2={yScale(tick)}/>
- </g>;
- });
- return <g>{grid}</g>;
- },
-
- renderTicks (xScale, yScale) {
- let format = xScale.tickFormat(7);
- let ticks = xScale.ticks(7);
- ticks = _.initial(ticks).map((tick, index) => {
- let nextTick = index + 1 < ticks.length ? ticks[index + 1] : xScale.domain()[1];
- let x = (xScale(tick) + xScale(nextTick)) / 2;
- let y = yScale.range()[0];
- return <text key={index} className="line-chart-tick" x={x} y={y} dy="1.5em">{format(tick)}</text>;
- });
- return <g>{ticks}</g>;
- },
-
- renderLeak (xScale, yScale) {
- if (!this.props.leakPeriodDate) {
- return null;
- }
- let opts = {
- x: xScale(this.props.leakPeriodDate),
- y: yScale.range()[1],
- width: xScale.range()[1] - xScale(this.props.leakPeriodDate),
- height: yScale.range()[0] - yScale.range()[1],
- fill: '#fffae7'
- };
- return <rect {...opts}/>;
- },
-
- renderLine (xScale, yScale) {
- let path = d3.svg.line()
- .x(d => xScale(d.x))
- .y(d => yScale(d.y))
- .interpolate(this.props.interpolate);
- return <path className="line-chart-path" d={path(this.props.data)}/>;
- },
-
- renderEvents(xScale, yScale) {
- let points = this.props.events
- .map(event => {
- let snapshot = this.props.data.find(d => d.x.getTime() === event.date.getTime());
- return _.extend(event, { snapshot });
- })
- .filter(event => event.snapshot)
- .map(event => {
- let key = `${event.date.getTime()}-${event.snapshot.y}`;
- let tooltip = [
- `<span class="nowrap">${event.version}</span>`,
- `<span class="nowrap">${moment(event.date).format('LL')}</span>`,
- `<span class="nowrap">${event.snapshot.y ? this.props.formatValue(event.snapshot.y) : '—'}</span>`
- ].join('<br>');
- return <circle key={key} className="line-chart-point"
- r="4" cx={xScale(event.snapshot.x)} cy={yScale(event.snapshot.y)}
- data-toggle="tooltip" data-title={tooltip}/>;
- });
- return <g>{points}</g>;
- },
-
- render () {
- if (!this.state.width || !this.state.height) {
- return <div/>;
- }
-
- let availableWidth = this.state.width - this.props.padding[1] - this.props.padding[3];
- let availableHeight = this.state.height - this.props.padding[0] - this.props.padding[2];
-
- let xScale = d3.time.scale()
- .domain(d3.extent(this.props.data, d => d.x))
- .range([0, availableWidth])
- .clamp(true);
- let yScale = d3.scale.linear()
- .range([availableHeight, 0])
- .domain([0, d3.max(this.props.data, d => d.y)])
- .nice();
-
- return <svg className="line-chart" width={this.state.width} height={this.state.height}>
- <g transform={`translate(${this.props.padding[3]}, ${this.props.padding[0]})`}>
- {this.renderLeak(xScale, yScale)}
- {this.renderHorizontalGrid(xScale, yScale)}
- {this.renderTicks(xScale, yScale)}
- {this.renderLine(xScale, yScale)}
- {this.renderEvents(xScale, yScale)}
- </g>
- </svg>;
- }
-});
--- /dev/null
+import _ from 'underscore';
+import moment from 'moment';
+import React from 'react';
+
+import { IssuesLink } from './issues-link';
+import { getComponentDrilldownUrl } from '../../helpers/urls';
+
+
+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() {
+ return ISSUE_MEASURES.indexOf(this.props.metric) !== -1;
+ },
+
+ propsToIssueParams() {
+ let params = {};
+ if (this.props.periodDate) {
+ params.createdAfter = moment(this.props.periodDate).format('YYYY-MM-DDTHH:mm:ssZZ');
+ }
+ switch (this.props.metric) {
+ case 'blocker_violations':
+ case 'new_blocker_violations':
+ _.extend(params, { resolved: 'false', severities: 'BLOCKER' });
+ break;
+ case 'critical_violations':
+ case 'new_critical_violations':
+ _.extend(params, { resolved: 'false', severities: 'CRITICAL' });
+ break;
+ case 'major_violations':
+ case 'new_major_violations':
+ _.extend(params, { resolved: 'false', severities: 'MAJOR' });
+ break;
+ case 'minor_violations':
+ case 'new_minor_violations':
+ _.extend(params, { resolved: 'false', severities: 'MINOR' });
+ break;
+ case 'info_violations':
+ case 'new_info_violations':
+ _.extend(params, { resolved: 'false', severities: 'INFO' });
+ break;
+ case 'open_issues':
+ _.extend(params, { resolved: 'false', statuses: 'OPEN' });
+ break;
+ case 'reopened_issues':
+ _.extend(params, { resolved: 'false', statuses: 'REOPENED' });
+ break;
+ case 'confirmed_issues':
+ _.extend(params, { resolved: 'false', statuses: 'CONFIRMED' });
+ break;
+ case 'false_positive_issues':
+ _.extend(params, { resolutions: 'FALSE-POSITIVE' });
+ break;
+ default:
+ _.extend(params, { resolved: 'false' });
+ }
+ return params;
+ },
+
+ renderIssuesLink() {
+ return <IssuesLink component={this.props.component} params={this.propsToIssueParams()}>
+ {this.props.children}
+ </IssuesLink>;
+ },
+
+ render() {
+ if (this.isIssueMeasure()) {
+ return this.renderIssuesLink();
+ }
+
+ let url = getComponentDrilldownUrl(this.props.component, this.props.metric, this.props.period);
+ return <a className={this.props.className} href={url}>{this.props.children}</a>;
+ }
+});
--- /dev/null
+import React from 'react';
+
+import { getComponentIssuesUrl } from '../../helpers/urls';
+
+
+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>;
+ }
+});
--- /dev/null
+import React from 'react';
+
+
+export const QualityGateLink = React.createClass({
+ render() {
+ let url = `${baseUrl}/quality_gates/show/${this.props.gate}`;
+ return <a href={url}>{this.props.children}</a>;
+ }
+});
--- /dev/null
+import React from 'react';
+
+
+export const QualityProfileLink = React.createClass({
+ render() {
+ let url = `${baseUrl}/profiles/show?key=${encodeURIComponent(this.props.profile)}`;
+ return <a href={url}>{this.props.children}</a>;
+ }
+});
--- /dev/null
+import React from 'react';
+
+import { formatMeasure } from '../../helpers/measures';
+
+
+export const Rating = React.createClass({
+ render() {
+ if (this.props.value == null || isNaN(this.props.value)) {
+ return null;
+ }
+ let formatted = formatMeasure(this.props.value, 'RATING');
+ let className = 'rating rating-' + formatted;
+ return <span className={className}>{formatted}</span>;
+ }
+});
flex: 1;
}
+.space-between {
+ justify-content: space-between !important;
+}
+
// Background Color
@import (reference) "../init/type";
@import (reference) "../init/links";
-@side-padding: 20px;
-
.overview {
display: flex;
flex-wrap: wrap;
background-color: @barBackgroundColor;
.overview-title {
- margin: 0 @side-padding;
+ margin: 0 20px;
}
}
}
.overview-gate-condition {
- padding: 10px @side-padding;
-}
-
-.overview-gate-condition-metric {
-
+ padding: 10px 20px;
}
.overview-gate-condition-value {
}
.overview-gate-warning {
- margin: 15px @side-padding 0;
+ margin: 15px 20px 0;
}
/*
}
}
-/*
- * Cards
- * TODO drop it
- */
-
-.overview-cards {
- display: flex;
- flex-wrap: wrap;
-}
-
/*
* Meta
*/
.overview-meta-card {
min-width: 200px;
- padding: @side-padding;
+ padding: 20px;
box-sizing: border-box;
}
* 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;
}
.overview-domain-measure {
-
}
.overview-domain-measure-value {
margin-bottom: 8px;
flex: 0 1 auto;
font-weight: 500;
+ text-align: center;
}
.overview-detailed-measure + .overview-detailed-measure {
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 {
* Charts
*/
-.overview-domain-charts {
- display: flex;
-
- & > .overview-domain,
- & > .overview-domain-chart {
- flex: 1;
- }
-}
-
.overview-domain-chart {
.overview-title {
display: inline-block;
}
}
+.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 {
cursor: pointer;
transition: all 0.2s ease;
- &:hover { fill-opacity: 0.8; }
+ &:hover {
+ fill-opacity: 0.8;
+ }
}
.bubble-chart-grid {
}
.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
+++ /dev/null
-import React from 'react';
-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';
-
-describe('Overview', function () {
-
- describe('Quality Gate', function () {
- it('should display a badge', function () {
- let output = TestUtils.renderIntoDocument(<Gate gate={{ level: 'ERROR', conditions: [] }} component={{ }}/>);
- TestUtils.findRenderedDOMComponentWithClass(output, 'badge-error');
- });
-
- it('should not be displayed', function () {
- let output = TestUtils.renderIntoDocument(<Gate component={{ }}/>);
- expect(TestUtils.scryRenderedDOMComponentsWithClass(output, 'overview-gate')).to.be.empty;
- });
-
- it('should display empty gate', function () {
- let output = TestUtils.renderIntoDocument(<Gate component={{ qualifier: 'TRK' }}/>);
- TestUtils.findRenderedDOMComponentWithClass(output, 'overview-gate');
- TestUtils.findRenderedDOMComponentWithClass(output, 'overview-gate-warning');
- });
-
- it('should filter out passed conditions', function () {
- const conditions = [
- { level: 'OK' },
- { level: 'ERROR', metric: { name: 'error metric' } },
- { level: 'WARN', metric: { name: 'warn metric' } },
- { level: 'OK' }
- ];
-
- let renderer = TestUtils.createRenderer();
- renderer.render(<GateConditions gate={{ conditions }} component={{}}/>);
- let output = renderer.getRenderOutput();
- expect(output.props.children).to.have.length(2);
- });
- });
-
-});
--- /dev/null
+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']);
+ });
+});
--- /dev/null
+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'
+ }
+ ]);
+ });
+});
--- /dev/null
+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%']);
+ });
+});
--- /dev/null
+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;
+ });
+});
--- /dev/null
+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');
+ });
+ });
+
+ });
+});
--- /dev/null
+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;
+ });
+ });
+
+ });
+});
--- /dev/null
+import React from 'react';
+import ReactDOM from 'react-dom';
+import TestUtils from 'react-addons-test-utils';
+import { expect } from 'chai';
+
+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 () {
+
+ describe('Quality Gate', function () {
+ it('should display a badge', function () {
+ let output = TestUtils.renderIntoDocument(<Gate gate={{ level: 'ERROR', conditions: [] }} component={{ }}/>);
+ TestUtils.findRenderedDOMComponentWithClass(output, 'badge-error');
+ });
+
+ it('should not be displayed', function () {
+ let output = TestUtils.renderIntoDocument(<Gate component={{ }}/>);
+ expect(TestUtils.scryRenderedDOMComponentsWithClass(output, 'overview-gate')).to.be.empty;
+ });
+
+ it('should display empty gate', function () {
+ let output = TestUtils.renderIntoDocument(<Gate component={{ qualifier: 'TRK' }}/>);
+ TestUtils.findRenderedDOMComponentWithClass(output, 'overview-gate');
+ TestUtils.findRenderedDOMComponentWithClass(output, 'overview-gate-warning');
+ });
+
+ it('should filter out passed conditions', function () {
+ const conditions = [
+ { level: 'OK' },
+ { level: 'ERROR', metric: { name: 'error metric' } },
+ { level: 'WARN', metric: { name: 'warn metric' } },
+ { level: 'OK' }
+ ];
+
+ let renderer = TestUtils.createRenderer();
+ renderer.render(<GateConditions gate={{ conditions }} component={{}}/>);
+ let output = renderer.getRenderOutput();
+ expect(output.props.children).to.have.length(2);
+ });
+ });
+
+
+ describe('Helpers', function () {
+ describe('Periods', function () {
+
+ });
+ });
+
+});