Commit b6a7edb744689b21a1c0f81e21a3eb0f2a05ef70

Authored by Carlos Purificação
2 parents 251f886b 22c795d6

Merge branch 'article-actions-permission' into 'master'

Hide article buttons when user doesn't have permission to execute it

See merge request !34
src/app/article/article-default-view-component.spec.ts
@@ -65,16 +65,36 @@ describe("Components", () => { @@ -65,16 +65,36 @@ describe("Components", () => {
65 expect(state.transitionTo).toHaveBeenCalled(); 65 expect(state.transitionTo).toHaveBeenCalled();
66 }); 66 });
67 67
  68 + it("hide button to delete article when user doesn't have permission", () => {
  69 + expect(helper.find(".article-toolbar .delete-article").attr('style')).toEqual("display: none; ");
  70 + });
  71 +
  72 + it("hide button to edit article when user doesn't have permission", () => {
  73 + expect(helper.find(".article-toolbar .edit-article").attr('style')).toEqual("display: none; ");
  74 + });
  75 +
  76 + it("show button to edit article when user has permission", () => {
  77 + (<any>helper.component['article'])['permissions'] = ['allow_edit'];
  78 + helper.detectChanges();
  79 + expect(helper.find(".article-toolbar .edit-article").attr('style')).toEqual('');
  80 + });
  81 +
  82 + it("show button to delete article when user has permission", () => {
  83 + (<any>helper.component['article'])['permissions'] = ['allow_delete'];
  84 + helper.detectChanges();
  85 + expect(helper.find(".article-toolbar .delete-article").attr('style')).toEqual('');
  86 + });
  87 +
68 /** 88 /**
69 * Execute the delete method on the target component 89 * Execute the delete method on the target component
70 */ 90 */
71 function doDeleteArticle() { 91 function doDeleteArticle() {
72 // Create a mock for the notification service confirmation 92 // Create a mock for the notification service confirmation
73 - spyOn(helper.component.notificationService, 'confirmation').and.callFake(function (params: Function) { 93 + spyOn(helper.component.notificationService, 'confirmation').and.callFake(function(params: Function) {
74 94
75 }); 95 });
76 // Create a mock for the ArticleService removeArticle method 96 // Create a mock for the ArticleService removeArticle method
77 - spyOn(helper.component.articleService, 'remove').and.callFake(function (param: noosfero.Article) { 97 + spyOn(helper.component.articleService, 'remove').and.callFake(function(param: noosfero.Article) {
78 98
79 return { 99 return {
80 catch: () => { } 100 catch: () => { }
src/app/article/article-default-view.component.ts
@@ -6,6 +6,7 @@ import {ArticleToolbarHotspotComponent} from &quot;../hotspot/article-toolbar-hotspot @@ -6,6 +6,7 @@ import {ArticleToolbarHotspotComponent} from &quot;../hotspot/article-toolbar-hotspot
6 import {ArticleContentHotspotComponent} from "../hotspot/article-content-hotspot.component"; 6 import {ArticleContentHotspotComponent} from "../hotspot/article-content-hotspot.component";
7 import {ArticleService} from "./../../lib/ng-noosfero-api/http/article.service"; 7 import {ArticleService} from "./../../lib/ng-noosfero-api/http/article.service";
8 import { NotificationService } from "./../shared/services/notification.service"; 8 import { NotificationService } from "./../shared/services/notification.service";
  9 +import {PermissionDirective} from '../shared/components/permission/permission.directive';
9 10
10 /** 11 /**
11 * @ngdoc controller 12 * @ngdoc controller
@@ -16,7 +17,8 @@ import { NotificationService } from &quot;./../shared/services/notification.service&quot;; @@ -16,7 +17,8 @@ import { NotificationService } from &quot;./../shared/services/notification.service&quot;;
16 */ 17 */
17 @Component({ 18 @Component({
18 selector: 'noosfero-default-article', 19 selector: 'noosfero-default-article',
19 - templateUrl: 'app/article/article.html' 20 + templateUrl: 'app/article/article.html',
  21 + directives: [PermissionDirective]
20 }) 22 })
21 @Inject("$state", ArticleService, NotificationService) 23 @Inject("$state", ArticleService, NotificationService)
22 export class ArticleDefaultViewComponent { 24 export class ArticleDefaultViewComponent {
src/app/article/article.html
@@ -4,13 +4,15 @@ @@ -4,13 +4,15 @@
4 </div> 4 </div>
5 5
6 <div class="sub-header clearfix"> 6 <div class="sub-header clearfix">
7 - <a href="#" class="btn btn-default btn-xs" ui-sref="main.cmsEdit({profile: ctrl.profile.identifier, id: ctrl.article.id})">  
8 - <i class="fa fa-pencil-square-o fa-fw fa-lg"></i> {{"article.actions.edit" | translate}}  
9 - </a>  
10 - <a href="#" class="btn btn-default btn-xs" ng-click="ctrl.delete()">  
11 - <i class="fa fa-trash-o fa-fw fa-lg" ng-click="ctrl.delete()"></i> {{"article.actions.delete" | translate}}  
12 - </a>  
13 - <noosfero-hotspot-article-toolbar [article]="ctrl.article"></noosfero-hotspot-article-toolbar> 7 + <div class="article-toolbar">
  8 + <a href="#" permission="ctrl.article.permissions" permission-action="allow_edit" class="btn btn-default btn-xs edit-article" ui-sref="main.cmsEdit({profile: ctrl.profile.identifier, id: ctrl.article.id})">
  9 + <i class="fa fa-pencil-square-o fa-fw fa-lg"></i> {{"article.actions.edit" | translate}}
  10 + </a>
  11 + <a href="#" permission="ctrl.article.permissions" permission-action="allow_delete" class="btn btn-default btn-xs delete-article" ng-click="ctrl.delete()">
  12 + <i class="fa fa-trash-o fa-fw fa-lg" ng-click="ctrl.delete()"></i> {{"article.actions.delete" | translate}}
  13 + </a>
  14 + <noosfero-hotspot-article-toolbar [article]="ctrl.article"></noosfero-hotspot-article-toolbar>
  15 + </div>
14 <div class="page-info pull-right small text-muted"> 16 <div class="page-info pull-right small text-muted">
15 <span class="time"> 17 <span class="time">
16 <i class="fa fa-clock-o"></i> <span am-time-ago="ctrl.article.created_at | dateFormat"></span> 18 <i class="fa fa-clock-o"></i> <span am-time-ago="ctrl.article.created_at | dateFormat"></span>
src/app/article/content-viewer/navbar-actions.html
1 -<ul class="nav navbar-nav"> 1 +<ul class="nav navbar-nav" permission="vm.profile.permissions" permission-action="allow_edit">
2 <li class="dropdown profile-menu" uib-dropdown> 2 <li class="dropdown profile-menu" uib-dropdown>
3 <a class="btn dropdown-toggle" data-toggle="dropdown" uib-dropdown-toggle> 3 <a class="btn dropdown-toggle" data-toggle="dropdown" uib-dropdown-toggle>
4 {{"navbar.content_viewer_actions.new_item" | translate}} 4 {{"navbar.content_viewer_actions.new_item" | translate}}
@@ -19,4 +19,3 @@ @@ -19,4 +19,3 @@
19 </li> 19 </li>
20 20
21 </ul> 21 </ul>
22 -  
src/app/main/main.component.ts
@@ -39,7 +39,7 @@ import {SidebarComponent} from &quot;../layout/sidebar/sidebar.component&quot;; @@ -39,7 +39,7 @@ import {SidebarComponent} from &quot;../layout/sidebar/sidebar.component&quot;;
39 39
40 import {MainBlockComponent} from "../layout/blocks/main/main-block.component"; 40 import {MainBlockComponent} from "../layout/blocks/main/main-block.component";
41 import {HtmlEditorComponent} from "../shared/components/html-editor/html-editor.component"; 41 import {HtmlEditorComponent} from "../shared/components/html-editor/html-editor.component";
42 - 42 +import {PermissionDirective} from "../shared/components/permission/permission.directive";
43 43
44 /** 44 /**
45 * @ngdoc controller 45 * @ngdoc controller
@@ -100,7 +100,7 @@ export class EnvironmentContent { @@ -100,7 +100,7 @@ export class EnvironmentContent {
100 LinkListBlockComponent, CommunitiesBlockComponent, HtmlEditorComponent, 100 LinkListBlockComponent, CommunitiesBlockComponent, HtmlEditorComponent,
101 MainBlockComponent, RecentDocumentsBlockComponent, Navbar, SidebarComponent, ProfileImageBlockComponent, 101 MainBlockComponent, RecentDocumentsBlockComponent, Navbar, SidebarComponent, ProfileImageBlockComponent,
102 MembersBlockComponent, NoosferoTemplate, DateFormat, RawHTMLBlockComponent, StatisticsBlockComponent, 102 MembersBlockComponent, NoosferoTemplate, DateFormat, RawHTMLBlockComponent, StatisticsBlockComponent,
103 - LoginBlockComponent, CustomContentComponent 103 + LoginBlockComponent, CustomContentComponent, PermissionDirective
104 ].concat(plugins.mainComponents).concat(plugins.hotspots), 104 ].concat(plugins.mainComponents).concat(plugins.hotspots),
105 105
106 providers: [AuthService, SessionService, NotificationService, BodyStateClassesService] 106 providers: [AuthService, SessionService, NotificationService, BodyStateClassesService]
src/app/profile/custom-content/custom-content.component.spec.ts
@@ -73,5 +73,16 @@ describe(&quot;Components&quot;, () =&gt; { @@ -73,5 +73,16 @@ describe(&quot;Components&quot;, () =&gt; {
73 helper.component.save(); 73 helper.component.save();
74 expect(helper.component['notificationService'].success).toHaveBeenCalled(); 74 expect(helper.component['notificationService'].success).toHaveBeenCalled();
75 }); 75 });
  76 +
  77 + it("hide button to edit content when user doesn't have the permission", () => {
  78 + helper.detectChanges();
  79 + expect(helper.find(".actions").attr('style')).toEqual('display: none; ');
  80 + });
  81 +
  82 + it("show button to edit content when user has the permission", () => {
  83 + (<any>helper.component['profile'])['permissions'] = ['allow_edit'];
  84 + helper.detectChanges();
  85 + expect(helper.find(".actions").attr('style')).toEqual('');
  86 + });
76 }); 87 });
77 }); 88 });
src/app/profile/custom-content/custom-content.component.ts
1 import {Component, Input, Inject} from 'ng-forward'; 1 import {Component, Input, Inject} from 'ng-forward';
2 import {ProfileService} from '../../../lib/ng-noosfero-api/http/profile.service'; 2 import {ProfileService} from '../../../lib/ng-noosfero-api/http/profile.service';
3 import {NotificationService} from '../../shared/services/notification.service'; 3 import {NotificationService} from '../../shared/services/notification.service';
  4 +import {PermissionDirective} from '../../shared/components/permission/permission.directive';
4 5
5 @Component({ 6 @Component({
6 selector: 'custom-content', 7 selector: 'custom-content',
7 templateUrl: "app/profile/custom-content/custom-content.html", 8 templateUrl: "app/profile/custom-content/custom-content.html",
  9 + directives: [PermissionDirective]
8 }) 10 })
9 @Inject("$uibModal", "$scope", ProfileService, NotificationService) 11 @Inject("$uibModal", "$scope", ProfileService, NotificationService)
10 export class CustomContentComponent { 12 export class CustomContentComponent {
src/app/profile/custom-content/custom-content.html
1 <div class="custom-content"> 1 <div class="custom-content">
2 - <div class="actions"> 2 + <div class="actions" permission="ctrl.profile.permissions" permission-action="allow_edit">
3 <button type="submit" class="btn btn-xs btn-default" ng-click="ctrl.openEdit()"><i class="fa fa-edit fa-fw"></i> {{ctrl.label | translate}}</button> 3 <button type="submit" class="btn btn-xs btn-default" ng-click="ctrl.openEdit()"><i class="fa fa-edit fa-fw"></i> {{ctrl.label | translate}}</button>
4 </div> 4 </div>
5 <div class="content" ng-bind-html="ctrl.content"></div> 5 <div class="content" ng-bind-html="ctrl.content"></div>
src/app/shared/components/permission/permission.directive.spec.ts 0 → 100644
@@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
  1 +import {Input, provide, Component} from 'ng-forward';
  2 +import {PermissionDirective} from "./permission.directive";
  3 +import * as helpers from "../../../../spec/helpers";
  4 +
  5 +const htmlTemplate: string = '<div permission="ctrl.permissions" permission-action="action"></div>';
  6 +
  7 +describe("Permission directive", () => {
  8 +
  9 + let element = jasmine.createSpyObj("$element", ["css"]);
  10 + let scope = jasmine.createSpyObj("$scope", ["$watch", "$eval"]);
  11 + scope.$watch = (param: string, f: Function) => { f(); };
  12 + scope.$eval = (param: any) => { return param; };
  13 +
  14 + it("hide element when there is no permission action in the permissions array", (done: Function) => {
  15 + let attrs: any = { permission: [], permissionAction: 'action' };
  16 + let directive = new PermissionDirective(<any>attrs, scope, element);
  17 + expect(element.css).toHaveBeenCalledWith('display', 'none');
  18 + done();
  19 + });
  20 +
  21 + it("show element when the permission action exists in the permissions array", (done: Function) => {
  22 + let attrs = { permission: ['action'], permissionAction: 'action' };
  23 + let directive = new PermissionDirective(<any>attrs, scope, element);
  24 + expect(element.css).toHaveBeenCalledWith('display', '');
  25 + done();
  26 + });
  27 +});
src/app/shared/components/permission/permission.directive.ts 0 → 100644
@@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
  1 +import {Directive, Inject, Input} from "ng-forward";
  2 +
  3 +@Directive({
  4 + selector: '[permission]'
  5 +})
  6 +@Inject('$attrs', '$scope', '$element')
  7 +export class PermissionDirective {
  8 +
  9 + constructor($attrs: ng.IAttributes, $scope: ng.IScope, $element: any) {
  10 + $scope.$watch($attrs['permission'], () => {
  11 + let permissions = $scope.$eval($attrs['permission']);
  12 + let permissionAction = $attrs['permissionAction'];
  13 + if (!permissions || permissions.indexOf(permissionAction) < 0) {
  14 + $element.css("display", "none");
  15 + } else {
  16 + $element.css("display", "");
  17 + }
  18 + });
  19 + }
  20 +}