]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-6359 add detailed "Issues & Technical Debt" panel for the "Overview" main page
authorStas Vilchik <vilchiks@gmail.com>
Tue, 10 Nov 2015 17:13:23 +0000 (18:13 +0100)
committerStas Vilchik <vilchiks@gmail.com>
Thu, 12 Nov 2015 14:18:06 +0000 (15:18 +0100)
15 files changed:
server/sonar-web/src/main/js/api/issues.js
server/sonar-web/src/main/js/apps/overview/coverage/coverage-measure.js
server/sonar-web/src/main/js/apps/overview/helpers/issues-link.js
server/sonar-web/src/main/js/apps/overview/issues/assignees.js
server/sonar-web/src/main/js/apps/overview/issues/bubble-chart.js [deleted file]
server/sonar-web/src/main/js/apps/overview/issues/issue-measure.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/issues/main.js
server/sonar-web/src/main/js/apps/overview/issues/tags.js
server/sonar-web/src/main/js/apps/overview/issues/timeline.js [deleted file]
server/sonar-web/src/main/js/apps/overview/issues/treemap.js [deleted file]
server/sonar-web/src/main/js/apps/overview/main/issues.js
server/sonar-web/src/main/js/apps/overview/overview.js
server/sonar-web/src/main/js/components/shared/status-helper.js
server/sonar-web/src/main/less/components/tooltips.less
server/sonar-web/src/main/less/pages/overview.less

index 76a54463b7dca05770df9937506ce6c249c69dc4..07b7d1e836a33e284ab16ac328e8a4908d59c6e1 100644 (file)
@@ -3,11 +3,18 @@ import _ from 'underscore';
 import { getJSON } from '../helpers/request.js';
 
 
-export function getFacet (query, facet) {
+export function getFacets (query, facets) {
   let url = baseUrl + '/api/issues/search';
-  let data = _.extend({}, query, { facets: facet, ps: 1, additionalFields: '_all' });
+  let data = _.extend({}, query, { facets: facets.join(), ps: 1, additionalFields: '_all' });
   return getJSON(url, data).then(r => {
-    return { facet: r.facets[0].values, response: r };
+    return { facets: r.facets, response: r };
+  });
+}
+
+
+export function getFacet (query, facet) {
+  return getFacets(query, [facet]).then(r => {
+    return { facet: r.facets[0].values, response: r.response };
   });
 }
 
@@ -22,16 +29,19 @@ export function getTags (query) {
 }
 
 
-export function getAssignees (query) {
-  return getFacet(query, 'assignees').then(r => {
-    return r.facet.map(item => {
-      let user = _.findWhere(r.response.users, { login: item.val });
-      return _.extend(item, { user });
-    });
+export function extractAssignees (facet, response) {
+  return facet.map(item => {
+    let user = _.findWhere(response.users, { login: item.val });
+    return _.extend(item, { user });
   });
 }
 
 
+export function getAssignees (query) {
+  return getFacet(query, 'assignees').then(r => extractAssignees(r.facet, r.response));
+}
+
+
 export function getIssuesCount (query) {
   let url = baseUrl + '/api/issues/search';
   let data = _.extend({}, query, { ps: 1, facetMode: 'debt' });
index 4ab36466fc70af5b11559195e69932e9610c5834..00821bd35ded60cb77bbef9e4db92d180e51390f 100644 (file)
@@ -49,7 +49,7 @@ export const CoverageMeasure = React.createClass({
   },
 
   renderDonut (measure) {
-    if (this.props.metric !== 'PERCENT') {
+    if (this.props.type !== 'PERCENT') {
       return null;
     }
 
index 065961496b4e2b3c1db496572f4d512b06ce07be..c3543f597e1aff0c60984d9156e979a48e671103 100644 (file)
@@ -5,6 +5,6 @@ import { getComponentIssuesUrl } from '../../../helpers/urls';
 export default React.createClass({
   render() {
     let url = getComponentIssuesUrl(this.props.component, this.props.params);
-    return <a href={url}>{this.props.children}</a>;
+    return <a className={this.props.className} href={url}>{this.props.children}</a>;
   }
 });
index b13bc21fd0ca0e27f4f3eab0043258f30e17a81d..1d143a8ad5272543746cd4a44fbe0b8ac417deae 100644 (file)
@@ -19,11 +19,8 @@ export default class extends React.Component {
       </tr>;
     });
 
-    return <div className="overview-domain-section">
-      <DomainHeader title="Issues to Review"/>
-      <table className="data zebra">
-        <tbody>{rows}</tbody>
-      </table>
-    </div>;
+    return <table className="data zebra">
+      <tbody>{rows}</tbody>
+    </table>;
   }
 }
diff --git a/server/sonar-web/src/main/js/apps/overview/issues/bubble-chart.js b/server/sonar-web/src/main/js/apps/overview/issues/bubble-chart.js
deleted file mode 100644 (file)
index f68dae5..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-import React from 'react';
-import { DomainBubbleChart } from '../domain/bubble-chart';
-
-
-export class IssuesBubbleChart extends React.Component {
-  render () {
-    return <DomainBubbleChart {...this.props}
-        xMetric="violations"
-        yMetric="sqale_index"
-        sizeMetrics={['blocker_violations', 'critical_violations']}/>;
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/overview/issues/issue-measure.js b/server/sonar-web/src/main/js/apps/overview/issues/issue-measure.js
new file mode 100644 (file)
index 0000000..7234696
--- /dev/null
@@ -0,0 +1,217 @@
+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>;
+  }
+});
index d615b8d44a3aec26ca0953cd48ac38e107a0045d..d5fd68c110a3319efa3affe80e355a2850afb8b1 100644 (file)
+import _ from 'underscore';
+import d3 from 'd3';
 import React from 'react';
 
-import IssuesSeverities from './severities';
-import IssuesAssignees from './assignees';
-import IssuesTags from './tags';
-import { IssuesBubbleChart } from './bubble-chart';
-import { IssuesTimeline } from './timeline';
-import { IssuesTreemap } from './treemap';
+import { getMeasuresAndVariations } from '../../../api/measures';
+import { DetailedMeasure } from '../common-components';
+import { DomainTimeline } from '../timeline/domain-timeline';
+import { DomainTreemap } from '../domain/treemap';
+import { DomainBubbleChart } from '../domain/bubble-chart';
+import { getPeriodLabel, getPeriodDate } from './../helpers/period-label';
+import { TooltipsMixin } from '../../../components/mixins/tooltips-mixin';
+import { filterMetrics, filterMetricsForDomains } from '../helpers/metrics';
+import { Legend } from '../common-components';
+import { CHART_COLORS_RANGE_PERCENT } from '../../../helpers/constants';
+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';
 
-import { getSeverities, getTags, getAssignees } from '../../../api/issues';
 
+const KNOWN_METRICS = ['violations', 'sqale_index', 'sqale_rating', 'sqale_debt_ratio', 'blocker_violations',
+  'critical_violations', 'major_violations', 'minor_violations', 'info_violations', 'confirmed_issues'];
 
-export default class OverviewDomain extends React.Component {
-  constructor () {
-    super();
-    this.state = { severities: [], tags: [], assignees: [] };
-  }
 
-  componentDidMount () {
+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.requestIssuesSeverities(),
-      this.requestTags(),
-      this.requestAssignees()
+      this.requestMeasures(),
+      this.requestIssues()
     ]).then(responses => {
-      this.setState({
-        severities: responses[0],
-        tags: responses[1],
-        assignees: responses[2]
-      });
+      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 });
     });
-  }
+  },
 
-  requestSeverities () {
-    return getSeverities({ resolved: 'false', componentUuids: this.props.component.id });
-  }
+  getMeasuresValues (measures, fieldKey) {
+    let values = {};
+    Object.keys(measures).forEach(measureKey => {
+      values[measureKey] = measures[measureKey][fieldKey];
+    });
+    return values;
+  },
 
-  requestTags () {
-    return getTags({ resolved: 'false', componentUuids: this.props.component.id });
-  }
+  getMetricsForDomain() {
+    return this.props.metrics
+        .filter(metric => ['Issues', 'Technical Debt'].indexOf(metric.domain) !== -1)
+        .map(metric => metric.key);
+  },
 
-  requestAssignees () {
-    return getAssignees({ statuses: 'OPEN,REOPENED', componentUuids: this.props.component.id });
-  }
+  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-domain-header">
-        <h2 className="overview-title">Issues & Technical Debt</h2>
-      </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>
 
-      <a className="overview-detailed-page-back" href="#">
-        <i className="icon-chevron-left"/>
-      </a>
+          <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>
 
-      <IssuesTimeline {...this.props}/>
+          <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="flex-columns">
-        <div className="flex-column flex-column-third">
-          <IssuesSeverities {...this.props} severities={this.state.severities}/>
-        </div>
-        <div className="flex-column flex-column-third">
-          <IssuesTags {...this.props} tags={this.state.tags}/>
-        </div>
-        <div className="flex-column flex-column-third">
-          <IssuesAssignees {...this.props} assignees={this.state.assignees}/>
+          <div className="overview-detailed-measures-list">
+            {this.renderOtherMeasures()}
+          </div>
         </div>
+        <DomainBubbleChart {...this.props}
+            xMetric="violations"
+            yMetric="sqale_index"
+            sizeMetrics={['blocker_violations', 'critical_violations']}/>
       </div>
 
-      <IssuesBubbleChart {...this.props}/>
-      <IssuesTreemap {...this.props}/>
+      <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>;
+
   }
-}
+});
index 6c91a4539b1d6e8d058540d9f927efa4c2d0561b..8d1644d11c4ac62d06df740bc9cd106157dddbc2 100644 (file)
@@ -16,11 +16,6 @@ export default class extends React.Component {
   }
 
   render () {
-    return <div className="overview-domain-section">
-      <DomainHeader title="Issues By Tag"/>
-      <div>
-        {this.renderWordCloud()}
-      </div>
-    </div>;
+    return this.renderWordCloud();
   }
 }
diff --git a/server/sonar-web/src/main/js/apps/overview/issues/timeline.js b/server/sonar-web/src/main/js/apps/overview/issues/timeline.js
deleted file mode 100644 (file)
index 3ca3f56..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-import React from 'react';
-
-import { DomainTimeline } from '../domain/timeline';
-import { filterMetricsForDomains } from '../helpers/metrics';
-
-
-const DOMAINS = ['Issues', 'Technical Debt'];
-
-
-export class IssuesTimeline extends React.Component {
-  render () {
-    return <DomainTimeline {...this.props}
-        initialMetric="violations"
-        metrics={filterMetricsForDomains(this.props.metrics, DOMAINS)}/>;
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/overview/issues/treemap.js b/server/sonar-web/src/main/js/apps/overview/issues/treemap.js
deleted file mode 100644 (file)
index 6cab867..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-import d3 from 'd3';
-import React from 'react';
-
-import { DomainTreemap } from '../domain/treemap';
-
-
-const COLORS_5 = ['#00aa00', '#80cc00', '#ffee00', '#f77700', '#ee0000'];
-
-
-export class IssuesTreemap extends React.Component {
-  render () {
-    let scale = d3.scale.ordinal()
-        .domain([1, 2, 3, 4, 5])
-        .range(COLORS_5);
-    return <DomainTreemap {...this.props}
-        sizeMetric="ncloc"
-        colorMetric="sqale_rating"
-        scale={scale}/>;
-  }
-}
index 7cff763c5ac793cdc361f61dc87ce65b23e900ef..f281f85d08de5ad0c3c118d7f13aa43dc9601ac8 100644 (file)
@@ -67,7 +67,7 @@ export const GeneralIssues = React.createClass({
 
   render () {
     return <Domain>
-      <DomainHeader title="Technical Debt"
+      <DomainHeader title="Technical Debt" linkTo="/issues"
                     leakPeriodLabel={this.props.leakPeriodLabel} leakPeriodDate={this.props.leakPeriodDate}/>
 
       <DomainPanel domain="issues">
index 01cf406a54d0a7b5aba945dbfb3911e2c7aefca2..8b36967694dd0fcacb3c738492c2061822ccb884 100644 (file)
@@ -6,6 +6,7 @@ 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 { getMetrics } from '../../api/metrics';
 import { RouterMixin } from '../../components/router/router';
@@ -60,6 +61,12 @@ export const Overview = React.createClass({
     </div>;
   },
 
+  renderIssues () {
+    return <div className="overview">
+      <IssuesMain {...this.props} {...this.state}/>
+    </div>;
+  },
+
   render () {
     if (!this.state.ready) {
       return this.renderLoading();
@@ -73,6 +80,8 @@ export const Overview = React.createClass({
         return this.renderDuplications();
       case '/tests':
         return this.renderTests();
+      case '/issues':
+        return this.renderIssues();
       default:
         throw new Error('Unknown route: ' + this.state.route);
     }
index 033fd3ff786701131259dcecd15d1e9af9e1deeb..f3fd386334bb01fed2ecd9fb3971936f4624f504 100644 (file)
@@ -1,26 +1,22 @@
-define([
-  'libs/third-party/react',
-  './status-icon'
-], function (React, StatusIcon) {
+import React from 'react';
+import StatusIcon from './status-icon';
 
-  return React.createClass({
-    render: function () {
-      if (!this.props.status) {
-        return null;
-      }
-      var resolution;
-      if (this.props.resolution) {
-        resolution = ' (' + window.t('issue.resolution', this.props.resolution) + ')';
-      }
-      return (
-          <span>
+export default React.createClass({
+  render: function () {
+    if (!this.props.status) {
+      return null;
+    }
+    var resolution;
+    if (this.props.resolution) {
+      resolution = ' (' + window.t('issue.resolution', this.props.resolution) + ')';
+    }
+    return (
+        <span>
             <StatusIcon status={this.props.status}/>
-            &nbsp;
-            {window.t('issue.status', this.props.status)}
-            {resolution}
+          &nbsp;
+          {window.t('issue.status', this.props.status)}
+          {resolution}
           </span>
-      );
-    }
-  });
-
+    );
+  }
 });
index 5cda531615546c41e06665091f86c9d6d66c9c96..e100dc70039babde807defb35eeb9cf21b238867 100644 (file)
@@ -46,6 +46,7 @@
   background-color: @background;
   border-radius: 4px;
   letter-spacing: 0.04em;
+  overflow: hidden;
 }
 
 .tooltip-arrow {
index ecad13667f4f45a81849627381445e5aeef9e85e..99a414b5448e53b8edde2da7a23cbe0b75b30094 100644 (file)
   margin-top: 40px;
 }
 
+.overview-detailed-measures-list-inline {
+  display: flex;
+  border: none;
+  background: none;
+
+  .overview-detailed-measure {
+    flex: 1;
+    flex-direction: column;
+    border: 1px solid @barBorderColor;
+  }
+
+  .overview-detailed-measure-name {
+    margin-bottom: 8px;
+    flex: 0 1 auto;
+    font-weight: 500;
+  }
+
+  .overview-detailed-measure + .overview-detailed-measure {
+    margin-left: 10px;
+  }
+
+  .overview-detailed-measure-nutshell {
+    flex-flow: column nowrap;
+    justify-content: flex-start;
+    align-items: stretch;
+
+    .overview-detailed-measure-value {
+      flex: 1;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+    }
+  }
+
+  .overview-detailed-measure-leak {
+    flex: 0 1 auto;
+  }
+}
+
 .overview-detailed-measure {
   display: flex;
   background-color: #fff;
   position: relative;
   padding: 7px 10px;
 
-  & & {
+  .overview-detailed-measure-nutshell,
+  .overview-detailed-measure-leak,
+  .overview-detailed-measure-chart {
     padding-right: 0;
   }
 }