From cd5957698e0110754c73fd8118de3909d3e96c00 Mon Sep 17 00:00:00 2001 From: Stas Vilchik Date: Wed, 15 Apr 2015 14:33:55 +0200 Subject: [PATCH] SONAR-5851 add comparison --- .../quality-profile-comparison.hbs | 85 ++++++++++++++++ .../quality-profiles-profile-details.hbs | 2 + .../main/js/quality-profiles/controller.js | 11 +++ .../profile-comparison-view.js | 60 +++++++++++ .../quality-profiles/profile-details-view.js | 7 +- .../src/main/js/quality-profiles/profile.js | 17 ++++ .../src/main/js/quality-profiles/router.js | 8 +- server/sonar-web/src/main/less/init/misc.less | 1 + .../app/controllers/profiles_controller.rb | 4 + .../sonar-web/src/test/js/quality-profiles.js | 98 ++++++++++++++++++ .../test/json/quality-profiles/compare.json | 99 +++++++++++++++++++ 11 files changed, 389 insertions(+), 3 deletions(-) create mode 100644 server/sonar-web/src/main/hbs/quality-profiles/quality-profile-comparison.hbs create mode 100644 server/sonar-web/src/main/js/quality-profiles/profile-comparison-view.js create mode 100644 server/sonar-web/src/test/json/quality-profiles/compare.json 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 @@ + + +{{#notEmpty profiles}} +
+ + + +
+{{else}} +
There is no profiles to compare with.
+{{/notEmpty}} + +{{#notNull comparison}} + + {{#notEmpty comparison.inLeft}} + + + + + {{#each comparison.inLeft}} + + + + + {{/each}} + {{/notEmpty}} + + {{#notEmpty comparison.inRight}} + + + + + {{#each comparison.inRight}} + + + + + {{/each}} + {{/notEmpty}} + + {{#notEmpty comparison.modified}} + + + + + + + + {{#each comparison.modified}} + + + + + {{/each}} + {{/notEmpty}} +
{{tp 'quality_profiles.x_rules_only_in' comparison.inLeftSize}} {{comparison.left.name}}
{{severityIcon severity}} {{name}}
{{tp 'quality_profiles.x_rules_only_in' comparison.inRightSize}} {{comparison.right.name}}
{{severityIcon severity}} {{name}}
+
{{tp 'quality_profiles.x_rules_have_different_configuration' comparison.modifiedSize}}
+
{{comparison.left.name}}
{{comparison.right.name}}
+

{{severityIcon left.severity}} {{name}}

+ {{#notNull left.params}} +
    + {{#each left.params}} +
  • {{@key}}: {{this}}
  • + {{/each}} +
+ {{/notNull}} +
+

{{severityIcon right.severity}} {{name}}

+ {{#notNull right.params}} +
    + {{#each right.params}} +
  • {{@key}}: {{this}}
  • + {{/each}} +
+ {{/notNull}} +
+{{/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 @@
+ +
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=&language=&format= 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" + } + ] +} -- 2.39.5