diff options
author | Stas Vilchik <vilchiks@gmail.com> | 2015-03-31 14:19:01 +0200 |
---|---|---|
committer | Stas Vilchik <vilchiks@gmail.com> | 2015-03-31 15:06:10 +0200 |
commit | ac60aa9eef80689f24b10857dbaf83dd1eb412fe (patch) | |
tree | 26ca0078baf516cbe24a389990907e47acbdf79c | |
parent | db171f5d73c0b6468efdd612553876406a673786 (diff) | |
download | sonarqube-ac60aa9eef80689f24b10857dbaf83dd1eb412fe.tar.gz sonarqube-ac60aa9eef80689f24b10857dbaf83dd1eb412fe.zip |
SONAR-6331 refactor overview page
-rw-r--r-- | server/sonar-web/Gruntfile.coffee | 4 | ||||
-rw-r--r-- | server/sonar-web/src/main/hbs/overview/overview-coverage.hbs | 3 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/graphics/sparkline.js | 4 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/graphics/timeline.js | 136 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/nav/context-navbar-view.js | 3 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/overview/models/state.js | 468 | ||||
-rw-r--r-- | server/sonar-web/src/test/js/overview.js | 98 | ||||
-rw-r--r-- | server/sonar-web/src/test/json/overview/issues.json | 11 | ||||
-rw-r--r-- | server/sonar-web/src/test/json/overview/measures.json | 88 | ||||
-rw-r--r-- | server/sonar-web/src/test/json/overview/metrics.json | 123 | ||||
-rw-r--r-- | server/sonar-web/src/test/json/overview/timemachine.json | 63 | ||||
-rw-r--r-- | server/sonar-web/src/test/views/layouts/main.jade | 2 | ||||
-rw-r--r-- | server/sonar-web/src/test/views/overview.jade | 17 |
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']); |