From: Stas Vilchik Date: Fri, 26 Dec 2014 13:43:40 +0000 (+0100) Subject: SONAR-5820 On "template" rules, make it possible to create "custom" rules X-Git-Tag: latest-silver-master-#65~407 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=388c4ada79aab682618844c293e939af11e9723d;p=sonarqube.git SONAR-5820 On "template" rules, make it possible to create "custom" rules --- diff --git a/server/sonar-web/src/main/hbs/coding-rules/rule/coding-rules-custom-rule-creation.hbs b/server/sonar-web/src/main/hbs/coding-rules/rule/coding-rules-custom-rule-creation.hbs new file mode 100644 index 00000000000..24638406c30 --- /dev/null +++ b/server/sonar-web/src/main/hbs/coding-rules/rule/coding-rules-custom-rule-creation.hbs @@ -0,0 +1,86 @@ +
+ + + + + +
diff --git a/server/sonar-web/src/main/hbs/coding-rules/rule/coding-rules-custom-rule.hbs b/server/sonar-web/src/main/hbs/coding-rules/rule/coding-rules-custom-rule.hbs index 4d8f07efd37..94f72b03f35 100644 --- a/server/sonar-web/src/main/hbs/coding-rules/rule/coding-rules-custom-rule.hbs +++ b/server/sonar-web/src/main/hbs/coding-rules/rule/coding-rules-custom-rule.hbs @@ -7,10 +7,12 @@ - {{#each parameters}} -
- {{key}}{{value}} -
+ {{#each params}} + {{#if defaultValue}} +
+ {{key}}{{defaultValue}} +
+ {{/if}} {{/each}}   diff --git a/server/sonar-web/src/main/hbs/coding-rules/rule/coding-rules-custom-rules.hbs b/server/sonar-web/src/main/hbs/coding-rules/rule/coding-rules-custom-rules.hbs index f2a2cc9793e..4e03b21754d 100644 --- a/server/sonar-web/src/main/hbs/coding-rules/rule/coding-rules-custom-rules.hbs +++ b/server/sonar-web/src/main/hbs/coding-rules/rule/coding-rules-custom-rules.hbs @@ -3,7 +3,7 @@ {{#if canWrite}}
- +
{{/if}}
diff --git a/server/sonar-web/src/main/js/coding-rules/controller.js b/server/sonar-web/src/main/js/coding-rules/controller.js index c731ee16c43..b1c1c498649 100644 --- a/server/sonar-web/src/main/js/coding-rules/controller.js +++ b/server/sonar-web/src/main/js/coding-rules/controller.js @@ -1,7 +1,8 @@ define([ 'components/navigator/controller', + 'coding-rules/models/rule', 'coding-rules/rule-details-view' -], function (Controller, RuleDetailsView) { +], function (Controller, Rule, RuleDetailsView) { var $ = jQuery; @@ -92,7 +93,7 @@ define([ showDetails: function (rule) { var that = this, - ruleModel = typeof rule === 'string' ? new Backbone.Model({ key: rule }) : rule; + ruleModel = typeof rule === 'string' ? new Rule({ key: rule }) : rule; this.app.layout.workspaceDetailsRegion.reset(); this.getRuleDetails(ruleModel).done(function (data) { key.setScope('details'); diff --git a/server/sonar-web/src/main/js/coding-rules/rule/custom-rule-creation-view.js b/server/sonar-web/src/main/js/coding-rules/rule/custom-rule-creation-view.js new file mode 100644 index 00000000000..0e8f6a8529a --- /dev/null +++ b/server/sonar-web/src/main/js/coding-rules/rule/custom-rule-creation-view.js @@ -0,0 +1,183 @@ +define([ + 'common/modals', + 'templates/coding-rules' +], function (Modal, Templates) { + + var $ = jQuery; + + return Modal.extend({ + template: Templates['coding-rules-custom-rule-creation'], + + ui: { + customRuleCreationKey: '#coding-rules-custom-rule-creation-key', + customRuleCreationName: '#coding-rules-custom-rule-creation-name', + customRuleCreationHtmlDescription: '#coding-rules-custom-rule-creation-html-description', + customRuleCreationSeverity: '#coding-rules-custom-rule-creation-severity', + customRuleCreationStatus: '#coding-rules-custom-rule-creation-status', + customRuleCreationParameters: '[name]', + customRuleCreationCreate: '#coding-rules-custom-rule-creation-create', + customRuleCreationReactivate: '#coding-rules-custom-rule-creation-reactivate', + modalFoot: '.modal-foot' + }, + + events: function () { + return _.extend(Modal.prototype.events.apply(this, arguments), { + 'input @ui.customRuleCreationName': 'generateKey', + 'keydown @ui.customRuleCreationName': 'generateKey', + 'keyup @ui.customRuleCreationName': 'generateKey', + + 'input @ui.customRuleCreationKey': 'flagKey', + 'keydown @ui.customRuleCreationKey': 'flagKey', + 'keyup @ui.customRuleCreationKey': 'flagKey', + + 'click #coding-rules-custom-rule-creation-cancel': 'close', + 'click @ui.customRuleCreationCreate': 'create', + 'click @ui.customRuleCreationReactivate': 'reactivate' + }); + }, + + generateKey: function () { + if (!this.keyModifiedByUser) { + if (this.ui.customRuleCreationKey) { + var generatedKey = this.ui.customRuleCreationName.val().latinize().replace(/[^A-Za-z0-9]/g, '_'); + this.ui.customRuleCreationKey.val(generatedKey); + } + } + }, + + flagKey: function () { + this.keyModifiedByUser = true; + // Cannot use @ui.customRuleCreationReactivate.hide() directly since it was not there at initial render + jQuery(this.ui.customRuleCreationReactivate.selector).hide(); + }, + + onRender: function () { + Modal.prototype.onRender.apply(this, arguments); + + this.keyModifiedByUser = false; + + var format = function (state) { + if (!state.id) { + return state.text; + } else { + return ' ' + state.text; + } + }, + severity = (this.model && this.model.get('severity')) || this.options.templateRule.get('severity'), + status = (this.model && this.model.get('status')) || this.options.templateRule.get('status'); + + this.ui.customRuleCreationSeverity.val(severity); + this.ui.customRuleCreationSeverity.select2({ + width: '250px', + minimumResultsForSearch: 999, + formatResult: format, + formatSelection: format + }); + + this.ui.customRuleCreationStatus.val(status); + this.ui.customRuleCreationStatus.select2({ + width: '250px', + minimumResultsForSearch: 999 + }); + }, + + create: function (e) { + e.preventDefault(); + var action = (this.model && this.model.has('key')) ? 'update' : 'create', + options = { + name: this.ui.customRuleCreationName.val(), + markdown_description: this.ui.customRuleCreationHtmlDescription.val(), + severity: this.ui.customRuleCreationSeverity.val(), + status: this.ui.customRuleCreationStatus.val() + }; + if (this.model && this.model.has('key')) { + options.key = this.model.get('key'); + } else { + _.extend(options, { + template_key: this.options.templateRule.get('key'), + custom_key: this.ui.customRuleCreationKey.val(), + prevent_reactivation: true + }); + } + var params = this.ui.customRuleCreationParameters.map(function () { + var node = $(this), + value = node.val(); + if (!value && action === 'create') { + value = node.prop('placeholder') || ''; + } + return { + key: node.prop('name'), + value: value + }; + }).get(); + options.params = params.map(function (param) { + return param.key + '=' + window.csvEscape(param.value); + }).join(';'); + this.sendRequest(action, options); + }, + + reactivate: function () { + var options = { + name: this.existingRule.name, + markdown_description: this.existingRule.mdDesc, + severity: this.existingRule.severity, + status: this.existingRule.status, + template_key: this.existingRule.templateKey, + custom_key: this.ui.customRuleCreationKey.val(), + prevent_reactivation: false + }, + params = this.existingRule.params; + options.params = params.map(function (param) { + return param.key + '=' + param.defaultValue; + }).join(';'); + this.sendRequest('create', options); + }, + + sendRequest: function (action, options) { + this.$('.modal-error').hide(); + this.$('.modal-warning').hide(); + var that = this, + origFooter = this.ui.modalFoot.html(), + url = baseUrl + '/api/rules/' + action; + return $.post(url, options).done(function () { + that.options.app.controller.showDetails(that.options.templateRule); + that.close(); + }).fail(function (jqXHR, textStatus, errorThrown) { + if (jqXHR.status === 409) { + that.existingRule = jqXHR.responseJSON.rule; + that.$('.modal-warning').show(); + that.ui.modalFoot.html(Templates['coding-rules-custom-rule-reactivation']()); + } else { + jQuery.ajaxSettings.error(jqXHR, textStatus, errorThrown); + that.ui.modalFoot.html(origFooter); + } + }); + }, + + serializeData: function () { + var params = {}; + if (this.options.templateRule) { + params = this.options.templateRule.get('params'); + } else if (this.model && this.model.has('params')) { + params = this.model.get('params').map(function (p) { + _.extend(p, { value: p.defaultValue }); + }); + } + + var statuses = ['READY', 'BETA', 'DEPRECATED'].map(function (status) { + return { + id: status, + text: t('rules.status', status.toLowerCase()) + }; + }); + + return _.extend(Modal.prototype.serializeData.apply(this, arguments), { + change: this.model && this.model.has('key'), + params: params, + severities: ['BLOCKER', 'CRITICAL', 'MAJOR', 'MINOR', 'INFO'], + statuses: statuses + }); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/coding-rules/rule/custom-rules-view.js b/server/sonar-web/src/main/js/coding-rules/rule/custom-rules-view.js index a6a25c7982d..ea519ab4b51 100644 --- a/server/sonar-web/src/main/js/coding-rules/rule/custom-rules-view.js +++ b/server/sonar-web/src/main/js/coding-rules/rule/custom-rules-view.js @@ -1,8 +1,9 @@ define([ 'backbone.marionette', 'templates/coding-rules', - 'coding-rules/rule/custom-rule-view' -], function (Marionette, Templates, CustomRuleView) { + 'coding-rules/rule/custom-rule-view', + 'coding-rules/rule/custom-rule-creation-view' +], function (Marionette, Templates, CustomRuleView, CustomRuleCreationView) { return Marionette.CompositeView.extend({ template: Templates['coding-rules-custom-rules'], @@ -20,10 +21,21 @@ define([ 'change': 'render' }, + events: { + 'click .js-create-custom-rule': 'createCustomRule' + }, + onRender: function () { this.$el.toggleClass('hidden', !this.model.get('isTemplate')); }, + createCustomRule: function () { + new CustomRuleCreationView({ + app: this.options.app, + templateRule: this.model + }).render(); + }, + serializeData: function () { return _.extend(Marionette.ItemView.prototype.serializeData.apply(this, arguments), { canWrite: this.options.app.canWrite diff --git a/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-page-should-create-custom-rules.js b/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-page-should-create-custom-rules.js new file mode 100644 index 00000000000..761a0d1819c --- /dev/null +++ b/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-page-should-create-custom-rules.js @@ -0,0 +1,55 @@ +/* global casper:false */ + +var lib = require('../lib'); + +lib.initMessages(); +lib.changeWorkingDirectory('coding-rules-page-should-create-custom-rules'); + + +casper.test.begin('coding-rules-page-should-delete-create-rules', 2, function (test) { + casper + .start(lib.buildUrl('coding-rules'), function () { + lib.setDefaultViewport(); + + lib.mockRequest('/api/l10n/index', '{}'); + lib.mockRequestFromFile('/api/rules/app', 'app.json'); + this.customRulesSearchMock = lib.mockRequestFromFile('/api/rules/search', 'search-custom-rules.json', + { data: { template_key: 'squid:ArchitecturalConstraint' } }); + this.searchMock = lib.mockRequestFromFile('/api/rules/search', 'search.json'); + lib.mockRequestFromFile('/api/rules/show', 'show.json'); + lib.mockRequest('/api/rules/create', '{}'); + }) + + .then(function () { + casper.waitForSelector('.coding-rule.selected', function () { + casper.click('.coding-rule.selected .js-rule'); + }); + }) + + .then(function () { + casper.waitForSelector('#coding-rules-detail-custom-rules .coding-rules-detail-list-name'); + }) + + .then(function () { + lib.clearRequestMock(this.customRulesSearchMock); + lib.clearRequestMock(this.searchMock); + lib.mockRequestFromFile('/api/rules/search', 'search-custom-rules2.json'); + }) + + .then(function () { + test.assertElementCount('#coding-rules-detail-custom-rules .coding-rules-detail-list-name', 1); + casper.click('.js-create-custom-rule'); + casper.fillForm('.modal form', { + name: 'test', + markdown_description: 'test' + }); + casper.click('#coding-rules-custom-rule-creation-create'); + lib.waitForElementCount('#coding-rules-detail-custom-rules .coding-rules-detail-list-name', 2, function () { + test.assert(true); // put dummy assert into wait statement + }); + }) + + .run(function () { + test.done(); + }); +}); diff --git a/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-page-should-create-custom-rules/app.json b/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-page-should-create-custom-rules/app.json new file mode 100644 index 00000000000..f66b07ba5a1 --- /dev/null +++ b/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-page-should-create-custom-rules/app.json @@ -0,0 +1,168 @@ +{ + "canWrite": true, + "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-should-create-custom-rules/search-custom-rules.json b/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-page-should-create-custom-rules/search-custom-rules.json new file mode 100644 index 00000000000..db83a85fbd6 --- /dev/null +++ b/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-page-should-create-custom-rules/search-custom-rules.json @@ -0,0 +1,26 @@ +{ + "total": 1, + "p": 1, + "ps": 10, + "rules": [ + { + "key": "squid:Do_not_use_org_h2_util_StringUtils", + "name": "Do not use org.h2.util.StringUtils", + "severity": "MAJOR", + "params": [ + { + "key": "fromClasses", + "htmlDesc": "Optional. If this property is not defined, all classes should adhere to this constraint. Ex : *.web.*", + "type": "STRING", + "defaultValue": "" + }, + { + "key": "toClasses", + "htmlDesc": "Mandatory. Ex : java.util.Vector, java.util.Hashtable, java.util.Enumeration", + "type": "STRING", + "defaultValue": "org.h2.util.StringUtils" + } + ] + } + ] +} diff --git a/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-page-should-create-custom-rules/search-custom-rules2.json b/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-page-should-create-custom-rules/search-custom-rules2.json new file mode 100644 index 00000000000..0e4184e48a9 --- /dev/null +++ b/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-page-should-create-custom-rules/search-custom-rules2.json @@ -0,0 +1,45 @@ +{ + "total": 2, + "p": 1, + "ps": 10, + "rules": [ + { + "key": "squid:Do_not_use_org_h2_util_StringUtils", + "name": "Do not use org.h2.util.StringUtils", + "severity": "MAJOR", + "params": [ + { + "key": "fromClasses", + "htmlDesc": "Optional. If this property is not defined, all classes should adhere to this constraint. Ex : *.web.*", + "type": "STRING", + "defaultValue": "" + }, + { + "key": "toClasses", + "htmlDesc": "Mandatory. Ex : java.util.Vector, java.util.Hashtable, java.util.Enumeration", + "type": "STRING", + "defaultValue": "org.h2.util.StringUtils" + } + ] + }, + { + "key": "squid:Do_not_use_edu_emory_mathcs_backport_java_util_Collections", + "name": "Do not use edu.emory.mathcs.backport.java.util.Collections", + "severity": "MAJOR", + "params": [ + { + "key": "fromClasses", + "htmlDesc": "Optional. If this property is not defined, all classes should adhere to this constraint. Ex : *.web.*", + "type": "STRING", + "defaultValue": "" + }, + { + "key": "toClasses", + "htmlDesc": "Mandatory. Ex : java.util.Vector, java.util.Hashtable, java.util.Enumeration", + "type": "STRING", + "defaultValue": "edu.emory.mathcs.backport.java.util.Collections" + } + ] + } + ] +} diff --git a/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-page-should-create-custom-rules/search.json b/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-page-should-create-custom-rules/search.json new file mode 100644 index 00000000000..40ab6ae953a --- /dev/null +++ b/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-page-should-create-custom-rules/search.json @@ -0,0 +1,36 @@ +{ + "total": 1, + "p": 1, + "ps": 200, + "rules": [ + { + "key": "squid:ArchitecturalConstraint", + "name": "Architectural constraint", + "lang": "java", + "langName": "Java", + "sysTags": [], + "tags": [], + "status": "READY" + } + ], + "facets": [ + { + "property": "languages", + "values": [ + { + "val": "java", + "count": 1 + } + ] + }, + { + "property": "repositories", + "values": [ + { + "val": "squid", + "count": 1 + } + ] + } + ] +} diff --git a/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-page-should-create-custom-rules/show.json b/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-page-should-create-custom-rules/show.json new file mode 100644 index 00000000000..517c5f6e754 --- /dev/null +++ b/server/sonar-web/src/main/js/tests/e2e/tests/coding-rules-page-should-create-custom-rules/show.json @@ -0,0 +1,37 @@ +{ + "rule": { + "key": "squid:ArchitecturalConstraint", + "repo": "squid", + "name": "Architectural constraint", + "createdAt": "2013-03-27T09:52:40+0100", + "severity": "MAJOR", + "status": "READY", + "internalKey": "ArchitecturalConstraint", + "isTemplate": true, + "tags": [], + "sysTags": [], + "lang": "java", + "langName": "Java", + "htmlDesc": "

A source code comply to an architectural model when it fully\n\tadheres to a set of architectural constraints. A constraint allows to\n\tdeny references between classes by pattern.

\n

You can for instance use this rule to :

\n", + "debtChar": "CHANGEABILITY", + "debtSubChar": "ARCHITECTURE_CHANGEABILITY", + "debtCharName": "Changeability", + "debtSubCharName": "Architecture related changeability", + "debtOverloaded": true, + "debtRemFnType": "LINEAR", + "debtRemFnCoeff": "3h", + "params": [ + { + "key": "fromClasses", + "htmlDesc": "Optional. If this property is not defined, all classes should adhere to this constraint. Ex : *.web.*", + "type": "STRING" + }, + { + "key": "toClasses", + "htmlDesc": "Mandatory. Ex : java.util.Vector, java.util.Hashtable, java.util.Enumeration", + "type": "STRING" + } + ] + }, + "actives": [] +}