aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStas Vilchik <vilchiks@gmail.com>2015-04-15 14:33:55 +0200
committerStas Vilchik <vilchiks@gmail.com>2015-04-15 15:00:18 +0200
commitcd5957698e0110754c73fd8118de3909d3e96c00 (patch)
tree7eb438dea9e9365144db94c9253783f836c82d99
parent5a707bfaece70e2c4690637d05266368aaa61ad9 (diff)
downloadsonarqube-cd5957698e0110754c73fd8118de3909d3e96c00.tar.gz
sonarqube-cd5957698e0110754c73fd8118de3909d3e96c00.zip
SONAR-5851 add comparison
-rw-r--r--server/sonar-web/src/main/hbs/quality-profiles/quality-profile-comparison.hbs85
-rw-r--r--server/sonar-web/src/main/hbs/quality-profiles/quality-profiles-profile-details.hbs2
-rw-r--r--server/sonar-web/src/main/js/quality-profiles/controller.js11
-rw-r--r--server/sonar-web/src/main/js/quality-profiles/profile-comparison-view.js60
-rw-r--r--server/sonar-web/src/main/js/quality-profiles/profile-details-view.js7
-rw-r--r--server/sonar-web/src/main/js/quality-profiles/profile.js17
-rw-r--r--server/sonar-web/src/main/js/quality-profiles/router.js8
-rw-r--r--server/sonar-web/src/main/less/init/misc.less1
-rw-r--r--server/sonar-web/src/main/webapp/WEB-INF/app/controllers/profiles_controller.rb4
-rw-r--r--server/sonar-web/src/test/js/quality-profiles.js98
-rw-r--r--server/sonar-web/src/test/json/quality-profiles/compare.json99
11 files changed, 389 insertions, 3 deletions
diff --git a/server/sonar-web/src/main/hbs/quality-profiles/quality-profile-comparison.hbs b/server/sonar-web/src/main/hbs/quality-profiles/quality-profile-comparison.hbs
new file mode 100644
index 00000000000..e6eeeb28eb5
--- /dev/null
+++ b/server/sonar-web/src/main/hbs/quality-profiles/quality-profile-comparison.hbs
@@ -0,0 +1,85 @@
+<header class="page-header">
+ <div class="page-title">
+ <span class="h3">{{t 'compare'}}</span>
+ </div>
+</header>
+
+{{#notEmpty profiles}}
+ <form class="spacer-bottom" id="quality-profile-comparison-form">
+ <label class="text-middle" for="quality-profile-comparison-with-key">With</label>
+ <select id="quality-profile-comparison-with-key">
+ {{#each profiles}}
+ <option value="{{key}}" {{#eq key ../comparedWith}}selected{{/eq}}>{{name}}</option>
+ {{/each}}
+ </select>
+ <button class="text-middle" id="quality-profile-comparison-form-submit">{{t 'compare'}}</button>
+ </form>
+{{else}}
+ <div class="alert alert-info">There is no profiles to compare with.</div>
+{{/notEmpty}}
+
+{{#notNull comparison}}
+ <table class="width-100 data zebra">
+ {{#notEmpty comparison.inLeft}}
+ <tr>
+ <td class="width-50"><h6>{{tp 'quality_profiles.x_rules_only_in' comparison.inLeftSize}} {{comparison.left.name}}</h6></td>
+ <td class="width-50"></td>
+ </tr>
+ {{#each comparison.inLeft}}
+ <tr class="js-comparison-in-left">
+ <td class="width-50">{{severityIcon severity}}&nbsp;<a href="{{rulePermalink key}}">{{name}}</a></td>
+ <td class="width-50"></td>
+ </tr>
+ {{/each}}
+ {{/notEmpty}}
+
+ {{#notEmpty comparison.inRight}}
+ <tr>
+ <td class="width-50"></td>
+ <td class="width-50"><h6>{{tp 'quality_profiles.x_rules_only_in' comparison.inRightSize}} {{comparison.right.name}}</h6></td>
+ </tr>
+ {{#each comparison.inRight}}
+ <tr class="js-comparison-in-right">
+ <td class="width-50"></td>
+ <td class="width-50">{{severityIcon severity}}&nbsp;<a href="{{rulePermalink key}}">{{name}}</a></td>
+ </tr>
+ {{/each}}
+ {{/notEmpty}}
+
+ {{#notEmpty comparison.modified}}
+ <tr>
+ <td class="text-center width-50" colspan="2">
+ <h6>{{tp 'quality_profiles.x_rules_have_different_configuration' comparison.modifiedSize}}</h6>
+ </td>
+ </tr>
+ <tr>
+ <td class="width-50"><h6>{{comparison.left.name}}</h6></td>
+ <td class="width-50"><h6>{{comparison.right.name}}</h6></td>
+ </tr>
+ {{#each comparison.modified}}
+ <tr class="js-comparison-modified">
+ <td class="width-50">
+ <p>{{severityIcon left.severity}}&nbsp;<a href="{{rulePermalink key}}">{{name}}</a></p>
+ {{#notNull left.params}}
+ <ul>
+ {{#each left.params}}
+ <li class="spacer-top"><code>{{@key}}: {{this}}</code></li>
+ {{/each}}
+ </ul>
+ {{/notNull}}
+ </td>
+ <td class="width-50">
+ <p>{{severityIcon right.severity}}&nbsp;<a href="{{rulePermalink key}}">{{name}}</a></p>
+ {{#notNull right.params}}
+ <ul>
+ {{#each right.params}}
+ <li class="spacer-top"><code>{{@key}}: {{this}}</code></li>
+ {{/each}}
+ </ul>
+ {{/notNull}}
+ </td>
+ </tr>
+ {{/each}}
+ {{/notEmpty}}
+ </table>
+{{/notNull}}
diff --git a/server/sonar-web/src/main/hbs/quality-profiles/quality-profiles-profile-details.hbs b/server/sonar-web/src/main/hbs/quality-profiles/quality-profiles-profile-details.hbs
index c40678d064d..8d6d7c0d332 100644
--- a/server/sonar-web/src/main/hbs/quality-profiles/quality-profiles-profile-details.hbs
+++ b/server/sonar-web/src/main/hbs/quality-profiles/quality-profiles-profile-details.hbs
@@ -115,3 +115,5 @@
</div>
<div class="panel panel-vertical" id="quality-profile-changelog"></div>
+
+<div class="panel panel-vertical" id="quality-profile-comparison"></div>
diff --git a/server/sonar-web/src/main/js/quality-profiles/controller.js b/server/sonar-web/src/main/js/quality-profiles/controller.js
index 906503b17c0..c3e3f0c27cf 100644
--- a/server/sonar-web/src/main/js/quality-profiles/controller.js
+++ b/server/sonar-web/src/main/js/quality-profiles/controller.js
@@ -57,6 +57,17 @@ define([
});
},
+ compare: function (key, withKey) {
+ var that = this;
+ this.fetchProfiles().done(function () {
+ var profile = that.options.app.profiles.findWhere({ key: key });
+ if (profile != null) {
+ profile.trigger('select', profile, { trigger: false });
+ profile.compareWith(withKey);
+ }
+ });
+ },
+
onProfileSelect: function (profile, options) {
var that = this,
key = profile.get('key'),
diff --git a/server/sonar-web/src/main/js/quality-profiles/profile-comparison-view.js b/server/sonar-web/src/main/js/quality-profiles/profile-comparison-view.js
new file mode 100644
index 00000000000..862ede66deb
--- /dev/null
+++ b/server/sonar-web/src/main/js/quality-profiles/profile-comparison-view.js
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ */
+define([
+ 'templates/quality-profiles'
+], function () {
+
+ return Marionette.ItemView.extend({
+ template: Templates['quality-profile-comparison'],
+
+ events: {
+ 'submit #quality-profile-comparison-form': 'onFormSubmit'
+ },
+
+ onRender: function () {
+ this.$('select').select2({
+ width: '250px',
+ minimumResultsForSearch: 50
+ });
+ },
+
+ onFormSubmit: function (e) {
+ e.preventDefault();
+ var withKey = this.$('#quality-profile-comparison-with-key').val();
+ this.model.compareWith(withKey);
+ },
+
+ getProfilesForComparison: function () {
+ var profiles = this.model.collection.toJSON(),
+ key = this.model.id,
+ language = this.model.get('language');
+ return profiles.filter(function (profile) {
+ return profile.language === language && key !== profile.key;
+ });
+ },
+
+ serializeData: function () {
+ return _.extend(Marionette.ItemView.prototype.serializeData.apply(this, arguments), {
+ profiles: this.getProfilesForComparison()
+ });
+ }
+ });
+
+});
diff --git a/server/sonar-web/src/main/js/quality-profiles/profile-details-view.js b/server/sonar-web/src/main/js/quality-profiles/profile-details-view.js
index 87a13beb80e..5d5d0977d57 100644
--- a/server/sonar-web/src/main/js/quality-profiles/profile-details-view.js
+++ b/server/sonar-web/src/main/js/quality-profiles/profile-details-view.js
@@ -20,10 +20,11 @@
define([
'quality-profiles/change-profile-parent-view',
'quality-profiles/profile-changelog-view',
+ 'quality-profiles/profile-comparison-view',
'common/select-list',
'quality-profiles/helpers',
'templates/quality-profiles'
-], function (ChangeProfileParentView, ProfileChangelogView) {
+], function (ChangeProfileParentView, ProfileChangelogView, ProfileComparisonView) {
var $ = jQuery;
@@ -31,7 +32,8 @@ define([
template: Templates['quality-profiles-profile-details'],
regions: {
- changelogRegion: '#quality-profile-changelog'
+ changelogRegion: '#quality-profile-changelog',
+ comparisonRegion: '#quality-profile-comparison'
},
modelEvents: {
@@ -48,6 +50,7 @@ define([
this.initProjectsSelect();
}
this.changelogRegion.show(new ProfileChangelogView({ model: this.model }));
+ this.comparisonRegion.show(new ProfileComparisonView({ model: this.model }));
},
initProjectsSelect: function () {
diff --git a/server/sonar-web/src/main/js/quality-profiles/profile.js b/server/sonar-web/src/main/js/quality-profiles/profile.js
index d6b0450caf9..4bf6a980697 100644
--- a/server/sonar-web/src/main/js/quality-profiles/profile.js
+++ b/server/sonar-web/src/main/js/quality-profiles/profile.js
@@ -102,6 +102,23 @@ define(function () {
totalEvents: r.total
});
});
+ },
+
+ compareWith: function (withKey) {
+ var that = this,
+ url = baseUrl + '/api/qualityprofiles/compare',
+ options = { leftKey: this.id, rightKey: withKey };
+ return $.get(url, options).done(function (r) {
+ var comparison = _.extend(r, {
+ inLeftSize: _.size(r.inLeft),
+ inRightSize: _.size(r.inRight),
+ modifiedSize: _.size(r.modified)
+ });
+ that.set({
+ comparison: comparison,
+ comparedWith: withKey
+ });
+ });
}
});
diff --git a/server/sonar-web/src/main/js/quality-profiles/router.js b/server/sonar-web/src/main/js/quality-profiles/router.js
index 0076462a9f4..cd82dacfc92 100644
--- a/server/sonar-web/src/main/js/quality-profiles/router.js
+++ b/server/sonar-web/src/main/js/quality-profiles/router.js
@@ -24,7 +24,8 @@ define(function () {
'': 'index',
'index': 'index',
'show?key=:key': 'show',
- 'changelog*': 'changelog'
+ 'changelog*': 'changelog',
+ 'compare*': 'compare'
},
initialize: function (options) {
@@ -42,6 +43,11 @@ define(function () {
changelog: function () {
var params = window.getQueryParams();
this.app.controller.changelog(params.key, params.since, params.to);
+ },
+
+ compare: function () {
+ var params = window.getQueryParams();
+ this.app.controller.compare(params.key, params.withKey);
}
});
diff --git a/server/sonar-web/src/main/less/init/misc.less b/server/sonar-web/src/main/less/init/misc.less
index a52a57d6e5e..971ce0c2f2e 100644
--- a/server/sonar-web/src/main/less/init/misc.less
+++ b/server/sonar-web/src/main/less/init/misc.less
@@ -61,6 +61,7 @@ td.spacer-top { padding-top: 8px; }
.width-80 { width: 80%; }
.width-60 { width: 60%; }
.width-55 { width: 55%; }
+.width-50 { width: 50%; }
.width-40 { width: 40%; }
.width-20 { width: 20%; }
.width-15 { width: 15%; }
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/profiles_controller.rb b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/profiles_controller.rb
index 8ab7cdf9c28..aa8d7613397 100644
--- a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/profiles_controller.rb
+++ b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/profiles_controller.rb
@@ -32,6 +32,10 @@ class ProfilesController < ApplicationController
render :action => 'index'
end
+ def compare
+ render :action => 'index'
+ end
+
# GET /profiles/export?name=<profile name>&language=<language>&format=<exporter key>
def export
language = params[:language]
diff --git a/server/sonar-web/src/test/js/quality-profiles.js b/server/sonar-web/src/test/js/quality-profiles.js
index 60062524a22..062be0179d8 100644
--- a/server/sonar-web/src/test/js/quality-profiles.js
+++ b/server/sonar-web/src/test/js/quality-profiles.js
@@ -863,3 +863,101 @@ casper.test.begin(testName('Changelog Permalink'), 2, function (test) {
test.done();
});
});
+
+
+casper.test.begin(testName('Comparison'), 12, function (test) {
+ casper
+ .start(lib.buildUrl('profiles#show?key=java-sonar-way-67887'), function () {
+ lib.setDefaultViewport();
+
+ lib.mockRequestFromFile('/api/users/current', 'user.json');
+ lib.mockRequestFromFile('/api/qualityprofiles/search', 'search-with-copy.json');
+ lib.mockRequestFromFile('/api/rules/search', 'rules.json');
+ lib.mockRequestFromFile('/api/qualityprofiles/inheritance', 'inheritance.json');
+ lib.mockRequestFromFile('/api/qualityprofiles/compare', 'compare.json', {
+ data: { leftKey: 'java-sonar-way-67887', rightKey: 'java-copied-profile-11711' }
+ });
+ })
+
+ .then(function () {
+ casper.evaluate(function () {
+ require(['/js/quality-profiles/app.js']);
+ });
+ })
+
+ .then(function () {
+ casper.waitForSelector('#quality-profile-comparison-form-submit');
+ })
+
+ .then(function () {
+ test.assertElementCount('#quality-profile-comparison-with-key option', 1);
+ casper.click('#quality-profile-comparison-form-submit');
+ casper.waitForSelector('#quality-profile-comparison table');
+ })
+
+ .then(function () {
+ test.assertElementCount('.js-comparison-in-left', 2);
+ test.assertElementCount('.js-comparison-in-right', 2);
+ test.assertElementCount('.js-comparison-modified', 2);
+
+ test.assertSelectorContains('.js-comparison-in-left', '".equals()" should not be used to test');
+ test.assertSelectorContains('.js-comparison-in-left', '"@Override" annotation should be used on');
+
+ test.assertSelectorContains('.js-comparison-in-right', '"ConcurrentLinkedQueue.size()" should not be used');
+ test.assertSelectorContains('.js-comparison-in-right', '"compareTo" results should not be checked');
+
+ test.assertSelectorContains('.js-comparison-modified', 'Control flow statements');
+ test.assertSelectorContains('.js-comparison-modified', '"Cloneables" should implement "clone"');
+ test.assertSelectorContains('.js-comparison-modified', 'max: 5');
+ test.assertSelectorContains('.js-comparison-modified', 'max: 3');
+ })
+
+ .then(function () {
+ lib.sendCoverage();
+ })
+
+ .run(function () {
+ test.done();
+ });
+});
+
+
+casper.test.begin(testName('Comparison Permalink'), 4, function (test) {
+ casper
+ .start(lib.buildUrl('profiles#compare?key=java-sonar-way-67887&withKey=java-copied-profile-11711'), function () {
+ lib.setDefaultViewport();
+
+ lib.mockRequestFromFile('/api/users/current', 'user.json');
+ lib.mockRequestFromFile('/api/qualityprofiles/search', 'search-with-copy.json');
+ lib.mockRequestFromFile('/api/rules/search', 'rules.json');
+ lib.mockRequestFromFile('/api/qualityprofiles/inheritance', 'inheritance.json');
+ lib.mockRequestFromFile('/api/qualityprofiles/compare', 'compare.json', {
+ data: { leftKey: 'java-sonar-way-67887', rightKey: 'java-copied-profile-11711' }
+ });
+ })
+
+ .then(function () {
+ casper.evaluate(function () {
+ require(['/js/quality-profiles/app.js']);
+ });
+ })
+
+ .then(function () {
+ casper.waitForSelector('#quality-profile-comparison table');
+ })
+
+ .then(function () {
+ test.assertElementCount('#quality-profile-comparison-with-key option', 1);
+ test.assertElementCount('.js-comparison-in-left', 2);
+ test.assertElementCount('.js-comparison-in-right', 2);
+ test.assertElementCount('.js-comparison-modified', 2);
+ })
+
+ .then(function () {
+ lib.sendCoverage();
+ })
+
+ .run(function () {
+ test.done();
+ });
+});
diff --git a/server/sonar-web/src/test/json/quality-profiles/compare.json b/server/sonar-web/src/test/json/quality-profiles/compare.json
new file mode 100644
index 00000000000..8d93357f0bd
--- /dev/null
+++ b/server/sonar-web/src/test/json/quality-profiles/compare.json
@@ -0,0 +1,99 @@
+{
+ "left": {
+ "key": "java-sonar-way-67887",
+ "name": "Sonar way"
+ },
+ "right": {
+ "key": "java-copied-profile-11711",
+ "name": "Copied Profile"
+ },
+ "inLeft": [
+ {
+ "key": "squid:S2204",
+ "name": "\".equals()\" should not be used to test the values of \"Atomic\" classes",
+ "pluginKey": "squid",
+ "pluginName": "SonarQube",
+ "languageKey": "java",
+ "languageName": "Java",
+ "severity": "BLOCKER"
+ },
+ {
+ "key": "squid:S1161",
+ "name": "\"@Override\" annotation should be used on any method overriding (since Java 5) or implementing (since Java 6) another one",
+ "pluginKey": "squid",
+ "pluginName": "SonarQube",
+ "languageKey": "java",
+ "languageName": "Java",
+ "severity": "MAJOR"
+ }
+ ],
+ "inRight": [
+ {
+ "key": "squid:S2250",
+ "name": "\"ConcurrentLinkedQueue.size()\" should not be used",
+ "pluginKey": "squid",
+ "pluginName": "SonarQube",
+ "languageKey": "java",
+ "languageName": "Java",
+ "severity": "CRITICAL"
+ },
+ {
+ "key": "squid:S2200",
+ "name": "\"compareTo\" results should not be checked for specific values",
+ "pluginKey": "squid",
+ "pluginName": "SonarQube",
+ "languageKey": "java",
+ "languageName": "Java",
+ "severity": "MAJOR"
+ }
+ ],
+ "modified": [
+ {
+ "key": "squid:S134",
+ "name": "Control flow statements \"if\", \"for\", \"while\", \"switch\" and \"try\" should not be nested too deeply",
+ "pluginKey": "squid",
+ "pluginName": "SonarQube",
+ "languageKey": "java",
+ "languageName": "Java",
+ "left": {
+ "severity": "MINOR",
+ "params": {
+ "max": "5"
+ }
+ },
+ "right": {
+ "severity": "MINOR",
+ "params": {
+ "max": "3"
+ }
+ }
+ },
+ {
+ "key": "squid:S2157",
+ "name": "\"Cloneables\" should implement \"clone\"",
+ "pluginKey": "squid",
+ "pluginName": "SonarQube",
+ "languageKey": "java",
+ "languageName": "Java",
+ "left": {
+ "severity": "CRITICAL",
+ "params": {}
+ },
+ "right": {
+ "severity": "INFO",
+ "params": {}
+ }
+ }
+ ],
+ "same": [
+ {
+ "key": "squid:S2111",
+ "name": "\"BigDecimal(double)\" should not be used",
+ "pluginKey": "squid",
+ "pluginName": "SonarQube",
+ "languageKey": "java",
+ "languageName": "Java",
+ "severity": "CRITICAL"
+ }
+ ]
+}