diff options
author | Stas Vilchik <vilchiks@gmail.com> | 2014-12-22 18:06:43 +0100 |
---|---|---|
committer | Stas Vilchik <vilchiks@gmail.com> | 2014-12-23 12:37:57 +0100 |
commit | 567c1fb55e6e53760adebe16bb55d60db9700342 (patch) | |
tree | 0bb890954976f9e9d78a6c20224e18786cb5a5d3 | |
parent | 8eb75e33ad854822f9a6765c5941dceab8d94f8e (diff) | |
download | sonarqube-567c1fb55e6e53760adebe16bb55d60db9700342.tar.gz sonarqube-567c1fb55e6e53760adebe16bb55d60db9700342.zip |
SONAR-5820 Add quality profiles
19 files changed, 931 insertions, 188 deletions
diff --git a/server/sonar-web/src/main/hbs/coding-rules/coding-rules-rule-details.hbs b/server/sonar-web/src/main/hbs/coding-rules/coding-rules-rule-details.hbs index 935dd66e8cb..4414fd197d8 100644 --- a/server/sonar-web/src/main/hbs/coding-rules/coding-rules-rule-details.hbs +++ b/server/sonar-web/src/main/hbs/coding-rules/coding-rules-rule-details.hbs @@ -1,125 +1,6 @@ -<h3 class="coding-rules-detail-header"> - {{name}} - <a class="coding-rules-detail-permalink icon-link" target="_blank" href="#rule_key={{key}}"></a> -</h3> -<span class="subtitle">{{key}}</span> - -<ul class="coding-rules-detail-properties"> - {{#unless isManual}} - <li class="coding-rules-detail-property">{{severityIcon severity}} {{t "severity" severity}}</li> - {{/unless}} - {{#notEq status 'READY'}} - <li class="coding-rules-detail-property"> - <span class="coding-rules-detail-status coding-rules-detail-not-ready">{{status}}</span> - </li> - {{/notEq}} - - - <li class="coding-rules-detail-property coding-rules-detail-tag-list {{#if canWrite}}coding-rules-detail-tags-change{{/if}}"> - <i class="icon-tags"></i> - <span>{{#if allTags}}{{join allTags ', '}}{{else}}{{t 'coding_rules.no_tags'}}{{/if}}</span> - </li> - {{#if canWrite}}<li class="coding-rules-detail-property coding-rules-detail-tag-edit"> - {{#if sysTags}}<i class="icon-tags"></i> - <span>{{join sysTags ', '}}</span>{{/if}} - <input class="coding-rules-detail-tag-input" type="text" value="{{#if tags}}{{join tags ','}}{{/if}}"> - - <div class="button-group"> - <button class="coding-rules-detail-tag-edit-done">{{t 'Done'}}</button> - </div> - <a class="coding-rules-details-tag-edit-cancel">{{t 'cancel'}}</a> - </li>{{/if}} - - {{#if subCharacteristic}} - <li class="coding-rules-detail-property coding-rules-subcharacteristic">{{subCharacteristic}}</li> - {{/if}} - <li class="coding-rules-detail-property">{{t 'coding_rules.available_since'}} {{d createdAt}}</li> - <li class="coding-rules-detail-property">{{repository}}{{#unless isManual}} ({{language}}){{/unless}}</li> - - {{#if isTemplate}} - <li class="coding-rules-detail-property" title="{{t 'coding_rules.rule_template.title'}}">{{t 'coding_rules.rule_template'}}</li> - {{/if}} - {{#if templateKey}} - <li class="coding-rules-detail-property" title="{{t 'coding_rules.custom_rule.title'}}">{{t 'coding_rules.custom_rule'}} - (<a href="#rule_key={{templateKey}}">{{t 'coding_rules.show_template'}}</a>) - </li> - {{/if}} -</ul> - -<div class="coding-rules-detail-description rule-desc markdown">{{{htmlDesc}}}</div> - -{{#unless isEditable}} - {{#unless isManual}} - <div class="coding-rules-detail-description coding-rules-detail-description-extra"> - <div id="coding-rules-detail-description-extra"> - {{#if htmlNote}} - <div class="rule-desc marginbottom10 markdown">{{{htmlNote}}}</div>{{/if}} - {{#if canWrite}}<div class="button-group"> - <button id="coding-rules-detail-extend-description">{{t 'coding_rules.extend_description'}}</button> - </div>{{/if}} - </div> - - {{#if canWrite}}<div class="coding-rules-detail-extend-description-form"> - <table class="width100"> - <tbody> - <tr> - <td class="width100" colspan="2"> - <textarea id="coding-rules-detail-extend-description-text" rows="4" - style="width: 100%; margin-bottom: 4px;">{{mdNote}}</textarea> - </td> - </tr> - <tr> - <td> - <div class="button-group"> - <button id="coding-rules-detail-extend-description-submit">{{t 'save'}}</button> - {{#if mdNote}} - <button id="coding-rules-detail-extend-description-remove" class="button-red">{{t 'remove'}}</button> - {{/if}} - </div> - <a id="coding-rules-detail-extend-description-cancel" class="action">{{t 'cancel'}}</a> - </td> - <td class="right"> - {{> '_markdown-tips' }} - </td> - </tr> - </tbody> - </table> - </div> - - <div id="coding-rules-detail-extend-description-spinner"> - <i class="spinner"></i> - </div>{{/if}} - </div> - {{/unless}} -{{/unless}} - - -{{#if params}} - <h3 class="coding-rules-detail-title">{{t 'coding_rules.parameters'}}</h3> - <div class="coding-rules-detail-parameters"> - {{#each params}} - <dl class="coding-rules-detail-parameter"> - <dt class="coding-rules-detail-parameter-name">{{key}}</dt> - <dd class="coding-rules-detail-parameter-description" data-key="{{key}}"> - <p>{{{htmlDesc}}}</p> - {{#if ../../templateKey}} - <div class="subtitle"> - {{#if defaultValue }} - <span class="value">{{defaultValue}}</span> - {{else}} - {{t 'coding_rules.parameter.empty'}} - {{/if}} - </div> - {{else}} - {{#if defaultValue}} - <div class="subtitle">{{t 'coding_rules.parameters.default_value'}} <span class="value">{{defaultValue}}</span></div> - {{/if}} - {{/if}} - </dd> - </dl> - {{/each}} - </div> -{{/if}} +<div class="js-rule-meta"></div> +<div class="js-rule-description"></div> +<div class="js-rule-parameters"></div> {{#if isEditable}} <div class="coding-rules-detail-description"> @@ -134,29 +15,17 @@ </div> {{/if}} - {{#if isTemplate}} <div class="coding-rules-detail-custom-rules-section"> <h3 class="coding-rules-detail-title">{{t 'coding_rules.custom_rules'}}</h3> - {{#if canWrite}}<div class="button-group coding-rules-detail-quality-profiles-activation"> - <button id="coding-rules-custom-rules-create">{{t 'coding_rules.create'}}</button> - </div>{{/if}} + {{#if canWrite}} + <div class="button-group coding-rules-detail-quality-profiles-activation"> + <button id="coding-rules-custom-rules-create">{{t 'coding_rules.create'}}</button> + </div> + {{/if}} <div id="coding-rules-detail-custom-rules"></div> </div> {{/if}} - -{{#if qualityProfilesVisible}} - <div class="coding-rules-detail-quality-profiles-section"> - <h3 class="coding-rules-detail-title">{{t 'coding_rules.quality_profiles'}}</h3> - - {{#if canWrite}}{{#unless isTemplate}}<div class="button-group coding-rules-detail-quality-profiles-activation"> - <button id="coding-rules-quality-profile-activate">{{t 'coding_rules.activate'}}</button> - </div>{{/unless}}{{/if}} - {{#if isTemplate}} - <div class="coding-rules-detail-quality-profiles-template-caption warning">{{t 'coding_rules.quality_profiles.template_caption'}}</div> - {{/if}} - <div id="coding-rules-detail-quality-profiles"></div> - </div> -{{/if}} +<div class="js-rule-profiles"></div> diff --git a/server/sonar-web/src/main/hbs/coding-rules/rule/coding-rules-profile-activation.hbs b/server/sonar-web/src/main/hbs/coding-rules/rule/coding-rules-profile-activation.hbs new file mode 100644 index 00000000000..dc3374d8d46 --- /dev/null +++ b/server/sonar-web/src/main/hbs/coding-rules/rule/coding-rules-profile-activation.hbs @@ -0,0 +1,76 @@ +<form> + <div class="modal-head"> + {{#if change}} + <h2>{{t 'coding_rules.change_details'}}</h2> + {{else}} + <h2>{{t 'coding_rules.activate_in_quality_profile'}}</h2> + {{/if}} + </div> + + <div class="modal-body modal-body-select2"> + <div class="modal-error"></div> + + <table> + <tr class="property"> + <th><h3>{{t 'coding_rules.quality_profile'}}</h3></th> + <td> + {{#if key}} + {{name}} + {{else}} + <select id="coding-rules-quality-profile-activation-select"> + {{#each qualityProfiles}} + <option value="{{key}}">{{name}}</option> + {{/each}} + </select> + {{/if}} + </td> + </tr> + <tr class="property"> + <th><h3>{{t 'severity'}}</h3></th> + <td> + <select id="coding-rules-quality-profile-activation-severity"> + {{#each severities}} + <option value="{{this}}">{{t 'severity' this}}</option> + {{/each}} + </select> + </td> + </tr> + {{#if isCustomRule}} + <tr class="property"> + <td colspan="2" class="note">{{t 'coding_rules.custom_rule.activation_notice'}}</td> + {{else}} + {{#each params}} + <tr class="property"> + <th><h3>{{key}}</h3></th> + <td> + {{#eq type 'TEXT'}} + <textarea class="width100" rows="3" name="{{key}}" placeholder="{{defaultValue}}">{{value}}</textarea> + {{else}} + {{#eq type 'BOOLEAN'}} + <select name="{{key}}" value="{{value}}"> + <option value="{{defaultValue}}">{{t 'default'}} ({{t defaultValue}})</option> + <option value="true"{{#eq value 'true'}} selected="selected"{{/eq}}>{{t 'true'}}</option> + <option value="false"{{#eq value 'false'}} selected="selected"{{/eq}}>{{t 'false'}}</option> + </select> + {{else}} + <input type="text" name="{{key}}" value="{{value}}" placeholder="{{defaultValue}}"> + {{/eq}} + {{/eq}} + <div class="note">{{description}}</div> + {{#if extra}} + <div class="note">{{extra}}</div> + {{/if}} + </td> + </tr> + {{/each}} + {{/if}} + </table> + </div> + + <div class="modal-foot"> + <button id="coding-rules-quality-profile-activation-activate" {{#unless saveEnabled}}disabled="disabled"{{/unless}}> + {{#if change}}{{t 'save'}}{{else}}{{t 'coding_rules.activate'}}{{/if}} + </button> + <a id="coding-rules-quality-profile-activation-cancel" class="js-modal-close">{{t 'cancel'}}</a> + </div> +</form> diff --git a/server/sonar-web/src/main/hbs/coding-rules/rule/coding-rules-rule-description.hbs b/server/sonar-web/src/main/hbs/coding-rules/rule/coding-rules-rule-description.hbs new file mode 100644 index 00000000000..69a31cbbccc --- /dev/null +++ b/server/sonar-web/src/main/hbs/coding-rules/rule/coding-rules-rule-description.hbs @@ -0,0 +1,48 @@ +<div class="coding-rules-detail-description rule-desc markdown">{{{htmlDesc}}}</div> + +{{#unless isEditable}} + {{#unless isManual}} + <div class="coding-rules-detail-description coding-rules-detail-description-extra"> + <div id="coding-rules-detail-description-extra"> + {{#if htmlNote}} + <div class="rule-desc marginbottom10 markdown">{{{htmlNote}}}</div> + {{/if}} + {{#if canWrite}} + <div class="button-group"> + <button id="coding-rules-detail-extend-description">{{t 'coding_rules.extend_description'}}</button> + </div> + {{/if}} + </div> + + {{#if canWrite}} + <div class="coding-rules-detail-extend-description-form hidden"> + <table class="width100"> + <tbody> + <tr> + <td class="width100" colspan="2"> + <textarea id="coding-rules-detail-extend-description-text" rows="4" + style="width: 100%; margin-bottom: 4px;">{{mdNote}}</textarea> + </td> + </tr> + <tr> + <td> + <div class="button-group"> + <button id="coding-rules-detail-extend-description-submit">{{t 'save'}}</button> + {{#if mdNote}} + <button id="coding-rules-detail-extend-description-remove" + class="button-red">{{t 'remove'}}</button> + {{/if}} + </div> + <a id="coding-rules-detail-extend-description-cancel" class="action">{{t 'cancel'}}</a> + </td> + <td class="right"> + {{> '_markdown-tips' }} + </td> + </tr> + </tbody> + </table> + </div> + {{/if}} + </div> + {{/unless}} +{{/unless}} 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 new file mode 100644 index 00000000000..829003f8ed4 --- /dev/null +++ b/server/sonar-web/src/main/hbs/coding-rules/rule/coding-rules-rule-meta.hbs @@ -0,0 +1,54 @@ +<h3 class="coding-rules-detail-header"> + {{name}} + <a class="coding-rules-detail-permalink icon-link" target="_blank" href="#rule_key={{key}}"></a> +</h3> + +<span class="subtitle">{{key}}</span> + +<ul class="coding-rules-detail-properties"> + {{#unless isManual}} + <li class="coding-rules-detail-property">{{severityIcon severity}} {{t "severity" severity}}</li> + {{/unless}} + + {{#notEq status 'READY'}} + <li class="coding-rules-detail-property">{{status}}</li> + {{/notEq}} + + <li class="coding-rules-detail-property coding-rules-detail-tag-list {{#if canWrite}}coding-rules-detail-tags-change{{/if}}"> + <i class="icon-tags"></i> + <span>{{#if allTags}}{{join allTags ', '}}{{else}}{{t 'coding_rules.no_tags'}}{{/if}}</span> + </li> + + {{#if canWrite}} + <li class="coding-rules-detail-property coding-rules-detail-tag-edit hidden"> + {{#if sysTags}}<i class="icon-tags"></i> + <span>{{join sysTags ', '}}</span>{{/if}} + <input class="coding-rules-detail-tag-input" type="text" value="{{#if tags}}{{join tags ','}}{{/if}}"> + + <div class="button-group"> + <button class="coding-rules-detail-tag-edit-done">{{t 'Done'}}</button> + </div> + <a class="coding-rules-details-tag-edit-cancel">{{t 'cancel'}}</a> + </li> + {{/if}} + + {{#if subCharacteristic}} + <li class="coding-rules-detail-property coding-rules-subcharacteristic">{{subCharacteristic}}</li> + {{/if}} + + <li class="coding-rules-detail-property">{{t 'coding_rules.available_since'}} {{d createdAt}}</li> + + <li class="coding-rules-detail-property">{{default repo.name repo}}{{#unless isManual}} ({{langName}}){{/unless}}</li> + + {{#if isTemplate}} + <li class="coding-rules-detail-property" + title="{{t 'coding_rules.rule_template.title'}}">{{t 'coding_rules.rule_template'}}</li> + {{/if}} + + {{#if templateKey}} + <li class="coding-rules-detail-property" + title="{{t 'coding_rules.custom_rule.title'}}">{{t 'coding_rules.custom_rule'}} + (<a href="#rule_key={{templateKey}}">{{t 'coding_rules.show_template'}}</a>) + </li> + {{/if}} +</ul> diff --git a/server/sonar-web/src/main/hbs/coding-rules/rule/coding-rules-rule-parameters.hbs b/server/sonar-web/src/main/hbs/coding-rules/rule/coding-rules-rule-parameters.hbs new file mode 100644 index 00000000000..f0dcb3d8e11 --- /dev/null +++ b/server/sonar-web/src/main/hbs/coding-rules/rule/coding-rules-rule-parameters.hbs @@ -0,0 +1,25 @@ +<h3 class="coding-rules-detail-title">{{t 'coding_rules.parameters'}}</h3> +<table class="coding-rules-detail-parameters"> + {{#each params}} + <tr class="coding-rules-detail-parameter"> + <td class="coding-rules-detail-parameter-name">{{key}}</td> + <td class="coding-rules-detail-parameter-description" data-key="{{key}}"> + <p>{{{htmlDesc}}}</p> + {{#if ../../templateKey}} + <div class="subtitle"> + {{#if defaultValue }} + <span class="value">{{defaultValue}}</span> + {{else}} + {{t 'coding_rules.parameter.empty'}} + {{/if}} + </div> + {{else}} + {{#if defaultValue}} + <div class="subtitle">{{t 'coding_rules.parameters.default_value'}} <span + class="value">{{defaultValue}}</span></div> + {{/if}} + {{/if}} + </td> + </tr> + {{/each}} +</table> diff --git a/server/sonar-web/src/main/hbs/coding-rules/rule/coding-rules-rule-profile.hbs b/server/sonar-web/src/main/hbs/coding-rules/rule/coding-rules-rule-profile.hbs new file mode 100644 index 00000000000..99f6f5f33c4 --- /dev/null +++ b/server/sonar-web/src/main/hbs/coding-rules/rule/coding-rules-rule-profile.hbs @@ -0,0 +1,72 @@ +<td class="coding-rules-detail-quality-profile-name"> + {{name}} + {{#if parent}} + <div class="coding-rules-detail-quality-profile-inheritance"> + {{#eq inherit 'OVERRIDES'}} + <i class="icon-inheritance" title="{{tp 'coding_rules.overrides' name parent.name}}"></i> + {{/eq}} + {{#eq inherit 'INHERITED'}} + <i class="icon-inheritance" title="{{tp 'coding_rules.inherits' name parent.name}}"></i> + {{/eq}} + {{parent.name}} + </div> + {{/if}} +</td> + +{{#if severity}} + <td class="coding-rules-detail-quality-profile-severity"> + {{severityIcon severity}} {{t "severity" severity}} + {{#if parent}}{{#notEq severity parent.severity}} + <div class="coding-rules-detail-quality-profile-inheritance"> + {{t 'coding_rules.original'}} {{t 'severity' parent.severity}} + </div> + {{/notEq}}{{/if}} + </td> + + {{#unless templateKey}} + <td class="coding-rules-detail-quality-profile-parameters"> + {{#each parameters}} + <div class="coding-rules-detail-quality-profile-parameter"> + <span class="key">{{key}}</span><span class="sep">: </span><span class="value" + title="{{value}}">{{value}}</span> + {{#if ../parent}}{{#notEq value original}} + <div class="coding-rules-detail-quality-profile-inheritance"> + {{t 'coding_rules.original'}} <span class="value">{{original}}</span> + </div> + {{/notEq}}{{/if}} + </div> + {{/each}} + + </td> + {{/unless}} + + {{#if canWrite}} + <td class="coding-rules-detail-quality-profile-actions"> + <div class="button-group"> + {{#unless isTemplate}} + <button class="coding-rules-detail-quality-profile-change">{{t 'change_verb'}}</button> + {{/unless}} + {{#if parent}} + {{#eq inherit 'OVERRIDES'}} + <button class="coding-rules-detail-quality-profile-revert button-red"> + {{t 'coding_rules.revert_to_parent_definition'}} + </button> + {{/eq}} + {{else}} + <button class="coding-rules-detail-quality-profile-deactivate button-red"> + {{t 'coding_rules.deactivate'}} + </button> + {{/if}} + </div> + </td> + {{/if}} + +{{else}} + {{#if canWrite}}{{#unless isTemplate}} + <td class="coding-rules-detail-quality-profile-actions"> + <div class="button-group"> + <button class="coding-rules-detail-quality-profile-activate">{{t 'coding_rules.activate'}}</button> + </div> + </td> + {{/unless}}{{/if}} +{{/if}} diff --git a/server/sonar-web/src/main/hbs/coding-rules/rule/coding-rules-rule-profiles.hbs b/server/sonar-web/src/main/hbs/coding-rules/rule/coding-rules-rule-profiles.hbs new file mode 100644 index 00000000000..629206a9162 --- /dev/null +++ b/server/sonar-web/src/main/hbs/coding-rules/rule/coding-rules-rule-profiles.hbs @@ -0,0 +1,19 @@ +<div class="coding-rules-detail-quality-profiles-section"> + <h3 class="coding-rules-detail-title">{{t 'coding_rules.quality_profiles'}}</h3> + + {{#if canWrite}} + {{#unless isTemplate}} + <div class="button-group coding-rules-detail-quality-profiles-activation"> + <button id="coding-rules-quality-profile-activate">{{t 'coding_rules.activate'}}</button> + </div> + {{/unless}} + {{/if}} + + {{#if isTemplate}} + <div class="coding-rules-detail-quality-profiles-template-caption warning"> + {{t 'coding_rules.quality_profiles.template_caption'}} + </div> + {{/if}} + + <table id="coding-rules-detail-quality-profiles" class="coding-rules-detail-quality-profiles width100"></table> +</div> 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 3eec23d697c..3d391182d74 100644 --- a/server/sonar-web/src/main/js/coding-rules/controller.js +++ b/server/sonar-web/src/main/js/coding-rules/controller.js @@ -39,6 +39,7 @@ define([ that.app.list.add(rules); } that.app.list.setIndex(); + that.app.list.addExtraAttributes(that.app.languages, that.app.repositories); that.app.facets.reset(that._allFacets()); that.app.facets.add(r.facets, { merge: true }); that.enableFacets(that._enabledFacets()); @@ -74,26 +75,30 @@ define([ }, getRuleDetails: function (rule) { - var url = baseUrl + '/api/rules/show', + var that = this, + url = baseUrl + '/api/rules/show', options = { key: rule.id, actives: true }; return $.get(url, options).done(function (data) { rule.set(data.rule); + rule.addExtraAttributes(that.app.languages, that.app.repositories); }); }, showDetails: function (rule) { - var that = this; + var that = this, + ruleModel = typeof rule === 'string' ? new Backbone.Model({ key: rule }) : rule; this.app.layout.workspaceDetailsRegion.reset(); - this.getRuleDetails(rule).done(function () { + this.getRuleDetails(ruleModel).done(function (data) { key.setScope('details'); that.app.workspaceListView.unbindScrollEvents(); - that.app.state.set({ rule: rule }); + that.app.state.set({ rule: ruleModel }); that.app.workspaceDetailsView = new RuleDetailsView({ app: that.app, - model: rule + model: ruleModel, + actives: data.actives }); that.app.layout.workspaceDetailsRegion.show(that.app.workspaceDetailsView); that.app.layout.showDetails(); diff --git a/server/sonar-web/src/main/js/coding-rules/models/rule.js b/server/sonar-web/src/main/js/coding-rules/models/rule.js index 78c1804a21d..0b20c8eab0d 100644 --- a/server/sonar-web/src/main/js/coding-rules/models/rule.js +++ b/server/sonar-web/src/main/js/coding-rules/models/rule.js @@ -3,7 +3,20 @@ define([ ], function (Backbone) { return Backbone.Model.extend({ - idAttribute: 'key' + idAttribute: 'key', + + addExtraAttributes: function (languages, repositories) { + var langName = languages[this.get('lang')] || this.get('lang'), + repo = _.findWhere(repositories, { key: this.get('repo') }) || this.get('repo'), + isManual = this.get('repo') === 'manual', + isCustom = this.has('templateKey'); + this.set({ + langName: langName, + repo: repo, + isManual: isManual, + isCustom: isCustom + }); + } }); }); diff --git a/server/sonar-web/src/main/js/coding-rules/models/rules.js b/server/sonar-web/src/main/js/coding-rules/models/rules.js index ea0c3577a2d..752576ee2f8 100644 --- a/server/sonar-web/src/main/js/coding-rules/models/rules.js +++ b/server/sonar-web/src/main/js/coding-rules/models/rules.js @@ -14,6 +14,12 @@ define([ this.forEach(function (rule, index) { rule.set({ index: index }); }); + }, + + addExtraAttributes: function (languages, repositories) { + this.models.forEach(function (model) { + model.addExtraAttributes(languages, repositories); + }); } }); 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 202e1698dfd..2fa6378a677 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 @@ -1,23 +1,62 @@ define([ - 'backbone.marionette', - 'templates/coding-rules' -], function (Marionette, Templates) { + 'backbone', + 'backbone.marionette', + 'templates/coding-rules', + 'coding-rules/rule/rule-meta-view', + 'coding-rules/rule/rule-description-view', + 'coding-rules/rule/rule-parameters-view', + 'coding-rules/rule/rule-profiles-view' +], function (Backbone, Marionette, Templates, MetaView, DescView, ParamView, ProfilesView) { - return Marionette.ItemView.extend({ + return Marionette.Layout.extend({ template: Templates['coding-rules-rule-details'], - modelEvents: { - 'change': 'render' + regions: { + metaRegion: '.js-rule-meta', + descRegion: '.js-rule-description', + paramRegion: '.js-rule-parameters', + profilesRegion: '.js-rule-profiles' }, initialize: function () { this.bindShortcuts(); }, + onRender: function () { + this.metaRegion.show(new MetaView({ + app: this.options.app, + model: this.model + })); + this.descRegion.show(new DescView({ + app: this.options.app, + model: this.model + })); + this.paramRegion.show(new ParamView({ + app: this.options.app, + model: this.model + })); + this.profilesRegion.show(new ProfilesView({ + app: this.options.app, + model: this.model, + collection: new Backbone.Collection(this.getQualityProfiles()) + })); + }, + onClose: function () { this.unbindShortcuts(); }, + getQualityProfiles: function () { + var that = this; + return this.options.actives.map(function (profile) { + var profileBase = _.findWhere(that.options.app.qualityProfiles, { key: profile.qProfile }); + if (profileBase != null) { + _.extend(profile, profileBase); + } + return profile; + }); + }, + bindShortcuts: function () { var that = this; key('up', 'details', function () { @@ -41,12 +80,25 @@ define([ }, serializeData: function () { - var isManual = (this.options.app.manualRepository().key === this.model.get('repo')); + var isManual = (this.options.app.manualRepository().key === this.model.get('repo')), + isCustom = this.model.has('templateKey'), + isEditable = this.options.app.canWrite && (isManual || isCustom), + qualityProfilesVisible = !isManual; + + if (qualityProfilesVisible) { + if (this.model.get('isTemplate')) { + qualityProfilesVisible = !_.isEmpty(this.options.actives); + } + else { + qualityProfilesVisible = (this.options.app.canWrite || !_.isEmpty(this.options.actives)); + } + } return _.extend(Marionette.ItemView.prototype.serializeData.apply(this, arguments), { - language: this.options.app.languages[this.model.get('lang')], - repository: _.findWhere(this.options.app.repositories, { key: this.model.get('repo') }).name, isManual: isManual, + isEditable: isEditable, + canWrite: this.options.app.canWrite, + qualityProfilesVisible: qualityProfilesVisible, subCharacteristic: this.options.app.getSubCharacteristicName(this.model.get('debtSubChar')), allTags: _.union(this.model.get('sysTags'), this.model.get('tags')) }); diff --git a/server/sonar-web/src/main/js/coding-rules/rule/profile-activation-view.js b/server/sonar-web/src/main/js/coding-rules/rule/profile-activation-view.js new file mode 100644 index 00000000000..3bd8855bddb --- /dev/null +++ b/server/sonar-web/src/main/js/coding-rules/rule/profile-activation-view.js @@ -0,0 +1,131 @@ +define([ + 'common/modals', + 'templates/coding-rules' +], function (Modal, Templates) { + + var $ = jQuery; + + return Modal.extend({ + template: Templates['coding-rules-profile-activation'], + + ui: { + qualityProfileSelect: '#coding-rules-quality-profile-activation-select', + qualityProfileSeverity: '#coding-rules-quality-profile-activation-severity', + qualityProfileActivate: '#coding-rules-quality-profile-activation-activate', + qualityProfileParameters: '[name]' + }, + + events: function () { + return _.extend(Modal.prototype.events.apply(this, arguments), { + 'click @ui.qualityProfileActivate': 'activate' + }); + }, + + onRender: function () { + Modal.prototype.onRender.apply(this, arguments); + + this.ui.qualityProfileSelect.select2({ + width: '250px', + minimumResultsForSearch: 5 + }); + + var format = function (state) { + if (!state.id) { + return state.text; + } else { + return '<i class="icon-severity-' + state.id.toLowerCase() + '"></i> ' + state.text; + } + }, + severity = (this.model && this.model.get('severity')) || this.options.rule.get('severity'); + this.ui.qualityProfileSeverity.val(severity); + this.ui.qualityProfileSeverity.select2({ + width: '250px', + minimumResultsForSearch: 999, + formatResult: format, + formatSelection: format + }); + }, + + activate: function () { + var that = this, + p = window.process.addBackgroundProcess(), + profileKey = this.ui.qualityProfileSelect.val(), + params = this.ui.qualityProfileParameters.map(function () { + return { + key: $(this).prop('name'), + value: $(this).val() || $(this).prop('placeholder') || '' + }; + }).get(), + paramsHash = (params.map(function (param) { + return param.key + '=' + window.csvEscape(param.value); + })).join(';'); + + if (this.model) { + profileKey = this.model.get('qProfile'); + if (!profileKey) { + profileKey = this.model.get('key'); + } + } + + var severity = this.ui.qualityProfileSeverity.val(), + ruleKey = this.options.rule.get('key'); + + this.close(); + + return jQuery.ajax({ + type: 'POST', + url: baseUrl + '/api/qualityprofiles/activate_rule', + data: { + profile_key: profileKey, + rule_key: ruleKey, + severity: severity, + params: paramsHash + } + }).done(function () { + that.options.app.controller.showDetails(that.options.rule); + window.process.finishBackgroundProcess(p); + }).fail(function () { + that.options.app.controller.showDetails(that.options.rule); + window.process.failBackgroundProcess(p); + }); + }, + + getAvailableQualityProfiles: function (lang) { + var activeQualityProfiles = this.collection, + inactiveProfiles = _.reject(this.options.app.qualityProfiles, function (profile) { + return activeQualityProfiles.findWhere({ key: profile.key }); + }); + return _.filter(inactiveProfiles, function (profile) { + return profile.lang === lang; + }); + }, + + serializeData: function () { + var params = this.options.rule.get('params'); + if (this.model != null) { + var modelParams = this.model.get('params'); + if (_.isArray(modelParams)) { + params = params.map(function (p) { + var parentParam = _.findWhere(modelParams, { key: p.key }); + if (parentParam != null) { + _.extend(p, { value: parentParam.value }); + } + return p; + }); + } + } + + var availableProfiles = this.getAvailableQualityProfiles(this.options.rule.get('lang')); + + return _.extend(Modal.prototype.serializeData.apply(this, arguments), { + change: this.model && this.model.has('severity'), + params: params, + qualityProfiles: availableProfiles, + severities: ['BLOCKER', 'CRITICAL', 'MAJOR', 'MINOR', 'INFO'], + saveEnabled: !_.isEmpty(availableProfiles) || (this.model && this.model.get('qProfile')), + isCustomRule: (this.model && this.model.has('templateKey')) || this.options.rule.has('templateKey') + }); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/coding-rules/rule/rule-description-view.js b/server/sonar-web/src/main/js/coding-rules/rule/rule-description-view.js new file mode 100644 index 00000000000..dfbe5e156d4 --- /dev/null +++ b/server/sonar-web/src/main/js/coding-rules/rule/rule-description-view.js @@ -0,0 +1,87 @@ +define([ + 'backbone.marionette', + 'templates/coding-rules' +], function (Marionette, Templates) { + + return Marionette.ItemView.extend({ + template: Templates['coding-rules-rule-description'], + + modelEvents: { + 'change': 'render' + }, + + ui: { + descriptionExtra: '#coding-rules-detail-description-extra', + extendDescriptionLink: '#coding-rules-detail-extend-description', + extendDescriptionForm: '.coding-rules-detail-extend-description-form', + extendDescriptionSubmit: '#coding-rules-detail-extend-description-submit', + extendDescriptionRemove: '#coding-rules-detail-extend-description-remove', + extendDescriptionText: '#coding-rules-detail-extend-description-text', + cancelExtendDescription: '#coding-rules-detail-extend-description-cancel' + }, + + events: { + 'click @ui.extendDescriptionLink': 'showExtendDescriptionForm', + 'click @ui.cancelExtendDescription': 'hideExtendDescriptionForm', + 'click @ui.extendDescriptionSubmit': 'submitExtendDescription', + 'click @ui.extendDescriptionRemove': 'removeExtendedDescription' + }, + + showExtendDescriptionForm: function () { + this.ui.descriptionExtra.addClass('hidden'); + this.ui.extendDescriptionForm.removeClass('hidden'); + this.ui.extendDescriptionText.focus(); + }, + + hideExtendDescriptionForm: function () { + this.ui.descriptionExtra.removeClass('hidden'); + this.ui.extendDescriptionForm.addClass('hidden'); + }, + + submitExtendDescription: function () { + var that = this, + p = window.process.addBackgroundProcess(); + this.ui.extendDescriptionForm.addClass('hidden'); + return jQuery.ajax({ + type: 'POST', + url: baseUrl + '/api/rules/update', + dataType: 'json', + data: { + key: this.model.get('key'), + markdown_note: this.ui.extendDescriptionText.val() + } + }).done(function (r) { + that.model.set({ + htmlNote: r.rule.htmlNote, + mdNote: r.rule.mdNote + }); + that.render(); + window.process.finishBackgroundProcess(p); + }).fail(function () { + that.render(); + window.process.failBackgroundProcess(p); + }); + }, + + removeExtendedDescription: function () { + var that = this; + window.confirmDialog({ + html: t('coding_rules.remove_extended_description.confirm'), + yesHandler: function () { + that.ui.extendDescriptionText.val(''); + that.submitExtendDescription(); + } + }); + }, + + serializeData: function () { + var isEditable = this.options.app.canWrite && (this.model.get('isManual') || this.model.get('isCustom')); + + return _.extend(Marionette.ItemView.prototype.serializeData.apply(this, arguments), { + isEditable: isEditable, + canWrite: this.options.app.canWrite + }); + } + }); + +}); 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 new file mode 100644 index 00000000000..6f3c3c36700 --- /dev/null +++ b/server/sonar-web/src/main/js/coding-rules/rule/rule-meta-view.js @@ -0,0 +1,86 @@ +define([ + 'backbone.marionette', + 'templates/coding-rules' +], function (Marionette, Templates) { + + return Marionette.ItemView.extend({ + template: Templates['coding-rules-rule-meta'], + + modelEvents: { + 'change': 'render' + }, + + ui: { + tagsChange: '.coding-rules-detail-tags-change', + tagInput: '.coding-rules-detail-tag-input', + tagsEdit: '.coding-rules-detail-tag-edit', + tagsEditDone: '.coding-rules-detail-tag-edit-done', + tagsEditCancel: '.coding-rules-details-tag-edit-cancel', + tagsList: '.coding-rules-detail-tag-list' + }, + + events: { + 'click @ui.tagsChange': 'changeTags', + 'click @ui.tagsEditDone': 'editDone', + 'click @ui.tagsEditCancel': 'cancelEdit' + }, + + requestTags: function () { + var url = baseUrl + '/api/rules/tags'; + return jQuery.get(url); + }, + + changeTags: function () { + var that = this; + this.requestTags().done(function (r) { + that.ui.tagInput.select2({ + tags: _.difference(_.difference(r.tags, that.model.get('tags')), that.model.get('sysTags')), + width: '300px' + }); + + that.ui.tagsEdit.removeClass('hidden'); + that.ui.tagsList.addClass('hidden'); + that.tagsBuffer = that.ui.tagInput.select2('val'); + }); + }, + + cancelEdit: function () { + this.ui.tagsList.removeClass('hidden'); + this.ui.tagsEdit.addClass('hidden'); + if (this.ui.tagInput.select2) { + this.ui.tagInput.select2('val', this.tagsBuffer); + this.ui.tagInput.select2('close'); + } + }, + + editDone: function () { + var that = this, + p = window.process.addBackgroundProcess(), + tags = this.ui.tagInput.val(); + return jQuery.ajax({ + type: 'POST', + url: baseUrl + '/api/rules/update', + data: { + key: this.model.get('key'), + tags: tags + } + }).done(function (r) { + that.model.set('tags', r.rule.tags); + that.cancelEdit(); + window.process.finishBackgroundProcess(p); + }).always(function () { + that.cancelEdit(); + window.process.failBackgroundProcess(p); + }); + }, + + serializeData: function () { + 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')) + }); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/coding-rules/rule/rule-parameters-view.js b/server/sonar-web/src/main/js/coding-rules/rule/rule-parameters-view.js new file mode 100644 index 00000000000..f2572460033 --- /dev/null +++ b/server/sonar-web/src/main/js/coding-rules/rule/rule-parameters-view.js @@ -0,0 +1,27 @@ +define([ + 'backbone.marionette', + 'templates/coding-rules' +], function (Marionette, Templates) { + + return Marionette.ItemView.extend({ + template: Templates['coding-rules-rule-parameters'], + + modelEvents: { + 'change': 'render' + }, + + onRender: function () { + this.$el.toggleClass('hidden', _.isEmpty(this.model.get('params'))); + }, + + serializeData: function () { + var isEditable = this.options.app.canWrite && (this.model.get('isManual') || this.model.get('isCustom')); + + return _.extend(Marionette.ItemView.prototype.serializeData.apply(this, arguments), { + isEditable: isEditable, + canWrite: this.options.app.canWrite + }); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/coding-rules/rule/rule-profile-view.js b/server/sonar-web/src/main/js/coding-rules/rule/rule-profile-view.js new file mode 100644 index 00000000000..6e90f6ff947 --- /dev/null +++ b/server/sonar-web/src/main/js/coding-rules/rule/rule-profile-view.js @@ -0,0 +1,135 @@ +define([ + 'backbone.marionette', + 'templates/coding-rules', + 'coding-rules/rule/profile-activation-view' +], function (Marionette, Templates, ProfileActivationView) { + + return Marionette.ItemView.extend({ + tagName: 'tr', + template: Templates['coding-rules-rule-profile'], + + modelEvents: { + 'change': 'render' + }, + + ui: { + change: '.coding-rules-detail-quality-profile-change', + revert: '.coding-rules-detail-quality-profile-revert', + deactivate: '.coding-rules-detail-quality-profile-deactivate' + }, + + events: { + 'click @ui.change': 'change', + 'click @ui.revert': 'revert', + 'click @ui.deactivate': 'deactivate' + }, + + change: function () { + new ProfileActivationView({ + model: this.model, + collection: this.model.collection, + rule: this.options.rule, + app: this.options.app + }).render(); + }, + + revert: function () { + var that = this, + ruleKey = this.options.rule.get('key'); + window.confirmDialog({ + title: t('coding_rules.revert_to_parent_definition'), + html: tp('coding_rules.revert_to_parent_definition.confirm', this.getParent().name), + yesHandler: function () { + var p = window.process.addBackgroundProcess(); + return jQuery.ajax({ + type: 'POST', + url: baseUrl + '/api/qualityprofiles/activate_rule', + data: { + profile_key: that.model.get('qProfile'), + rule_key: ruleKey, + reset: true + } + }).done(function () { + window.process.finishBackgroundProcess(p); + that.options.app.controller.showDetails(that.options.rule); + }); + } + }); + }, + + deactivate: function () { + var that = this, + ruleKey = this.options.rule.get('key'), + myProfile = _.findWhere(this.options.app.qualityProfiles, { + key: this.model.get('qProfile') + }); + window.confirmDialog({ + title: t('coding_rules.deactivate'), + html: tp('coding_rules.deactivate.confirm', myProfile.name), + yesHandler: function () { + var p = window.process.addBackgroundProcess(); + return jQuery.ajax({ + type: 'POST', + url: baseUrl + '/api/qualityprofiles/deactivate_rule', + data: { + profile_key: that.model.get('qProfile'), + rule_key: ruleKey + } + }).done(function () { + window.process.finishBackgroundProcess(p); + that.options.app.controller.showDetails(that.options.rule); + }); + } + }); + }, + + enableUpdate: function () { + return this.ui.update.prop('disabled', false); + }, + + getParent: function () { + if (!(this.model.get('inherit') && this.model.get('inherit') !== 'NONE')) { + return null; + } + var myProfile = _.findWhere(this.options.app.qualityProfiles, { + key: this.model.get('qProfile') + }), + parentKey = myProfile.parentKey, + parent = _.extend({}, _.findWhere(this.options.app.qualityProfiles, { + key: parentKey + })), + parentActiveInfo = this.model.collection.findWhere({ qProfile: parentKey }) || new Backbone.Model(); + _.extend(parent, parentActiveInfo.toJSON()); + return parent; + }, + + enhanceParameters: function () { + var parent = this.getParent(), + params = _.sortBy(this.model.get('params'), 'key'); + if (!parent) { + return params; + } + return params.map(function (p) { + var parentParam = _.findWhere(parent.params, { key: p.key }); + if (parentParam != null) { + return _.extend(p, { + original: _.findWhere(parent.params, { key: p.key }).value + }); + } else { + return p; + } + }); + }, + + serializeData: function () { + return _.extend(Marionette.ItemView.prototype.serializeData.apply(this, arguments), { + canWrite: this.options.app.canWrite, + parent: this.getParent(), + parameters: this.enhanceParameters(), + templateKey: this.options.rule.get('templateKey'), + isTemplate: this.options.rule.get('isTemplate') + }); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/coding-rules/rule/rule-profiles-view.js b/server/sonar-web/src/main/js/coding-rules/rule/rule-profiles-view.js new file mode 100644 index 00000000000..b9e36555201 --- /dev/null +++ b/server/sonar-web/src/main/js/coding-rules/rule/rule-profiles-view.js @@ -0,0 +1,43 @@ +define([ + 'backbone.marionette', + 'templates/coding-rules', + 'coding-rules/rule/rule-profile-view', + 'coding-rules/rule/profile-activation-view' +], function (Marionette, Templates, ProfileView, ProfileActivationView) { + + return Marionette.CompositeView.extend({ + template: Templates['coding-rules-rule-profiles'], + itemView: ProfileView, + itemViewContainer: '#coding-rules-detail-quality-profiles', + + itemViewOptions: function () { + return { + app: this.options.app, + rule: this.model + }; + }, + + modelEvents: { + 'change': 'render' + }, + + events: { + 'click #coding-rules-quality-profile-activate': 'activate' + }, + + activate: function () { + new ProfileActivationView({ + rule: this.model, + collection: this.collection, + app: this.options.app + }).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/less/coding-rules.less b/server/sonar-web/src/main/less/coding-rules.less index f8bd0a43811..86badc92f7a 100644 --- a/server/sonar-web/src/main/less/coding-rules.less +++ b/server/sonar-web/src/main/less/coding-rules.less @@ -57,9 +57,10 @@ .coding-rules-detail-header, .coding-rules-detail-title { position: relative; - margin-bottom: @navigatorPadding; + margin: 1em 0; line-height: 1.5; - font-weight: bold; + font-size: @bigFontSize; + font-weight: 400; } .coding-rules-detail-header { @@ -70,8 +71,7 @@ .coding-rules-detail-title { display: inline-block; - margin-top: 3 * @navigatorPadding; - text-transform: uppercase; + margin-top: 2em; } .coding-rules-detail-permalink { @@ -227,28 +227,24 @@ } .coding-rules-detail-parameters { + width: 100%; margin: @navigatorPadding 0 @navigatorPadding * 2; } .coding-rules-detail-parameter { - margin: @navigatorPadding 0; + } .coding-rules-detail-parameter-name { - display: block; - margin-left: 2 * @navigatorPadding; + width: 1px; + vertical-align: top; + padding: 5px 10px 5px 0; font-weight: bold; - cursor: pointer; } .coding-rules-detail-parameter-description { - display: inline-block; - text-overflow: ellipsis; vertical-align: top; - max-width: 75%; - margin-left: 2 * @navigatorPadding; - padding: @navigatorPadding; - .box-sizing(border-box); + padding: 5px 5px; .subtitle { margin-top: @navigatorPadding; @@ -278,44 +274,43 @@ } .coding-rules-detail-quality-profiles { - font-size: 0; -} + line-height: 22px; -.coding-rules-detail-quality-profile { - margin-left: 2 * @navigatorPadding; -} + td { + border-top: 1px solid @barBorderColor; + } -.coding-rules-detail-quality-profile + .coding-rules-detail-quality-profile { - margin-top: @navigatorPadding; - padding-top: @navigatorPadding; - border-top: 1px solid @navigatorBorderLightColor; + tr:first-child td { + border-top: none; + } } .coding-rules-detail-quality-profile-name { vertical-align: top; - width: 15%; + width: 1px; + padding: 8px 5px 8px 0; font-weight: bold; white-space: nowrap; - padding-right: 5px; } .coding-rules-detail-quality-profile-severity { vertical-align: top; - width: 10%; + width: 1px; + padding: 8px 5px; + white-space: nowrap; } .coding-rules-detail-quality-profile-parameters { vertical-align: top; -} - -.coding-rules-detail-quality-profile-parameter + .coding-rules-detail-quality-profile-parameter { - margin-top: 8px; + padding: 8px 5px; } .coding-rules-detail-quality-profile-actions { vertical-align: top; - width: 25%; + width: 1px; + padding: 8px 0 8px 5px; text-align: right; + white-space: nowrap; } .coding-rules-detail-quality-profile-inheritance { diff --git a/server/sonar-web/src/main/less/components/modals.less b/server/sonar-web/src/main/less/components/modals.less index 97059162852..6581e6691c6 100644 --- a/server/sonar-web/src/main/less/components/modals.less +++ b/server/sonar-web/src/main/less/components/modals.less @@ -1,6 +1,6 @@ .modal { position: fixed; - z-index: 9100; + z-index: 9000; top: 15%; left: 50%; margin-left: -270px; @@ -10,7 +10,7 @@ .modal-overlay { position: fixed; - z-index: 9099; + z-index: 8999; top: 0; bottom: 0; left: 0; right: 0; background-color: rgba(0, 0, 0, 0.5); } |