From c158c1b03e2fbc205a310e2fc8b20c35ec4ccbad Mon Sep 17 00:00:00 2001 From: Stas Vilchik Date: Wed, 24 Dec 2014 17:18:45 +0100 Subject: [PATCH] SONAR-5820 Provide a permalink on each rule description --- server/sonar-web/Gruntfile.coffee | 4 + .../coffee/issue/views/rule-overlay.coffee | 2 +- .../rule/coding-rules-rule-meta.hbs | 2 +- .../main/js/coding-rules/rule-details-view.js | 1 + .../js/coding-rules/rule/rule-meta-view.js | 3 +- .../src/main/js/coding-rules/show-app.js | 94 ++++++++++ .../coding-rules-page-rule-permalink/app.json | 168 +++++++++++++++++ .../search.json | 171 ++++++++++++++++++ .../show.json | 76 ++++++++ ...g-rules-page-rule-should-have-permalink.js | 36 ++++ .../src/main/less/components/rules.less | 4 + .../controllers/coding_rules_controller.rb | 5 + .../app/views/coding_rules/show.html.erb | 6 + 13 files changed, 569 insertions(+), 3 deletions(-) create mode 100644 server/sonar-web/src/main/js/coding-rules/show-app.js create mode 100644 server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-page-rule-permalink/app.json create mode 100644 server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-page-rule-permalink/search.json create mode 100644 server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-page-rule-permalink/show.json create mode 100644 server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-page-rule-should-have-permalink.js create mode 100644 server/sonar-web/src/main/webapp/WEB-INF/app/views/coding_rules/show.html.erb diff --git a/server/sonar-web/Gruntfile.coffee b/server/sonar-web/Gruntfile.coffee index 736f83885b8..4e00555ddfa 100644 --- a/server/sonar-web/Gruntfile.coffee +++ b/server/sonar-web/Gruntfile.coffee @@ -174,6 +174,10 @@ module.exports = (grunt) -> name: 'coding-rules/app' out: '<%= pkg.assets %>build/js/coding-rules/app.js' + codingRulesShow: options: + name: 'coding-rules/app' + out: '<%= pkg.assets %>build/js/coding-rules/show-app.js' + codingRulesOld: options: name: 'coding-rules-old/app' out: '<%= pkg.assets %>build/js/coding-rules-old/app.js' diff --git a/server/sonar-web/src/main/coffee/issue/views/rule-overlay.coffee b/server/sonar-web/src/main/coffee/issue/views/rule-overlay.coffee index 78415e338e0..54d13bd8f0a 100644 --- a/server/sonar-web/src/main/coffee/issue/views/rule-overlay.coffee +++ b/server/sonar-web/src/main/coffee/issue/views/rule-overlay.coffee @@ -12,5 +12,5 @@ define [ serializeData: -> _.extend super, - permalink: "#{baseUrl}/coding_rules#rule_key=#{@model.get('key')}" + permalink: "#{baseUrl}/coding_rules/show?key=#{encodeURIComponent @model.get('key')}" allTags: _.union @model.get('sysTags'), @model.get('tags') diff --git a/server/sonar-web/src/main/hbs/coding-rules/rule/coding-rules-rule-meta.hbs b/server/sonar-web/src/main/hbs/coding-rules/rule/coding-rules-rule-meta.hbs index 44cd94a7adf..383258b9bbb 100644 --- a/server/sonar-web/src/main/hbs/coding-rules/rule/coding-rules-rule-meta.hbs +++ b/server/sonar-web/src/main/hbs/coding-rules/rule/coding-rules-rule-meta.hbs @@ -1,6 +1,6 @@

{{name}} - +

{{key}} diff --git a/server/sonar-web/src/main/js/coding-rules/rule-details-view.js b/server/sonar-web/src/main/js/coding-rules/rule-details-view.js index 2e41d7663fa..051413e679f 100644 --- a/server/sonar-web/src/main/js/coding-rules/rule-details-view.js +++ b/server/sonar-web/src/main/js/coding-rules/rule-details-view.js @@ -9,6 +9,7 @@ define([ ], function (Backbone, Marionette, Templates, MetaView, DescView, ParamView, ProfilesView) { return Marionette.Layout.extend({ + className: 'coding-rule-details', template: Templates['coding-rules-rule-details'], regions: { diff --git a/server/sonar-web/src/main/js/coding-rules/rule/rule-meta-view.js b/server/sonar-web/src/main/js/coding-rules/rule/rule-meta-view.js index 6f3c3c36700..e01fa1d2942 100644 --- a/server/sonar-web/src/main/js/coding-rules/rule/rule-meta-view.js +++ b/server/sonar-web/src/main/js/coding-rules/rule/rule-meta-view.js @@ -78,7 +78,8 @@ define([ return _.extend(Marionette.ItemView.prototype.serializeData.apply(this, arguments), { canWrite: this.options.app.canWrite, subCharacteristic: this.options.app.getSubCharacteristicName(this.model.get('debtSubChar')), - allTags: _.union(this.model.get('sysTags'), this.model.get('tags')) + allTags: _.union(this.model.get('sysTags'), this.model.get('tags')), + permalink: baseUrl + '/coding_rules/show?key=' + encodeURIComponent(this.model.id) }); } }); diff --git a/server/sonar-web/src/main/js/coding-rules/show-app.js b/server/sonar-web/src/main/js/coding-rules/show-app.js new file mode 100644 index 00000000000..ee68beb0df4 --- /dev/null +++ b/server/sonar-web/src/main/js/coding-rules/show-app.js @@ -0,0 +1,94 @@ +requirejs.config({ + baseUrl: baseUrl + '/js', + + paths: { + 'backbone': 'third-party/backbone', + 'backbone.marionette': 'third-party/backbone.marionette', + 'handlebars': 'third-party/handlebars' + }, + + shim: { + 'backbone.marionette': { + deps: ['backbone'], + exports: 'Marionette' + }, + 'backbone': { + exports: 'Backbone' + }, + 'handlebars': { + exports: 'Handlebars' + } + } +}); + + +requirejs([ + 'backbone', + 'backbone.marionette', + + 'coding-rules/models/rule', + 'coding-rules/rule-details-view', + + 'common/handlebars-extensions' +], + function (Backbone, + Marionette, + Rule, + RuleDetailsView) { + + var $ = jQuery, + App = new Marionette.Application(), + p = window.process.addBackgroundProcess(); + + App.addInitializer(function () { + var url = baseUrl + '/api/rules/show', + key = decodeURIComponent(window.location.search.substr(5)), + options = { + key: key, + actives: true + }; + $.get(url, options).done(function (data) { + this.ruleDetailsView = new RuleDetailsView({ + app: App, + model: new Rule(data.rule), + actives: data.actives + }); + this.ruleDetailsView.render().$el.appendTo($('.page')); + window.process.finishBackgroundProcess(p); + }).fail(function () { + window.process.failBackgroundProcess(p); + }); + }); + + App.manualRepository = function () { + return { + key: 'manual', + name: t('coding_rules.manual_rules'), + language: 'none' + }; + }; + + App.getSubCharacteristicName = function (name) { + return (App.characteristics[name] || '').replace(': ', ' > '); + }; + + var appXHR = $.get(baseUrl + '/api/rules/app').done(function(r) { + App.canWrite = r.canWrite; + App.qualityProfiles = _.sortBy(r.qualityprofiles, ['name', 'lang']); + App.languages = _.extend(r.languages, { + none: 'None' + }); + _.map(App.qualityProfiles, function(profile) { + profile.language = App.languages[profile.lang]; + }); + App.repositories = r.repositories; + App.repositories.push(App.manualRepository()); + App.statuses = r.statuses; + App.characteristics = r.characteristics; + }); + + $.when(window.requestMessages(), appXHR).done(function () { + App.start(); + }); + + }); diff --git a/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-page-rule-permalink/app.json b/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-page-rule-permalink/app.json new file mode 100644 index 00000000000..4f3319c8707 --- /dev/null +++ b/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-page-rule-permalink/app.json @@ -0,0 +1,168 @@ +{ + "canWrite": false, + "qualityprofiles": [ + { + "key": "java-default-with-mojo-conventions-49307", + "name": "Default - Maven Conventions", + "lang": "java", + "parentKey": "java-top-profile-without-formatting-conventions-50037" + }, + { + "key": "java-default-with-sonarsource-conventions-27339", + "name": "Default - SonarSource conventions", + "lang": "java", + "parentKey": "java-top-profile-without-formatting-conventions-50037" + }, + { + "key": "java-top-profile-without-formatting-conventions-50037", + "name": "Default - Top", + "lang": "java" + }, + { + "key": "java-findbugs-14954", + "name": "FindBugs", + "lang": "java" + }, + { + "key": "java-for-sq-java-plugin-only-92289", + "name": "For SQ Java Plugin Only", + "lang": "java", + "parentKey": "java-default-with-sonarsource-conventions-27339" + }, + { + "key": "java-for-sq-only-95381", + "name": "For SQ Only", + "lang": "java", + "parentKey": "java-default-with-sonarsource-conventions-27339" + }, + { + "key": "php-psr-2-06315", + "name": "PSR-2", + "lang": "php" + }, + { + "key": "java-sonar-way-80423", + "name": "Sonar way", + "lang": "java" + }, + { + "key": "js-sonar-way", + "name": "Sonar way", + "lang": "js" + }, + { + "key": "php-sonar-way-05548", + "name": "Sonar way", + "lang": "php" + }, + { + "key": "py-sonar-way-80265", + "name": "Sonar way", + "lang": "py" + }, + { + "key": "java-without-findbugs", + "name": "Without Findbugs", + "lang": "java" + } + ], + "languages": { + "py": "Python", + "js": "JavaScript", + "php": "PHP", + "java": "Java" + }, + "repositories": [ + { + "key": "common-java", + "name": "Common SonarQube", + "language": "java" + }, + { + "key": "common-js", + "name": "Common SonarQube", + "language": "js" + }, + { + "key": "common-php", + "name": "Common SonarQube", + "language": "php" + }, + { + "key": "common-py", + "name": "Common SonarQube", + "language": "py" + }, + { + "key": "Pylint", + "name": "Pylint", + "language": "py" + }, + { + "key": "javascript", + "name": "SonarQube", + "language": "js" + }, + { + "key": "php", + "name": "SonarQube", + "language": "php" + }, + { + "key": "python", + "name": "SonarQube", + "language": "py" + }, + { + "key": "squid", + "name": "SonarQube", + "language": "java" + } + ], + "statuses": { + "BETA": "Beta", + "DEPRECATED": "Deprecated", + "READY": "Ready" + }, + "characteristics": { + "UNDERSTANDABILITY": "Maintainability: Understandability", + "MAINTAINABILITY": "Maintainability", + "TIME_ZONE_RELATED_PORTABILITY": "Portability: Time zone related portability", + "READABILITY": "Maintainability: Readability", + "SECURITY_FEATURES": "Security: Security features", + "ARCHITECTURE_RELIABILITY": "Reliability: Architecture related reliability", + "OS_RELATED_PORTABILITY": "Portability: OS related portability", + "EXCEPTION_HANDLING": "Reliability: Exception handling", + "LOGIC_CHANGEABILITY": "Changeability: Logic related changeability", + "SOFTWARE_RELATED_PORTABILITY": "Portability: Software related portability", + "INPUT_VALIDATION_AND_REPRESENTATION": "Security: Input validation and representation", + "LANGUAGE_RELATED_PORTABILITY": "Portability: Language related portability", + "ERRORS": "Security: Errors", + "SECURITY": "Security", + "RELIABILITY": "Reliability", + "PORTABILITY": "Portability", + "HARDWARE_RELATED_PORTABILITY": "Portability: Hardware related portability", + "SYNCHRONIZATION_RELIABILITY": "Reliability: Synchronization related reliability", + "TRANSPORTABILITY": "Reusability: Transportability", + "COMPILER_RELATED_PORTABILITY": "Portability: Compiler related portability", + "RESOURCE_RELIABILITY": "Reliability: Resource", + "CPU_EFFICIENCY": "Efficiency: Processor use", + "EFFICIENCY": "Efficiency", + "CHANGEABILITY": "Changeability", + "DATA_CHANGEABILITY": "Changeability: Data related changeability", + "API_ABUSE": "Security: API abuse", + "ARCHITECTURE_CHANGEABILITY": "Changeability: Architecture related changeability", + "UNIT_TESTS": "Reliability: Unit tests", + "INSTRUCTION_RELIABILITY": "Reliability: Instruction related reliability", + "REUSABILITY": "Reusability", + "MODULARITY": "Reusability: Modularity", + "UNIT_TESTABILITY": "Testability: Unit level testability", + "TESTABILITY": "Testability", + "INTEGRATION_TESTABILITY": "Testability: Integration level testability", + "NETWORK_USE": "Efficiency: Network use", + "MEMORY_EFFICIENCY": "Efficiency: Memory use", + "DATA_RELIABILITY": "Reliability: Data related reliability", + "FAULT_TOLERANCE": "Reliability: Fault tolerance", + "LOGIC_RELIABILITY": "Reliability: Logic related reliability" + } +} diff --git a/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-page-rule-permalink/search.json b/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-page-rule-permalink/search.json new file mode 100644 index 00000000000..b5a6bc269b9 --- /dev/null +++ b/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-page-rule-permalink/search.json @@ -0,0 +1,171 @@ +{ + "total": 10, + "p": 1, + "ps": 200, + "rules": [ + { + "key": "squid:S1181", + "name": "Throwable and Error classes should not be caught", + "lang": "java", + "langName": "Java", + "sysTags": [ + "error-handling" + ], + "tags": [] + }, + { + "key": "squid:S1849", + "name": "\"Iterator.hasNext()\" should not call \"Iterator.next()\"", + "lang": "java", + "langName": "Java", + "sysTags": [ + "bug" + ], + "tags": [] + }, + { + "key": "squid:S1844", + "name": "\"Object.wait(...)\" should never be called on objects that implement \"java.util.concurrent.locks.Condition\"", + "lang": "java", + "langName": "Java", + "sysTags": [ + "bug", + "pitfall" + ], + "tags": [] + }, + { + "key": "squid:S2258", + "name": "\"javax.crypto.NullCipher\" should not be used for anything other than testing", + "lang": "java", + "langName": "Java", + "sysTags": [ + "cwe", + "owasp-top10", + "security" + ], + "tags": [] + }, + { + "key": "squid:S2251", + "name": "A \"for\" loop update clause should move the counter in the right direction", + "lang": "java", + "langName": "Java", + "sysTags": [ + "bug" + ], + "tags": [] + }, + { + "key": "squid:ObjectFinalizeOverridenCallsSuperFinalizeCheck", + "name": "super.finalize() should be called at the end of Object.finalize() implementations", + "lang": "java", + "langName": "Java", + "sysTags": [ + "bug" + ], + "tags": [] + }, + { + "key": "squid:S1143", + "name": "Return statements should not occur in finally blocks", + "lang": "java", + "langName": "Java", + "sysTags": [ + "bug" + ], + "tags": [] + }, + { + "key": "squid:S1206", + "name": "\"equals(Object obj)\" and \"hashCode()\" should be overridden in pairs", + "lang": "java", + "langName": "Java", + "sysTags": [ + "bug" + ], + "tags": [] + }, + { + "key": "squid:S1451", + "name": "Copyright and license headers should be defined in all source files", + "lang": "java", + "langName": "Java", + "sysTags": [ + "convention" + ], + "tags": [] + }, + { + "key": "squid:S1697", + "name": "Short-circuit logic should be used to prevent null pointer dereferences in conditionals", + "lang": "java", + "langName": "Java", + "sysTags": [ + "bug" + ], + "tags": [] + } + ], + "facets": [ + { + "property": "tags", + "values": [ + { + "val": "bug", + "count": 7 + }, + { + "val": "convention", + "count": 1 + }, + { + "val": "cwe", + "count": 1 + }, + { + "val": "error-handling", + "count": 1 + }, + { + "val": "owasp-top10", + "count": 1 + }, + { + "val": "pitfall", + "count": 1 + }, + { + "val": "security", + "count": 1 + } + ] + }, + { + "property": "languages", + "values": [ + { + "val": "java", + "count": 10 + }, + { + "val": "js", + "count": 6 + }, + { + "val": "php", + "count": 2 + } + ] + }, + { + "property": "repositories", + "values": [ + { + "val": "squid", + "count": 10 + } + ] + } + ] +} diff --git a/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-page-rule-permalink/show.json b/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-page-rule-permalink/show.json new file mode 100644 index 00000000000..274b587bfb2 --- /dev/null +++ b/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-page-rule-permalink/show.json @@ -0,0 +1,76 @@ +{ + "rule": { + "key": "squid:S1181", + "repo": "squid", + "name": "Throwable and Error classes should not be caught", + "createdAt": "2013-08-09T14:40:54+0200", + "severity": "BLOCKER", + "status": "READY", + "internalKey": "S1181", + "isTemplate": false, + "tags": [], + "sysTags": [ + "error-handling" + ], + "lang": "java", + "langName": "Java", + "htmlDesc": "

\nThrowable is the superclass of all errors and exceptions in Java.\nError is the superclass of all errors which are not meant to be caught by applications.\n

\n\n

\nCatching either Throwable or Error will also catch OutOfMemoryError or InternalError from which an application should not attempt to recover.\n

\n\n

Only Exception and its subclasses should be caught.

\n\n

Noncompliant Code Example

\n\n
\ntry { /* ... */ } catch (Throwable t) { /* ... */ }\ntry { /* ... */ } catch (Error e) { /* ... */ } \n
\n\n

Compliant Solution

\n\n
\ntry { /* ... */ } catch (Exception e) { /* ... */ }  \ntry { /* ... */ } catch (RuntimeException e) { /* ... */ }  \ntry { /* ... */ } catch (MyException e) { /* ... */ }  \n
", + "defaultDebtChar": "RELIABILITY", + "defaultDebtSubChar": "EXCEPTION_HANDLING", + "debtChar": "RELIABILITY", + "debtSubChar": "EXCEPTION_HANDLING", + "debtCharName": "Reliability", + "debtSubCharName": "Exception handling", + "defaultDebtRemFnType": "CONSTANT_ISSUE", + "defaultDebtRemFnOffset": "20min", + "debtOverloaded": true, + "debtRemFnType": "LINEAR", + "debtRemFnCoeff": "20min", + "params": [ + { + "key": "max", + "htmlDesc": "Maximum authorized number of parameters", + "type": "INTEGER", + "defaultValue": "7" + } + ] + }, + "actives": [ + { + "qProfile": "java-top-profile-without-formatting-conventions-50037", + "inherit": "NONE", + "severity": "BLOCKER", + "params": [] + }, + { + "qProfile": "java-default-with-sonarsource-conventions-27339", + "inherit": "INHERITED", + "severity": "BLOCKER", + "params": [] + }, + { + "qProfile": "java-for-sq-java-plugin-only-92289", + "inherit": "INHERITED", + "severity": "BLOCKER", + "params": [] + }, + { + "qProfile": "java-for-sq-only-95381", + "inherit": "INHERITED", + "severity": "BLOCKER", + "params": [] + }, + { + "qProfile": "java-default-with-mojo-conventions-49307", + "inherit": "INHERITED", + "severity": "BLOCKER", + "params": [] + }, + { + "qProfile": "java-sonar-way-80423", + "inherit": "NONE", + "severity": "BLOCKER", + "params": [] + } + ] +} diff --git a/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-page-rule-should-have-permalink.js b/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-page-rule-should-have-permalink.js new file mode 100644 index 00000000000..75898fd15fe --- /dev/null +++ b/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-page-rule-should-have-permalink.js @@ -0,0 +1,36 @@ +/* global casper:false */ + +var lib = require('../lib'); + +lib.initMessages(); +lib.changeWorkingDirectory('coding-rules-page-rule-permalink'); + + +casper.test.begin('coding-rules-page-rule-permalink', 1, function (test) { + casper + .start(lib.buildUrl('coding-rules'), function () { + lib.setDefaultViewport(); + + lib.mockRequest('/api/l10n/index', '{}'); + lib.mockRequestFromFile('/api/rules/app', 'app.json'); + lib.mockRequestFromFile('/api/rules/search', 'search.json'); + lib.mockRequestFromFile('/api/rules/show', 'show.json'); + }) + + .then(function () { + casper.waitForSelector('.coding-rule.selected'); + }) + + .then(function () { + casper.click('.coding-rule.selected .js-rule'); + casper.waitForSelector('.coding-rules-detail-header'); + }) + + .then(function () { + test.assertExists('a[href="/coding_rules/show?key=squid%3AS1181"]'); + }) + + .run(function () { + test.done(); + }); +}); diff --git a/server/sonar-web/src/main/less/components/rules.less b/server/sonar-web/src/main/less/components/rules.less index affe5c40766..41793326f44 100644 --- a/server/sonar-web/src/main/less/components/rules.less +++ b/server/sonar-web/src/main/less/components/rules.less @@ -57,3 +57,7 @@ content: ""; } } + +.coding-rule-details { + max-width: 1020px; +} diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/coding_rules_controller.rb b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/coding_rules_controller.rb index cc4298b9383..04900e82a39 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/coding_rules_controller.rb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/coding_rules_controller.rb @@ -27,4 +27,9 @@ class CodingRulesController < ApplicationController end + # GET /coding_rules/show + def show + + end + end diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/coding_rules/show.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/coding_rules/show.html.erb new file mode 100644 index 00000000000..629a4a120d0 --- /dev/null +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/coding_rules/show.html.erb @@ -0,0 +1,6 @@ +<% content_for :script do %> + +<% end %> + + +
-- 2.39.5