]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-6331 refactor overview page
authorStas Vilchik <vilchiks@gmail.com>
Tue, 31 Mar 2015 12:19:01 +0000 (14:19 +0200)
committerStas Vilchik <vilchiks@gmail.com>
Tue, 31 Mar 2015 13:06:10 +0000 (15:06 +0200)
13 files changed:
server/sonar-web/Gruntfile.coffee
server/sonar-web/src/main/hbs/overview/overview-coverage.hbs
server/sonar-web/src/main/js/graphics/sparkline.js
server/sonar-web/src/main/js/graphics/timeline.js [deleted file]
server/sonar-web/src/main/js/nav/context-navbar-view.js
server/sonar-web/src/main/js/overview/models/state.js
server/sonar-web/src/test/js/overview.js [new file with mode: 0644]
server/sonar-web/src/test/json/overview/issues.json [new file with mode: 0644]
server/sonar-web/src/test/json/overview/measures.json [new file with mode: 0644]
server/sonar-web/src/test/json/overview/metrics.json [new file with mode: 0644]
server/sonar-web/src/test/json/overview/timemachine.json [new file with mode: 0644]
server/sonar-web/src/test/views/layouts/main.jade
server/sonar-web/src/test/views/overview.jade [new file with mode: 0644]

index 2ad0c61f9b77c7e3368241b5ff6072e7aef93747..b61210f74593581adef49369cbbbea1d5700d8c5 100644 (file)
@@ -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'
index 2f7b2ae8e8aafcc4a4938e13d1e710b10b0d1cd9..a46eb394a9470d0ef51c791c0ca103c2c0e7726e 100644 (file)
@@ -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>
index c4c22920271a822e0b45da6648cbe4bf7ffd31b3..461c03e13c434e6a9ccd214ce0307379865e16e6 100644 (file)
@@ -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 (file)
index ef9fd2b..0000000
+++ /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);
index 7c8d3e911c9893cc525258c26f566ae191819601..6f37d7ab2e458258d35af0d9f57ab2249f29c337 100644 (file)
@@ -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;
           });
index 10d51657a7779b74ad89e3dd2c246a2765dc3ef9..c8b2f228aeb8a4f29323ce0c8994a08122d5c72c 100644 (file)
  */
 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 (file)
index 0000000..39ab846
--- /dev/null
@@ -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 (file)
index 0000000..ffc685c
--- /dev/null
@@ -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 (file)
index 0000000..b48b46d
--- /dev/null
@@ -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 (file)
index 0000000..34f7af9
--- /dev/null
@@ -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 (file)
index 0000000..a2d4391
--- /dev/null
@@ -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
+        ]
+      }
+    ]
+  }
+]
index 414392be48f9e8841de0c460e2336347d0b727ac..9c6e867aaa51b1aa807f1fd15b7d97413305c101 100644 (file)
@@ -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 (file)
index 0000000..7f50024
--- /dev/null
@@ -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']);