]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-1330 Allow rule changes only for quality profile admins
authorStas Vilchik <stas.vilchik@sonarsource.com>
Tue, 26 Sep 2017 12:41:01 +0000 (14:41 +0200)
committerStas Vilchik <stas.vilchik@sonarsource.com>
Mon, 2 Oct 2017 15:18:15 +0000 (17:18 +0200)
17 files changed:
server/sonar-web/src/main/js/api/rules.ts
server/sonar-web/src/main/js/apps/coding-rules/bulk-change-modal-view.js
server/sonar-web/src/main/js/apps/coding-rules/bulk-change-popup-view.js
server/sonar-web/src/main/js/apps/coding-rules/facets/quality-profile-facet.js
server/sonar-web/src/main/js/apps/coding-rules/init.js
server/sonar-web/src/main/js/apps/coding-rules/rule/profile-activation-view.js
server/sonar-web/src/main/js/apps/coding-rules/rule/rule-profile-view.js
server/sonar-web/src/main/js/apps/coding-rules/rule/rule-profiles-view.js
server/sonar-web/src/main/js/apps/coding-rules/templates/_coding-rules-workspace-list-item-activation.hbs [deleted file]
server/sonar-web/src/main/js/apps/coding-rules/templates/coding-rules-workspace-header.hbs
server/sonar-web/src/main/js/apps/coding-rules/templates/coding-rules-workspace-list-item.hbs
server/sonar-web/src/main/js/apps/coding-rules/templates/rule/coding-rules-rule-profile.hbs
server/sonar-web/src/main/js/apps/coding-rules/templates/rule/coding-rules-rule-profiles.hbs
server/sonar-web/src/main/js/apps/coding-rules/workspace-header-view.js
server/sonar-web/src/main/js/apps/coding-rules/workspace-list-item-view.js
server/sonar-web/src/main/less/components/search-navigator.less
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 20a0b26f5a2ce5a001707f70b5aae37368ad1507..ded000324d1f7bcf7e760c2fc9ae42b7004a2386 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import { getJSON, RequestData } from '../helpers/request';
+import throwGlobalError from '../app/utils/throwGlobalError';
+
+export interface GetRulesAppResponse {
+  respositories: Array<{ key: string; language: string; name: string }>;
+}
+
+export function getRulesApp(): Promise<GetRulesAppResponse> {
+  return getJSON('/api/rules/app').catch(throwGlobalError);
+}
 
 export function searchRules(data: RequestData) {
   return getJSON('/api/rules/search', data);
index 07277f983f6bfd53164b2bda60e0d37bbf7580e9..953c9c7900e0ec56e1d781a00eee4fdc01abc337 100644 (file)
@@ -107,9 +107,11 @@ export default ModalFormView.extend({
     const languages = queryLanguages && queryLanguages.length > 0 ? queryLanguages.split(',') : [];
     let profiles = this.options.app.qualityProfiles;
     if (languages.length > 0) {
-      profiles = profiles.filter(profile => languages.indexOf(profile.lang) !== -1);
+      profiles = profiles.filter(profile => languages.indexOf(profile.language) !== -1);
     }
-    return profiles.filter(profile => !profile.isBuiltIn);
+    return profiles
+      .filter(profile => profile.actions && profile.actions.edit)
+      .filter(profile => !profile.isBuiltIn);
   },
 
   serializeData() {
index 50c6c6d46762c0ee05bcff7ac025a83c77d3e5db..d038bf5c9df076c3479af669298a3bf1b5ee9fd7 100644 (file)
@@ -44,12 +44,14 @@ export default PopupView.extend({
     const profileKey = query.qprofile;
     const profile = this.options.app.qualityProfiles.find(p => p.key === profileKey);
     const activation = '' + query.activation;
+    const canChangeProfile =
+      profile != null && !profile.isBuiltIn && profile.actions && profile.actions.edit;
 
     return {
       qualityProfile: profileKey,
       qualityProfileName: profile != null ? profile.name : null,
-      allowActivateOnProfile: profile != null && activation === 'false' && !profile.isBuiltIn,
-      allowDeactivateOnProfile: profileKey != null && activation === 'true'
+      allowActivateOnProfile: canChangeProfile && activation === 'false',
+      allowDeactivateOnProfile: canChangeProfile && activation === 'true'
     };
   }
 });
index 3efdac89cfb2bacd2ecdb3752d771326b3bc6ffb..d0329e4caaa1d2b331acff8a73dde44ecadeb718 100644 (file)
@@ -50,9 +50,9 @@ export default BaseFacet.extend({
     const languages = languagesQuery != null ? languagesQuery.split(',') : [];
     const lang = languages.length === 1 ? languages[0] : null;
     const values = this.options.app.qualityProfiles
-      .filter(profile => (lang != null ? profile.lang === lang : true))
+      .filter(profile => (lang != null ? profile.language === lang : true))
       .map(profile => ({
-        extra: that.options.app.languages[profile.lang],
+        extra: that.options.app.languages[profile.language],
         isBuiltIn: profile.isBuiltIn,
         label: profile.name,
         val: profile.key
index 36d7f608fdaebcb626d7e275e2785e388c591084..ed38639c9e0b8cf56349157d8a996cb89ad75186 100644 (file)
@@ -32,6 +32,8 @@ import Router from '../../components/navigator/router';
 import WorkspaceListView from './workspace-list-view';
 import WorkspaceHeaderView from './workspace-header-view';
 import FacetsView from './facets-view';
+import { searchQualityProfiles } from '../../api/quality-profiles';
+import { getRulesApp } from '../../api/rules';
 import { areThereCustomOrganizations } from '../../store/organizations/utils';
 
 const App = new Marionette.Application();
@@ -45,20 +47,16 @@ App.on('start', function(
 ) {
   App.organization = options.organization;
   const data = options.organization ? { organization: options.organization } : {};
-  $.get(window.baseUrl + '/api/rules/app', data)
-    .done(r => {
+  Promise.all([getRulesApp(data), searchQualityProfiles(data)])
+    .then(([appResponse, profilesResponse]) => {
       App.customRules = !areThereCustomOrganizations();
-      App.canWrite = r.canWrite;
+      App.canWrite = appResponse.canWrite;
       App.organization = options.organization;
-      App.qualityProfiles = sortBy(r.qualityprofiles, ['name', 'lang']);
-      App.languages = { ...r.languages, none: 'None' };
-      App.qualityProfiles.forEach(profile => {
-        profile.language = App.languages[profile.lang];
-      });
-      App.repositories = r.repositories;
-      App.statuses = r.statuses;
-    })
-    .done(() => {
+      App.qualityProfiles = sortBy(profilesResponse.profiles, ['name', 'lang']);
+      App.languages = { ...appResponse.languages, none: 'None' };
+      App.repositories = appResponse.repositories;
+      App.statuses = appResponse.statuses;
+
       this.layout = new Layout({ el: options.el });
       this.layout.render();
       $('#footer').addClass('page-footer-with-sidebar');
@@ -109,6 +107,9 @@ App.on('start', function(
         app: this
       });
       Backbone.history.start();
+    })
+    .catch(() => {
+      // do nothing in case of WS error
     });
 });
 
index f55361b621f67dfb3a1f611dd140ae7e87f2bd28..c4c34051150638a8141a83ad2759617da5532037 100644 (file)
@@ -132,8 +132,10 @@ export default ModalForm.extend({
     const inactiveProfiles = this.options.app.qualityProfiles.filter(
       profile => !activeQualityProfiles.findWhere({ key: profile.key })
     );
+    // choose QP which a user can administrate, which are the same language and which are not built-in
     return inactiveProfiles
-      .filter(profile => profile.lang === lang)
+      .filter(profile => profile.actions && profile.actions.edit)
+      .filter(profile => profile.language === lang)
       .filter(profile => !profile.isBuiltIn);
   },
 
index 916af2e5f037619ac4209806cbeaa8b07d87b0ec..3ddbfd5096131e39ec6a3da5ec8b6709bb509244 100644 (file)
@@ -164,12 +164,13 @@ export default Marionette.ItemView.extend({
     return {
       ...Marionette.ItemView.prototype.serializeData.apply(this, arguments),
       parent,
+      actions: this.model.get('actions') || {},
       canWrite: this.options.app.canWrite,
       parameters: this.enhanceParameters(parent),
       templateKey: this.options.rule.get('templateKey'),
       isTemplate: this.options.rule.get('isTemplate'),
-      profilePath: this.getProfilePath(this.model.get('lang'), this.model.get('name')),
-      parentProfilePath: parent && this.getProfilePath(parent.lang, parent.name)
+      profilePath: this.getProfilePath(this.model.get('language'), this.model.get('name')),
+      parentProfilePath: parent && this.getProfilePath(parent.language, parent.name)
     };
   }
 });
index dadabe8ea18bf1603f68f59d4beaf8d5845e38ce..81781afab8bf9d3b1c5553419b8ec2919e2d245a 100644 (file)
@@ -85,9 +85,14 @@ export default Marionette.CompositeView.extend({
   },
 
   serializeData() {
+    // show "Activate" button only if user has at least one QP which he administates
+    const canActivate = this.options.app.qualityProfiles.some(
+      profile => profile.actions && profile.actions.edit
+    );
+
     return {
       ...Marionette.ItemView.prototype.serializeData.apply(this, arguments),
-      canWrite: this.options.app.canWrite
+      canActivate
     };
   }
 });
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/templates/_coding-rules-workspace-list-item-activation.hbs b/server/sonar-web/src/main/js/apps/coding-rules/templates/_coding-rules-workspace-list-item-activation.hbs
deleted file mode 100644 (file)
index 84f8997..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-{{#each activeProfiles}}
-  {{severityIcon severity}}
-
-  {{#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}}
-
-  {{#if ../canWrite}}
-    <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>
-  {{/if}}
-{{/each}}
index 53dcba3b520923dd8d30a2c6e18548f2d9cbf217..97623f1e77a18360fe0e1fed52fd87be363da152 100644 (file)
@@ -29,7 +29,7 @@
   <div class="search-navigator-header-buttons button-group">
     <button class="js-reload">{{t 'reload'}}</button>
     <button class="js-new-search" id="coding-rules-new-search">{{t 'coding_rules.new_search'}}</button>
-    {{#if canWrite}}
+    {{#if canBulkChange}}
       <button class="js-bulk-change">{{t 'bulk_change'}}</button>
     {{/if}}
   </div>
index 23324db3e9eaff5a3cea7c41bbbce1eef7d79a6b..139d46cdcb60b84cd07e76996ae30a3e2706b5c6 100644 (file)
     </td>
 
     {{#any activation selectedProfile}}
-      {{#if canWrite}}
+      {{#if canEditQualityProfile}}
         {{#unless isSelectedProfileBuiltIn}}
           <td class="coding-rule-table-meta-cell coding-rule-activation-actions">
             <div class="button-group">
               {{#if activation}}
-                {{#eq activation.inherit 'NONE'}}
-                  <button class="coding-rules-detail-quality-profile-deactivate button-red">
-                    {{t 'coding_rules.deactivate'}}
-                  </button>
-                {{/eq}}
+                <button class="coding-rules-detail-quality-profile-deactivate button-red" 
+                        {{#notEq activation.inherit 'NONE'}}disabled  title="{{t 'coding_rules.can_not_deactivate'}}"{{/notEq}}>
+                  {{t 'coding_rules.deactivate'}}
+                </button>
               {{else}}
                 {{#unless isTemplate}}
                   <button class="coding-rules-detail-quality-profile-activate">{{t 'coding_rules.activate'}}</button>
index 00a1c8054118798be39ec77f8b13bfa771c701f4..e65ab66389261ab7fed2015140ff54bd8e2f4991 100644 (file)
@@ -52,7 +52,7 @@
     </td>
   {{/unless}}
 
-  {{#if canWrite}}
+  {{#if actions.edit}}
     {{#unless isBuiltIn}}
       <td class="coding-rules-detail-quality-profile-actions">
         <div class="button-group">
index 2a28c69d62e2c728da032bffd7895e2cb8872a7e..efd296d207425f9daf10071f96045654a7b8d157 100644 (file)
@@ -3,7 +3,7 @@
 
   <h3 class="coding-rules-detail-title">{{t 'coding_rules.quality_profiles'}}</h3>
 
-  {{#if canWrite}}
+  {{#if canActivate}}
     {{#unless isTemplate}}
       <div class="button-group coding-rules-detail-quality-profiles-activation">
         <button id="coding-rules-quality-profile-activate">{{t 'coding_rules.activate'}}</button>
index a5ac0165f28aee34e301dcc796e3da74c4d23f1f..708164d62aeb72bc0d11e09bb084981961da409a 100644 (file)
@@ -58,9 +58,14 @@ export default WorkspaceHeaderView.extend({
   },
 
   serializeData() {
+    // show "Bulk Change" button only if user has at least one QP which he administates
+    const canBulkChange = this.options.app.qualityProfiles.some(
+      profile => profile.actions && profile.actions.edit
+    );
+
     return {
       ...WorkspaceHeaderView.prototype.serializeData.apply(this, arguments),
-      canWrite: this.options.app.canWrite
+      canBulkChange
     };
   }
 });
index b5d0fafe2ee007b733603a2f2447f1242739ada0..6235c15c7abf1a59cc8f167e093c37cb2a7e1817 100644 (file)
@@ -118,10 +118,13 @@ export default WorkspaceListItemView.extend(RuleFilterMixin).extend({
       this.options.app.qualityProfiles.find(profile => profile.key === selectedProfileKey);
     const isSelectedProfileBuiltIn = selectedProfile != null && selectedProfile.isBuiltIn;
 
+    const canEditQualityProfile =
+      selectedProfile && selectedProfile.actions && selectedProfile.actions.edit;
+
     return {
       ...WorkspaceListItemView.prototype.serializeData.apply(this, arguments),
+      canEditQualityProfile,
       tags: union(this.model.get('sysTags'), this.model.get('tags')),
-      canWrite: this.options.app.canWrite,
       selectedProfile: selectedProfileKey,
       isSelectedProfileBuiltIn
     };
index 268629f8e3c800149920bb1efff5104f358bf008..e6c3701479506513c55e22b3dc4f47d1abdf504b 100644 (file)
     float: left;
     height: 16px;
     line-height: 16px;
+    margin-top: -1px;
     padding: 0 5px;
-    border-radius: 3px;
+    border-radius: 2px;
     font-size: 11px;
-    font-weight: 300;
     text-transform: lowercase;
 
     &:hover {
index 973400b7713d47fe6bf21cb23ff84cd9fc3702c1..661dde6c15e93f3d6e3d482b7640ffad28c88557 100644 (file)
@@ -1674,6 +1674,7 @@ coding_rules.available_since=Available Since
 coding_rules.bulk_change=Bulk Change
 coding_rules.bulk_change.success={2} rule(s) changed in profile {0} - {1}
 coding_rules.bulk_change.warning={2} rule(s) changed, {3} rule(s) ignored in profile {0} - {1}
+coding_rules.can_not_deactivate=This rule is inherited and can not be deactivated.
 coding_rules.change_severity=Change Severity
 coding_rules.change_severity_in=Change Severity In
 coding_rules.change_details=Change Details of Quality Profile