aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStas Vilchik <vilchiks@gmail.com>2015-03-31 14:19:01 +0200
committerStas Vilchik <vilchiks@gmail.com>2015-03-31 15:06:10 +0200
commitac60aa9eef80689f24b10857dbaf83dd1eb412fe (patch)
tree26ca0078baf516cbe24a389990907e47acbdf79c
parentdb171f5d73c0b6468efdd612553876406a673786 (diff)
downloadsonarqube-ac60aa9eef80689f24b10857dbaf83dd1eb412fe.tar.gz
sonarqube-ac60aa9eef80689f24b10857dbaf83dd1eb412fe.zip
SONAR-6331 refactor overview page
-rw-r--r--server/sonar-web/Gruntfile.coffee4
-rw-r--r--server/sonar-web/src/main/hbs/overview/overview-coverage.hbs3
-rw-r--r--server/sonar-web/src/main/js/graphics/sparkline.js4
-rw-r--r--server/sonar-web/src/main/js/graphics/timeline.js136
-rw-r--r--server/sonar-web/src/main/js/nav/context-navbar-view.js3
-rw-r--r--server/sonar-web/src/main/js/overview/models/state.js468
-rw-r--r--server/sonar-web/src/test/js/overview.js98
-rw-r--r--server/sonar-web/src/test/json/overview/issues.json11
-rw-r--r--server/sonar-web/src/test/json/overview/measures.json88
-rw-r--r--server/sonar-web/src/test/json/overview/metrics.json123
-rw-r--r--server/sonar-web/src/test/json/overview/timemachine.json63
-rw-r--r--server/sonar-web/src/test/views/layouts/main.jade2
-rw-r--r--server/sonar-web/src/test/views/overview.jade17
13 files changed, 577 insertions, 443 deletions
diff --git a/server/sonar-web/Gruntfile.coffee b/server/sonar-web/Gruntfile.coffee
index 2ad0c61f9b7..b61210f7459 100644
--- a/server/sonar-web/Gruntfile.coffee
+++ b/server/sonar-web/Gruntfile.coffee
@@ -107,7 +107,6 @@ module.exports = (grunt) ->
'<%= grunt.option("assetsDir") || pkg.assets %>js/widgets/tag-cloud.js'
'<%= grunt.option("assetsDir") || pkg.assets %>js/widgets/treemap.js'
'<%= grunt.option("assetsDir") || pkg.assets %>js/graphics/pie-chart.js'
- '<%= grunt.option("assetsDir") || pkg.assets %>js/graphics/timeline.js'
'<%= grunt.option("assetsDir") || pkg.assets %>js/graphics/sparkline.js'
'<%= grunt.option("assetsDir") || pkg.assets %>js/graphics/barchart.js'
'<%= grunt.option("assetsDir") || pkg.assets %>js/sortable.js'
@@ -146,7 +145,6 @@ module.exports = (grunt) ->
'<%= grunt.option("assetsDir") || pkg.assets %>js/widgets/widget.js'
'<%= grunt.option("assetsDir") || pkg.assets %>js/widgets/bubble-chart.js'
'<%= grunt.option("assetsDir") || pkg.assets %>js/widgets/timeline.js'
- '<%= grunt.option("assetsDir") || pkg.assets %>js/graphics/sparkline.js'
'<%= grunt.option("assetsDir") || pkg.assets %>js/widgets/stack-area.js'
'<%= grunt.option("assetsDir") || pkg.assets %>js/widgets/pie-chart.js'
'<%= grunt.option("assetsDir") || pkg.assets %>js/widgets/histogram.js'
@@ -154,7 +152,7 @@ module.exports = (grunt) ->
'<%= grunt.option("assetsDir") || pkg.assets %>js/widgets/tag-cloud.js'
'<%= grunt.option("assetsDir") || pkg.assets %>js/widgets/treemap.js'
'<%= grunt.option("assetsDir") || pkg.assets %>js/graphics/pie-chart.js'
- '<%= grunt.option("assetsDir") || pkg.assets %>js/graphics/timeline.js'
+ '<%= grunt.option("assetsDir") || pkg.assets %>js/graphics/sparkline.js'
'<%= grunt.option("assetsDir") || pkg.assets %>js/graphics/barchart.js'
'<%= grunt.option("assetsDir") || pkg.assets %>js/sortable.js'
'<%= grunt.option("assetsDir") || pkg.assets %>js/common/inputs.js'
diff --git a/server/sonar-web/src/main/hbs/overview/overview-coverage.hbs b/server/sonar-web/src/main/hbs/overview/overview-coverage.hbs
index 2f7b2ae8e8a..a46eb394a94 100644
--- a/server/sonar-web/src/main/hbs/overview/overview-coverage.hbs
+++ b/server/sonar-web/src/main/hbs/overview/overview-coverage.hbs
@@ -39,6 +39,7 @@
<td class="width-55"></td>
<td class="width-15">
{{#notNull newCoverage1}}
+ <div class="spacer-top"></div>
<a class="overview-measure" href="{{urlForDrilldown componentKey 'new_overall_coverage' 1}}">
{{formatMeasure newCoverage1 'PERCENT'}}
</a>
@@ -47,6 +48,7 @@
</td>
<td class="width-15">
{{#notNull newCoverage2}}
+ <div class="spacer-top"></div>
<a class="overview-measure spacer-top" href="{{urlForDrilldown componentKey 'new_overall_coverage' 2}}">
{{formatMeasure newCoverage2 'PERCENT'}}
</a>
@@ -55,6 +57,7 @@
</td>
<td class="width-15">
{{#notNull newCoverage3}}
+ <div class="spacer-top"></div>
<a class="overview-measure spacer-top" href="{{urlForDrilldown componentKey 'new_overall_coverage' 3}}">
{{formatMeasure newCoverage3 'PERCENT'}}
</a>
diff --git a/server/sonar-web/src/main/js/graphics/sparkline.js b/server/sonar-web/src/main/js/graphics/sparkline.js
index c4c22920271..461c03e13c4 100644
--- a/server/sonar-web/src/main/js/graphics/sparkline.js
+++ b/server/sonar-web/src/main/js/graphics/sparkline.js
@@ -55,7 +55,7 @@
xScale = d3.time.scale()
.domain(d3.extent(data, function (d) {
- return new Date(d.val);
+ return moment(d.val).toDate();
})),
yScale = d3.scale.linear()
@@ -65,7 +65,7 @@
line = d3.svg.line()
.x(function (d) {
- return xScale(new Date(d.val));
+ return xScale(moment(d.val).toDate());
})
.y(function (d) {
return yScale(d.count);
diff --git a/server/sonar-web/src/main/js/graphics/timeline.js b/server/sonar-web/src/main/js/graphics/timeline.js
deleted file mode 100644
index ef9fd2b0273..00000000000
--- a/server/sonar-web/src/main/js/graphics/timeline.js
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-(function ($) {
-
- function trans (left, top) {
- return 'translate(' + left + ', ' + top + ')';
- }
-
- var defaults = {
- height: 140,
- color: '#1f77b4',
- interpolate: 'basis',
- type: 'UNKNOWN'
- };
-
- /*
- * data = [
- * { val: '2015-01-30', count: 30 },
- * ...
- * ]
- */
-
- $.fn.timeline = function (data, opts) {
- $(this).each(function () {
- var options = _.defaults(opts || {}, $(this).data(), defaults);
- _.extend(options, { width: $(this).width() });
-
- var container = d3.select(this),
- svg = container.append('svg')
- .attr('width', options.width + 12)
- .attr('height', options.height + 12)
- .classed('sonar-d3', true),
-
- extra = svg.append('g'),
-
- plot = svg.append('g')
- .classed('plot', true),
-
- xScale = d3.time.scale()
- .domain(d3.extent(data, function (d) {
- return new Date(d.val);
- })),
-
- yScale = d3.scale.linear()
- .domain(d3.extent(data, function (d) {
- return d.count;
- })),
-
- line = d3.svg.line()
- .x(function (d) {
- return xScale(new Date(d.val));
- })
- .y(function (d) {
- return yScale(d.count);
- })
- .interpolate(options.interpolate);
-
- // Medians
- var medianValue = getNiceMedian(0.5, data, function (d) {
- return d.count;
- }),
- medianLabel = extra.append('text')
- .text(window.formatMeasure(medianValue, options.type))
- .style('text-anchor', 'end')
- .style('font-size', '10px')
- .style('fill', '#ccc')
- .attr('dy', '0.32em'),
- medianLabelWidth = medianLabel.node().getBBox().width;
-
- _.extend(options, {
- marginLeft: 1,
- marginRight: 1 + medianLabelWidth + 4,
- marginTop: 6,
- marginBottom: 6
- });
-
- _.extend(options, {
- availableWidth: options.width - options.marginLeft - options.marginRight,
- availableHeight: options.height - options.marginTop - options.marginBottom
- });
-
- plot.attr('transform', trans(options.marginLeft, options.marginTop));
- xScale.range([0, options.availableWidth]);
- yScale.range([options.availableHeight, 0]);
-
- plot.append('path')
- .datum(data)
- .attr('d', line)
- .classed('line', true)
- .style('stroke', options.color);
-
- medianLabel
- .attr('x', options.width - 1)
- .attr('y', options.marginTop + yScale(medianValue));
- extra.append('line')
- .attr('x1', options.marginLeft)
- .attr('y1', options.marginTop + yScale(medianValue))
- .attr('x2', options.availableWidth + options.marginLeft)
- .attr('y2', options.marginTop + yScale(medianValue))
- .style('stroke', '#eee')
- .style('shape-rendering', 'crispedges');
- }
- )
- ;
- };
-
- function getNiceMedian (p, array, accessor) {
- var min = d3.min(array, accessor),
- max = d3.max(array, accessor),
- median = d3.median(array, accessor),
- threshold = (max - min) / 2,
- threshold10 = Math.pow(10, Math.floor(Math.log(threshold) / Math.LN10) - 1);
- return (p - 0.5) > 0.0001 ?
- Math.floor(median / threshold10) * threshold10 :
- Math.ceil(median / threshold10) * threshold10;
- }
-
-})
-(window.jQuery);
diff --git a/server/sonar-web/src/main/js/nav/context-navbar-view.js b/server/sonar-web/src/main/js/nav/context-navbar-view.js
index 7c8d3e911c9..6f37d7ab2e4 100644
--- a/server/sonar-web/src/main/js/nav/context-navbar-view.js
+++ b/server/sonar-web/src/main/js/nav/context-navbar-view.js
@@ -60,10 +60,9 @@ define([
serializeData: function () {
var href = window.location.href,
- search = window.location.search,
isMoreActive = _.some(OVERVIEW_URLS, function (url) {
return href.indexOf(url) !== -1;
- });
+ }),
isSettingsActive = _.some(SETTINGS_URLS, function (url) {
return href.indexOf(url) !== -1;
});
diff --git a/server/sonar-web/src/main/js/overview/models/state.js b/server/sonar-web/src/main/js/overview/models/state.js
index 10d51657a77..c8b2f228aeb 100644
--- a/server/sonar-web/src/main/js/overview/models/state.js
+++ b/server/sonar-web/src/main/js/overview/models/state.js
@@ -19,369 +19,239 @@
*/
define(function () {
- var $ = jQuery;
+ var $ = jQuery,
+ GATE_METRIC = 'quality_gate_details',
+ SIZE_METRIC = 'ncloc',
+ ISSUES_METRIC = 'violations',
+ DEBT_METRIC = 'sqale_index',
+ COVERAGE_METRIC = 'overall_coverage',
+ NEW_COVERAGE_METRIC = 'new_overall_coverage',
+ DUPLICATIONS_METRIC = 'duplicated_lines_density';
return Backbone.Model.extend({
+
defaults: function () {
return {
qualityGateStatus: 'ERROR'
};
},
- fetch: function () {
- return $.when(
- this.fetchGate(),
-
- this.fetchSize(),
- this.fetchSizeTrend(),
-
- this.fetchIssues(),
- this.fetchIssues1(),
- this.fetchIssues2(),
- this.fetchIssues3(),
- this.fetchIssuesTrend(),
-
- this.fetchDebt(),
- this.fetchDebtTrend(),
+ hasPeriod: function (index) {
+ var property = 'period' + index + 'Date';
+ return !!this.get(property);
+ },
- this.fetchCoverage(),
- this.fetchCoverageTrend(),
+ fetch: function () {
+ var that = this;
+ this.fetchMetrics().done(function () {
+ that.fetchMeasures();
+ that.fetchTrends();
+ that.fetchIssues();
+ });
+ },
- this.fetchDuplications(),
- this.fetchDuplicationsTrend()
- );
+ fetchMetrics: function () {
+ var that = this,
+ url = baseUrl + '/api/metrics';
+ return $.get(url).done(function (r) {
+ that.set('metrics', r);
+ });
},
- fetchGate: function () {
+ fetchMeasures: function () {
var that = this,
url = baseUrl + '/api/resources/index',
options = {
resource: this.get('componentKey'),
- metrics: 'quality_gate_details'
+ metrics: [
+ GATE_METRIC,
+ SIZE_METRIC,
+ DEBT_METRIC,
+ COVERAGE_METRIC,
+ NEW_COVERAGE_METRIC,
+ DUPLICATIONS_METRIC
+ ].join(','),
+ includetrends: true
};
return $.get(url, options).done(function (r) {
- if (!r || !r[0] || !r[0].msr || !r[0].msr[0] || !r[0].msr[0].data) {
+ if (!_.isArray(r) || !_.isArray(r[0].msr)) {
return;
}
- var gateData = JSON.parse(r[0].msr[0].data),
- gateConditions = gateData.conditions,
- urlMetrics = baseUrl + '/api/metrics';
- $.get(urlMetrics).done(function (r) {
- var gateConditionsWithMetric = gateConditions.map(function (c) {
- var metric = _.findWhere(r, { key: c.metric }),
- type = metric != null ? metric.val_type : null,
- periodDate = that.get('period' + c.period + 'Date'),
- periodName = that.get('period' + c.period + 'Name');
- return _.extend(c, {
- type: type,
- periodName: periodName,
- periodDate: periodDate
- });
- });
- that.set({
- gateStatus: gateData.level,
- gateConditions: gateConditionsWithMetric
- });
- });
+ that.parseGate(r[0].msr);
+ that.parseSize(r[0].msr);
+ that.parseDebt(r[0].msr);
+ that.parseCoverage(r[0].msr);
+ that.parseDuplications(r[0].msr);
});
},
- fetchSize: function () {
+ parseGate: function (msr) {
var that = this,
- url = baseUrl + '/api/resources/index',
- options = {
- resource: this.get('componentKey'),
- metrics: 'ncloc,ncloc_language_distribution,function_complexity,file_complexity',
- includetrends: true
- };
- return $.get(url, options).done(function (r) {
- var msr = r[0].msr,
- nclocMeasure = _.findWhere(msr, { key: 'ncloc' }),
- nclocLangMeasure = _.findWhere(msr, { key: 'ncloc_language_distribution' }),
- nclocLangParsed = nclocLangMeasure.data.split(';').map(function (token) {
- var tokens = token.split('=');
- return { key: tokens[0], value: +tokens[1] };
- }),
- nclocLangSorted = _.sortBy(nclocLangParsed, function (item) {
- return -item.value;
- }),
- nclocLang = _.first(nclocLangSorted, 2),
- functionComplexityMeasure = _.findWhere(msr, { key: 'function_complexity' }),
- fileComplexityMeasure = _.findWhere(msr, { key: 'file_complexity' });
- that.set({
- ncloc: nclocMeasure && nclocMeasure.val,
- ncloc1: nclocMeasure && nclocMeasure.var1,
- ncloc2: nclocMeasure && nclocMeasure.var2,
- ncloc3: nclocMeasure && nclocMeasure.var3,
- nclocLang: nclocLang,
-
- functionComplexity: functionComplexityMeasure && functionComplexityMeasure.val,
- functionComplexity1: functionComplexityMeasure && functionComplexityMeasure.var1,
- functionComplexity2: functionComplexityMeasure && functionComplexityMeasure.var2,
- functionComplexity3: functionComplexityMeasure && functionComplexityMeasure.var3,
-
- fileComplexity: fileComplexityMeasure && fileComplexityMeasure.val,
- fileComplexity1: fileComplexityMeasure && fileComplexityMeasure.var1,
- fileComplexity2: fileComplexityMeasure && fileComplexityMeasure.var2,
- fileComplexity3: fileComplexityMeasure && fileComplexityMeasure.var3
+ measure = _.findWhere(msr, { key: GATE_METRIC });
+ if (measure != null) {
+ var metrics = this.get('metrics'),
+ gateData = JSON.parse(measure.data),
+ gateConditions = gateData.conditions,
+ gateConditionsWithMetric = gateConditions.map(function (c) {
+ var metric = _.findWhere(metrics, { key: c.metric }),
+ type = metric != null ? metric.val_type : null,
+ periodDate = that.get('period' + c.period + 'Date'),
+ periodName = that.get('period' + c.period + 'Name');
+ return _.extend(c, {
+ type: type,
+ periodName: periodName,
+ periodDate: periodDate
+ });
+ });
+ this.set({
+ gateStatus: gateData.level,
+ gateConditions: gateConditionsWithMetric
});
- });
+ }
},
- fetchSizeTrend: function () {
- var that = this,
- url = baseUrl + '/api/timemachine/index',
- options = {
- resource: this.get('componentKey'),
- metrics: 'ncloc'
- };
- return $.get(url, options).done(function (r) {
- var trend = r[0].cells.map(function (cell) {
- return { val: cell.d, count: cell.v[0] };
+ parseSize: function (msr) {
+ var nclocMeasure = _.findWhere(msr, { key: SIZE_METRIC });
+ if (nclocMeasure != null) {
+ this.set({
+ ncloc: nclocMeasure.val,
+ ncloc1: nclocMeasure.var1,
+ ncloc2: nclocMeasure.var2,
+ ncloc3: nclocMeasure.var3
});
- that.set({ sizeTrend: trend });
- });
+ }
},
fetchIssues: function () {
- var that = this,
- url = baseUrl + '/api/issues/search',
- options = {
- ps: 1,
- resolved: 'false',
- componentUuids: this.get('componentUuid'),
- facets: 'severities,statuses,tags'
- };
- return $.get(url, options).done(function (r) {
- var severityFacet = _.findWhere(r.facets, { property: 'severities' }),
- statusFacet = _.findWhere(r.facets, { property: 'statuses' }),
- tagFacet = _.findWhere(r.facets, { property: 'tags' }),
- tags = _.first(tagFacet.values, 10),
- minTagCount = _.min(tags, function (t) {
- return t.count;
- }).count,
- maxTagCount = _.max(tags, function (t) {
- return t.count;
- }).count,
- tagScale = d3.scale.linear().domain([minTagCount, maxTagCount]).range([10, 24]),
- sizedTags = tags.map(function (tag) {
- return _.extend(tag, { size: tagScale(tag.count) });
- });
- that.set({
- issues: r.total,
- blockerIssues: _.findWhere(severityFacet.values, { val: 'BLOCKER' }).count,
- criticalIssues: _.findWhere(severityFacet.values, { val: 'CRITICAL' }).count,
- majorIssues: _.findWhere(severityFacet.values, { val: 'MAJOR' }).count,
- openIssues: _.findWhere(statusFacet.values, { val: 'OPEN' }).count +
- _.findWhere(statusFacet.values, { val: 'REOPENED' }).count,
- issuesTags: sizedTags
+ var that = this;
+
+ function _fetch (field, createdAfter) {
+ var url = baseUrl + '/api/issues/search',
+ options = {
+ ps: 1,
+ resolved: 'false',
+ componentUuids: that.get('componentUuid')
+ };
+ if (createdAfter != null) {
+ _.extend(options, { createdAfter: createdAfter });
+ }
+ return $.get(url, options).done(function (r) {
+ that.set(field, r.total);
});
- });
- },
+ }
- fetchIssues1: function () {
- if (!this.get('period1Date')) {
- return;
+ _fetch('issues', null);
+ if (this.hasPeriod(1)) {
+ _fetch('issues1', this.get('period1Date'));
}
- var that = this,
- url = baseUrl + '/api/issues/search',
- options = {
- ps: 1,
- resolved: 'false',
- createdAfter: this.get('period1Date'),
- componentUuids: this.get('componentUuid'),
- facets: 'severities,statuses'
- };
- return $.get(url, options).done(function (r) {
- var severityFacet = _.findWhere(r.facets, { property: 'severities' }),
- statusFacet = _.findWhere(r.facets, { property: 'statuses' });
- that.set({
- issues1: r.total,
- blockerIssues1: _.findWhere(severityFacet.values, { val: 'BLOCKER' }).count,
- criticalIssues1: _.findWhere(severityFacet.values, { val: 'CRITICAL' }).count,
- majorIssues1: _.findWhere(severityFacet.values, { val: 'MAJOR' }).count,
- openIssues1: _.findWhere(statusFacet.values, { val: 'OPEN' }).count +
- _.findWhere(statusFacet.values, { val: 'REOPENED' }).count
+ if (this.hasPeriod(2)) {
+ _fetch('issues2', this.get('period2Date'));
+ }
+ if (this.hasPeriod(3)) {
+ _fetch('issues3', this.get('period3Date'));
+ }
+ },
+
+ parseDebt: function (msr) {
+ var debtMeasure = _.findWhere(msr, { key: DEBT_METRIC });
+ if (debtMeasure != null) {
+ this.set({
+ debt: debtMeasure.val,
+ debt1: debtMeasure.var1,
+ debt2: debtMeasure.var2,
+ debt3: debtMeasure.var3
});
- });
+ }
},
- fetchIssues2: function () {
- if (!this.get('period2Date')) {
- return;
+ parseCoverage: function (msr) {
+ var coverageMeasure = _.findWhere(msr, { key: COVERAGE_METRIC }),
+ newCoverageMeasure = _.findWhere(msr, { key: NEW_COVERAGE_METRIC });
+ if (coverageMeasure != null) {
+ this.set({
+ coverage: coverageMeasure.val,
+ coverage1: coverageMeasure.var1,
+ coverage2: coverageMeasure.var2,
+ coverage3: coverageMeasure.var3
+ });
}
- var that = this,
- url = baseUrl + '/api/issues/search',
- options = {
- ps: 1,
- resolved: 'false',
- createdAfter: this.get('period2Date'),
- componentUuids: this.get('componentUuid'),
- facets: 'severities,statuses'
- };
- return $.get(url, options).done(function (r) {
- var severityFacet = _.findWhere(r.facets, { property: 'severities' }),
- statusFacet = _.findWhere(r.facets, { property: 'statuses' });
- that.set({
- issues2: r.total,
- blockerIssues2: _.findWhere(severityFacet.values, { val: 'BLOCKER' }).count,
- criticalIssues2: _.findWhere(severityFacet.values, { val: 'CRITICAL' }).count,
- majorIssues2: _.findWhere(severityFacet.values, { val: 'MAJOR' }).count,
- openIssues2: _.findWhere(statusFacet.values, { val: 'OPEN' }).count +
- _.findWhere(statusFacet.values, { val: 'REOPENED' }).count
+ if (newCoverageMeasure != null) {
+ this.set({
+ newCoverage1: newCoverageMeasure.var1,
+ newCoverage2: newCoverageMeasure.var2,
+ newCoverage3: newCoverageMeasure.var3
});
- });
+ }
},
- fetchIssues3: function () {
- if (!this.get('period3Date')) {
- return;
- }
- var that = this,
- url = baseUrl + '/api/issues/search',
- options = {
- ps: 1,
- resolved: 'false',
- createdAfter: this.get('period3Date'),
- componentUuids: this.get('componentUuid'),
- facets: 'severities,statuses'
- };
- return $.get(url, options).done(function (r) {
- var severityFacet = _.findWhere(r.facets, { property: 'severities' }),
- statusFacet = _.findWhere(r.facets, { property: 'statuses' });
- that.set({
- issues3: r.total,
- blockerIssues3: _.findWhere(severityFacet.values, { val: 'BLOCKER' }).count,
- criticalIssues3: _.findWhere(severityFacet.values, { val: 'CRITICAL' }).count,
- majorIssues3: _.findWhere(severityFacet.values, { val: 'MAJOR' }).count,
- openIssues3: _.findWhere(statusFacet.values, { val: 'OPEN' }).count +
- _.findWhere(statusFacet.values, { val: 'REOPENED' }).count
+ parseDuplications: function (msr) {
+ var duplicationsMeasure = _.findWhere(msr, { key: DUPLICATIONS_METRIC });
+ if (duplicationsMeasure != null) {
+ this.set({
+ duplications: duplicationsMeasure.val,
+ duplications1: duplicationsMeasure.var1,
+ duplications2: duplicationsMeasure.var2,
+ duplications3: duplicationsMeasure.var3
});
- });
+ }
},
- fetchIssuesTrend: function () {
+ fetchTrends: function () {
var that = this,
url = baseUrl + '/api/timemachine/index',
options = {
resource: this.get('componentKey'),
- metrics: 'violations'
+ metrics: [
+ SIZE_METRIC,
+ ISSUES_METRIC,
+ DEBT_METRIC,
+ COVERAGE_METRIC,
+ DUPLICATIONS_METRIC
+ ].join(',')
};
return $.get(url, options).done(function (r) {
- var trend = r[0].cells.map(function (cell) {
- return { val: cell.d, count: cell.v[0] };
- });
- that.set({ issuesTrend: trend });
+ if (_.isArray(r)) {
+ that.parseSizeTrend(r[0]);
+ that.parseIssuesTrend(r[0]);
+ that.parseDebtTrend(r[0]);
+ that.parseCoverageTrend(r[0]);
+ that.parseDuplicationsTrend(r[0]);
+ }
});
},
- fetchDebt: function () {
+ parseTrend: function (r, property, metric) {
var that = this,
- url = baseUrl + '/api/resources/index',
- options = {
- resource: this.get('componentKey'),
- metrics: 'sqale_index',
- includetrends: true
- };
- return $.get(url, options).done(function (r) {
- var msr = r[0].msr,
- debtMeasure = _.findWhere(msr, { key: 'sqale_index' });
- that.set({
- debt: debtMeasure && debtMeasure.val,
- debt1: debtMeasure && debtMeasure.var1,
- debt2: debtMeasure && debtMeasure.var2,
- debt3: debtMeasure && debtMeasure.var3
- });
- });
+ index = _.pluck(r.cols, 'metric').indexOf(metric);
+ if (index !== -1) {
+ var trend = r.cells.map(function (cell) {
+ return { val: cell.d, count: cell.v[index] };
+ }),
+ filteredTrend = trend.filter(function (t) {
+ return t.val != null && t.count != null;
+ });
+ that.set(property, filteredTrend);
+ }
},
- fetchDebtTrend: function () {
- var that = this,
- url = baseUrl + '/api/timemachine/index',
- options = {
- resource: this.get('componentKey'),
- metrics: 'sqale_index'
- };
- return $.get(url, options).done(function (r) {
- var trend = r[0].cells.map(function (cell) {
- return { val: cell.d, count: cell.v[0] };
- });
- that.set({ debtTrend: trend });
- });
+ parseSizeTrend: function (r) {
+ this.parseTrend(r, 'sizeTrend', SIZE_METRIC);
},
- fetchCoverage: function () {
- var that = this,
- url = baseUrl + '/api/resources/index',
- options = {
- resource: this.get('componentKey'),
- metrics: 'overall_coverage,new_overall_coverage',
- includetrends: true
- };
- return $.get(url, options).done(function (r) {
- var msr = r[0].msr,
- coverageMeasure = _.findWhere(msr, { key: 'overall_coverage' }),
- newCoverageMeasure = _.findWhere(msr, { key: 'new_overall_coverage' });
- that.set({
- coverage: coverageMeasure && coverageMeasure.val,
- coverage1: coverageMeasure && coverageMeasure.var1,
- coverage2: coverageMeasure && coverageMeasure.var2,
- coverage3: coverageMeasure && coverageMeasure.var3,
- newCoverage1: newCoverageMeasure && newCoverageMeasure.var1,
- newCoverage2: newCoverageMeasure && newCoverageMeasure.var2,
- newCoverage3: newCoverageMeasure && newCoverageMeasure.var3
- });
- });
+ parseIssuesTrend: function (r) {
+ this.parseTrend(r, 'issuesTrend', ISSUES_METRIC);
},
- fetchCoverageTrend: function () {
- var that = this,
- url = baseUrl + '/api/timemachine/index',
- options = {
- resource: this.get('componentKey'),
- metrics: 'coverage'
- };
- return $.get(url, options).done(function (r) {
- var trend = r[0].cells.map(function (cell) {
- return { val: cell.d, count: cell.v[0] };
- });
- that.set({ coverageTrend: trend });
- });
+ parseDebtTrend: function (r) {
+ this.parseTrend(r, 'debtTrend', DEBT_METRIC);
},
- fetchDuplications: function () {
- var that = this,
- url = baseUrl + '/api/resources/index',
- options = {
- resource: this.get('componentKey'),
- metrics: 'duplicated_lines_density',
- includetrends: true
- };
- return $.get(url, options).done(function (r) {
- var msr = r[0].msr,
- duplicationsMeasure = _.findWhere(msr, { key: 'duplicated_lines_density' });
- that.set({
- duplications: duplicationsMeasure && duplicationsMeasure.val,
- duplications1: duplicationsMeasure && duplicationsMeasure.var1,
- duplications2: duplicationsMeasure && duplicationsMeasure.var2,
- duplications3: duplicationsMeasure && duplicationsMeasure.var3
- });
- });
+ parseCoverageTrend: function (r) {
+ this.parseTrend(r, 'coverageTrend', COVERAGE_METRIC);
},
- fetchDuplicationsTrend: function () {
- var that = this,
- url = baseUrl + '/api/timemachine/index',
- options = {
- resource: this.get('componentKey'),
- metrics: 'duplicated_lines_density'
- };
- return $.get(url, options).done(function (r) {
- var trend = r[0].cells.map(function (cell) {
- return { val: cell.d, count: cell.v[0] };
- });
- that.set({ duplicationsTrend: trend });
- });
+ parseDuplicationsTrend: function (r) {
+ this.parseTrend(r, 'duplicationsTrend', DUPLICATIONS_METRIC);
}
});
diff --git a/server/sonar-web/src/test/js/overview.js b/server/sonar-web/src/test/js/overview.js
new file mode 100644
index 00000000000..39ab84605cf
--- /dev/null
+++ b/server/sonar-web/src/test/js/overview.js
@@ -0,0 +1,98 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+/* globals casper: false */
+var lib = require('../lib'),
+ testName = lib.testName('Overview');
+
+lib.initMessages();
+lib.changeWorkingDirectory('overview');
+lib.configureCasper();
+
+
+casper.test.begin(testName(), 33, function (test) {
+ casper
+ .start(lib.buildUrl('overview'), function () {
+ lib.setDefaultViewport();
+
+ lib.mockRequestFromFile('/api/metrics', 'metrics.json');
+ lib.mockRequestFromFile('/api/resources/index', 'measures.json');
+ lib.mockRequestFromFile('/api/timemachine/index', 'timemachine.json');
+ lib.mockRequestFromFile('/api/issues/search', 'issues.json');
+ })
+
+ .then(function () {
+ casper.evaluate(function () {
+ require(['/js/overview/app.js']);
+ });
+ })
+
+ .then(function () {
+ casper.waitForText('165,077');
+ })
+
+ .then(function () {
+ test.assertSelectorContains('#overview-gate', '7');
+ test.assertSelectorContains('#overview-gate', '64.7%');
+ test.assertSelectorContains('#overview-gate', '2');
+ test.assertSelectorContains('#overview-gate', '5');
+ test.assertSelectorContains('#overview-gate', '0');
+ test.assertElementCount('#overview-gate .overview-status', 9);
+ test.assertElementCount('#overview-gate .overview-status-ERROR', 3);
+ test.assertElementCount('#overview-gate .overview-status-WARN', 1);
+ test.assertElementCount('#overview-gate .overview-status-OK', 5);
+
+ test.assertSelectorContains('#overview-size', '165,077');
+ test.assertSelectorContains('#overview-size', '+14');
+ test.assertSelectorContains('#overview-size', '+62,886');
+ test.assertSelectorContains('#overview-size', '+3,916');
+ test.assertExists('#overview-size-trend path');
+
+ test.assertSelectorContains('#overview-issues', '1,605');
+ test.assertExists('#overview-issues-trend path');
+
+ test.assertSelectorContains('#overview-debt', '66');
+ test.assertSelectorContains('#overview-debt', '-2');
+ test.assertSelectorContains('#overview-debt', '-49');
+ test.assertSelectorContains('#overview-debt', '-64');
+ test.assertExists('#overview-debt-trend path');
+
+ test.assertSelectorContains('#overview-coverage', '83.9%');
+ test.assertSelectorContains('#overview-coverage', '0%');
+ test.assertSelectorContains('#overview-coverage', '+0.6%');
+ test.assertSelectorContains('#overview-coverage', '88.2%');
+ test.assertSelectorContains('#overview-coverage', '87.9%');
+ test.assertSelectorContains('#overview-coverage', '90.0%');
+ test.assertExists('#overview-coverage-trend path');
+
+ test.assertSelectorContains('#overview-duplications', '1.0%');
+ test.assertSelectorContains('#overview-duplications', '0%');
+ test.assertSelectorContains('#overview-duplications', '-0.1%');
+ test.assertSelectorContains('#overview-duplications', '+0.1%');
+ test.assertExists('#overview-duplications-trend path');
+ })
+
+ .then(function () {
+ lib.sendCoverage();
+ })
+
+ .run(function () {
+ test.done();
+ });
+});
diff --git a/server/sonar-web/src/test/json/overview/issues.json b/server/sonar-web/src/test/json/overview/issues.json
new file mode 100644
index 00000000000..ffc685c0c48
--- /dev/null
+++ b/server/sonar-web/src/test/json/overview/issues.json
@@ -0,0 +1,11 @@
+{
+ "total": 1605,
+ "p": 1,
+ "ps": 1,
+ "projects": [],
+ "components": [],
+ "issues": [],
+ "rules": [],
+ "users": [],
+ "languages": []
+}
diff --git a/server/sonar-web/src/test/json/overview/measures.json b/server/sonar-web/src/test/json/overview/measures.json
new file mode 100644
index 00000000000..b48b46d7169
--- /dev/null
+++ b/server/sonar-web/src/test/json/overview/measures.json
@@ -0,0 +1,88 @@
+[
+ {
+ "id": 2865,
+ "key": "org.codehaus.sonar:sonar",
+ "name": "SonarQube",
+ "scope": "PRJ",
+ "qualifier": "TRK",
+ "date": "2015-03-30T23:08:38+0200",
+ "creationDate": null,
+ "lname": "SonarQube",
+ "version": "5.2-SNAPSHOT",
+ "description": "Open source platform for continuous inspection of code quality",
+ "p1": "previous_analysis",
+ "p1p": "2015-03-30",
+ "p1d": "2015-03-30T18:41:20+0200",
+ "p2": "days",
+ "p2p": "365",
+ "p2d": "2014-04-07T23:34:01+0200",
+ "p3": "previous_version",
+ "p3p": "5.1",
+ "p3d": "2015-03-10T12:05:17+0100",
+ "msr": [
+ {
+ "key": "ncloc",
+ "val": 165077,
+ "frmt_val": "165,077",
+ "trend": 0,
+ "var": 2,
+ "var1": 14,
+ "fvar1": "14",
+ "var2": 62886,
+ "fvar2": "62,886",
+ "var3": 3916,
+ "fvar3": "3,916"
+ },
+ {
+ "key": "duplicated_lines_density",
+ "val": 1,
+ "frmt_val": "1.0%",
+ "trend": 0,
+ "var": 0,
+ "var1": 0,
+ "fvar1": "0.0%",
+ "var2": -0.1,
+ "fvar2": "-0.1%",
+ "var3": 0.1,
+ "fvar3": "0.1%"
+ },
+ {
+ "key": "sqale_index",
+ "val": 31929,
+ "frmt_val": "66d",
+ "trend": 1,
+ "var": -2,
+ "var1": -2,
+ "fvar1": "-2min",
+ "var2": -23834,
+ "fvar2": "-49d",
+ "var3": -30819,
+ "fvar3": "-64d"
+ },
+ {
+ "key": "overall_coverage",
+ "val": 83.9,
+ "frmt_val": "83.9%",
+ "trend": 0,
+ "var": 0,
+ "var1": 0,
+ "fvar1": "0.0%",
+ "var3": 0.600000000000009,
+ "fvar3": "0.6%"
+ },
+ {
+ "key": "new_overall_coverage",
+ "var1": 88.2352941176471,
+ "fvar1": "88.2%",
+ "var2": 87.9254132585941,
+ "fvar2": "87.9%",
+ "var3": 90.0445765230312,
+ "fvar3": "90.0%"
+ },
+ {
+ "key": "quality_gate_details",
+ "data": "{\"level\":\"ERROR\",\"conditions\":[{\"metric\":\"blocker_violations\",\"op\":\"GT\",\"period\":3,\"warning\":\"\",\"error\":\"0\",\"actual\":\"7.0\",\"level\":\"ERROR\"},{\"metric\":\"new_coverage\",\"op\":\"LT\",\"period\":3,\"warning\":\"\",\"error\":\"85\",\"actual\":\"64.67002385369732\",\"level\":\"ERROR\"},{\"metric\":\"critical_violations\",\"op\":\"GT\",\"period\":3,\"warning\":\"\",\"error\":\"0\",\"actual\":\"2.0\",\"level\":\"ERROR\"},{\"metric\":\"open_issues\",\"op\":\"GT\",\"warning\":\"0\",\"error\":\"\",\"actual\":\"5.0\",\"level\":\"WARN\"},{\"metric\":\"reopened_issues\",\"op\":\"GT\",\"warning\":\"0\",\"error\":\"\",\"actual\":\"0.0\",\"level\":\"OK\"},{\"metric\":\"sqale_debt_ratio\",\"op\":\"GT\",\"warning\":\"\",\"error\":\"5\",\"actual\":\"0.6\",\"level\":\"OK\"},{\"metric\":\"test_errors\",\"op\":\"GT\",\"warning\":\"\",\"error\":\"0\",\"actual\":\"0.0\",\"level\":\"OK\"},{\"metric\":\"test_failures\",\"op\":\"GT\",\"warning\":\"\",\"error\":\"0\",\"actual\":\"0.0\",\"level\":\"OK\"},{\"metric\":\"skipped_tests\",\"op\":\"GT\",\"warning\":\"0\",\"error\":\"\",\"actual\":\"0.0\",\"level\":\"OK\"}]}"
+ }
+ ]
+ }
+]
diff --git a/server/sonar-web/src/test/json/overview/metrics.json b/server/sonar-web/src/test/json/overview/metrics.json
new file mode 100644
index 00000000000..34f7af93d1c
--- /dev/null
+++ b/server/sonar-web/src/test/json/overview/metrics.json
@@ -0,0 +1,123 @@
+[
+ {
+ "key": "blocker_violations",
+ "name": "Blocker issues",
+ "description": "Blocker issues",
+ "domain": "Issues",
+ "qualitative": true,
+ "user_managed": false,
+ "direction": -1,
+ "val_type": "INT",
+ "hidden": false
+ },
+ {
+ "key": "new_overall_coverage",
+ "name": "Overall coverage on new code",
+ "description": "Overall coverage of new/changed code",
+ "domain": "Tests (Overall)",
+ "qualitative": true,
+ "user_managed": false,
+ "direction": 1,
+ "val_type": "PERCENT",
+ "hidden": false
+ },
+ {
+ "key": "overall_coverage",
+ "name": "Overall coverage",
+ "description": "Overall test coverage",
+ "domain": "Tests (Overall)",
+ "qualitative": true,
+ "user_managed": false,
+ "direction": 1,
+ "val_type": "PERCENT",
+ "hidden": false
+ },
+ {
+ "key": "open_issues",
+ "name": "Open issues",
+ "description": "Open issues",
+ "domain": "Issues",
+ "qualitative": false,
+ "user_managed": false,
+ "direction": -1,
+ "val_type": "INT",
+ "hidden": false
+ },
+ {
+ "key": "sqale_debt_ratio",
+ "name": "Technical Debt Ratio",
+ "description": "Ratio of the actual technical debt compared to the estimated cost to develop the whole source code from scratch.",
+ "domain": "Technical Debt",
+ "qualitative": true,
+ "user_managed": false,
+ "direction": -1,
+ "val_type": "PERCENT",
+ "hidden": false
+ },
+ {
+ "key": "new_coverage",
+ "name": "Coverage on new code",
+ "description": "Coverage of new/changed code",
+ "domain": "Tests",
+ "qualitative": true,
+ "user_managed": false,
+ "direction": 1,
+ "val_type": "PERCENT",
+ "hidden": false
+ },
+ {
+ "key": "reopened_issues",
+ "name": "Reopened issues",
+ "description": "Reopened issues",
+ "domain": "Issues",
+ "qualitative": true,
+ "user_managed": false,
+ "direction": -1,
+ "val_type": "INT",
+ "hidden": false
+ },
+ {
+ "key": "skipped_tests",
+ "name": "Skipped unit tests",
+ "description": "Number of skipped unit tests",
+ "domain": "Tests",
+ "qualitative": true,
+ "user_managed": false,
+ "direction": -1,
+ "val_type": "INT",
+ "hidden": false
+ },
+ {
+ "key": "critical_violations",
+ "name": "Critical issues",
+ "description": "Critical issues",
+ "domain": "Issues",
+ "qualitative": true,
+ "user_managed": false,
+ "direction": -1,
+ "val_type": "INT",
+ "hidden": false
+ },
+ {
+ "key": "test_errors",
+ "name": "Unit tests errors",
+ "description": "Number of unit test errors",
+ "domain": "Tests",
+ "qualitative": true,
+ "user_managed": false,
+ "direction": -1,
+ "val_type": "INT",
+ "hidden": false
+ },
+ {
+ "key": "test_failures",
+ "name": "Unit tests failures",
+ "description": "Number of unit test failures",
+ "domain": "Tests",
+ "qualitative": true,
+ "user_managed": false,
+ "direction": -1,
+ "val_type": "INT",
+ "hidden": false
+ }
+]
diff --git a/server/sonar-web/src/test/json/overview/timemachine.json b/server/sonar-web/src/test/json/overview/timemachine.json
new file mode 100644
index 00000000000..a2d4391d1e7
--- /dev/null
+++ b/server/sonar-web/src/test/json/overview/timemachine.json
@@ -0,0 +1,63 @@
+[
+ {
+ "cols": [
+ {
+ "metric": "ncloc"
+ },
+ {
+ "metric": "violations"
+ },
+ {
+ "metric": "sqale_index"
+ },
+ {
+ "metric": "overall_coverage"
+ },
+ {
+ "metric": "duplicated_lines_density"
+ }
+ ],
+ "cells": [
+ {
+ "d": "2011-10-02T00:01:00+0200",
+ "v": [
+ 53922,
+ 1174,
+ 70409,
+ 15,
+ 0.8
+ ]
+ },
+ {
+ "d": "2011-10-25T12:27:41+0200",
+ "v": [
+ 59063,
+ 3710,
+ 71528,
+ 12,
+ 0.7
+ ]
+ },
+ {
+ "d": "2011-11-04T09:24:35+0100",
+ "v": [
+ 56003,
+ 1244,
+ 73259,
+ 13,
+ 0.9
+ ]
+ },
+ {
+ "d": "2011-11-08T16:11:09+0100",
+ "v": [
+ 56004,
+ 1221,
+ 72029,
+ 16,
+ 0.9
+ ]
+ }
+ ]
+ }
+]
diff --git a/server/sonar-web/src/test/views/layouts/main.jade b/server/sonar-web/src/test/views/layouts/main.jade
index 414392be48f..9c6e867aaa5 100644
--- a/server/sonar-web/src/test/views/layouts/main.jade
+++ b/server/sonar-web/src/test/views/layouts/main.jade
@@ -32,7 +32,7 @@ html
script(src='/js/widgets/tag-cloud.js')
script(src='/js/widgets/treemap.js')
script(src='/js/graphics/pie-chart.js')
- script(src='/js/graphics/timeline.js')
+ script(src='/js/graphics/sparkline.js')
script(src='/js/graphics/barchart.js')
script(src='/js/sortable.js')
script(src='/js/common/inputs.js')
diff --git a/server/sonar-web/src/test/views/overview.jade b/server/sonar-web/src/test/views/overview.jade
new file mode 100644
index 00000000000..7f50024c955
--- /dev/null
+++ b/server/sonar-web/src/test/views/overview.jade
@@ -0,0 +1,17 @@
+extends layouts/main
+
+block body
+ #content
+ .overview
+ script.
+ window.overviewConf = {
+ period1Name: 'since previous analysis',
+ period1Date: '2015-03-30T18:41:20+0200',
+ period2Name: 'over 365 days',
+ period2Date: '2014-04-07T23:34:01+0200',
+ period3Name: 'since previous version',
+ period3Date: '2015-03-10T12:05:17+0100',
+ componentKey: 'org.codehaus.sonar:sonar',
+ componentUuid: '69e57151-be0d-4157-adff-c06741d88879'
+ };
+ require(['overview/app']);