[ACA-4361] permission layout modified (#6937)

* * reusable data table column moved

* [ACA-4361] permission layout modified

* * build fixed

* fix build

* * import fixed

* * null safety operation

* * fixed comments

* * fix lint

* * wait for reload list

* * remove sleep

* * add sleep

* * fix comments

* * fix comments

* * floating promises fix

* * remove wait
This commit is contained in:
Dharan 2021-04-23 13:20:28 +05:30 committed by GitHub
parent 59dc6cb1d1
commit 0635b7fd06
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
66 changed files with 2803 additions and 684 deletions

View File

@ -33,6 +33,9 @@
<data-column type="text" key="users" title="Users"></data-column>
<data-column key="status" title="Status">
<ng-template let-value="value">{{value | json }}</ng-template>
<adf-data-column-header>
<ng-template>STATUS</ng-template>
</adf-data-column-header>
</data-column>
</data-columns>
-->

View File

@ -410,7 +410,7 @@
(execute)="onManageMetadata($event)">
</content-action>
<content-action
icon="settings_input_component"
icon="supervisor_account"
title="DOCUMENT_LIST.ACTIONS.PERMISSION"
permission="copy"
(error)="onContentActionError($event)"

View File

@ -1,18 +1,3 @@
<div class="app-inherit_permission_button">
<button mat-raised-button
[color]="toggleStatus?'accent':'primary'"
adf-inherit-permission [nodeId]="nodeId"
(error)="showErrorMessage($event)"
(updated)="onUpdatedPermissions($event)">
{{ (toggleStatus?'DEMO_PERMISSION.INHERITED_PERMISSIONS_BUTTON':'DEMO_PERMISSION.INHERIT_PERMISSION_BUTTON') | translate}}</button>
<button mat-button color="primary" (click)="openAddPermissionDialog()" data-automation-id="adf-add-permission-button">Add User or Group</button>
<div class="app-permission-section">
<adf-permission-list [nodeId]="nodeId"></adf-permission-list>
</div>
<div>
<adf-permission-list #permissionList
[nodeId]="nodeId"
(update)="reloadList()"
(error)="showErrorMessage($event)">
</adf-permission-list>
</div>

View File

@ -1,6 +1,4 @@
.app-inherit_permission_button {
padding-top: 20px;
display: flex;
justify-content: center;
padding-bottom: 20px;
.app-permission-section {
box-sizing: border-box;
height: 100%;
}

View File

@ -15,11 +15,8 @@
* limitations under the License.
*/
import { Component, Optional, OnInit, ViewChild } from '@angular/core';
import { Component, OnInit, Optional } from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router';
import { PermissionListComponent, NodePermissionDialogService } from '@alfresco/adf-content-services';
import { MinimalNodeEntryEntity } from '@alfresco/js-api';
import { NodesApiService, NotificationService } from '@alfresco/adf-core';
@Component({
selector: 'app-permissions',
@ -28,16 +25,9 @@ import { NodesApiService, NotificationService } from '@alfresco/adf-core';
})
export class DemoPermissionComponent implements OnInit {
@ViewChild('permissionList', { static: true })
displayPermissionComponent: PermissionListComponent;
nodeId: string;
toggleStatus = false;
constructor(@Optional() private route: ActivatedRoute,
private nodeService: NodesApiService,
private nodePermissionDialogService: NodePermissionDialogService,
private notificationService: NotificationService) {
constructor(@Optional() private route: ActivatedRoute) {
}
ngOnInit() {
@ -48,34 +38,5 @@ export class DemoPermissionComponent implements OnInit {
}
});
}
this.nodeService
.getNode(this.nodeId, {include: ['permissions'] })
.subscribe( (currentNode: MinimalNodeEntryEntity) => {
this.toggleStatus = currentNode.permissions?.isInheritanceEnabled ?? false;
});
}
onUpdatedPermissions(node: MinimalNodeEntryEntity) {
this.toggleStatus = node.permissions?.isInheritanceEnabled ?? false;
this.displayPermissionComponent.reload();
}
reloadList() {
this.displayPermissionComponent.reload();
}
openAddPermissionDialog() {
this.nodePermissionDialogService
.updateNodePermissionByDialog(this.nodeId)
.subscribe(
() => this.displayPermissionComponent.reload(),
(error) => this.showErrorMessage(error)
);
}
showErrorMessage(error) {
const message = error.message ? error.message : error;
this.notificationService.openSnackMessage(message);
}
}

View File

@ -2,7 +2,7 @@
Title: Permission List Component
Added: v2.3.0
Status: Active
Last reviewed: 2018-11-20
Last reviewed: 2021-4-17
---
# [Permission List Component](../../../lib/content-services/src/lib/permission-manager/components/permission-list/permission-list.component.ts "Defined in permission-list.component.ts")
@ -14,22 +14,9 @@ Shows node permissions as a table.
## Basic Usage
```html
<adf-permission-list [nodeId]="nodeId">
</adf-permission-list>
<adf-permission-list [nodeId]="nodeId"></adf-permission-list>
```
### [Transclusions](../../user-guide/transclusion.md)
When the list is empty, the contents will simply say "No permissions" by default,
but you can also supply your own content:
```html
<adf-permission-list [nodeId]="nodeId">
<adf-no-permission-template>
Custom no permission template!
</adf-no-permission-template>
</adf-permission-list>
```
## Class members

View File

@ -120,7 +120,7 @@ To disable the tooltip your function can return `null` or an empty string.
### Column Template
You can provide custom column/cell templates that may contain other Angular components or HTML elements:
You can provide custom column/cell header and templates that may contain other Angular components or HTML elements:
Every cell in the DataTable component is bound to the dynamic data context containing the following properties:
@ -185,6 +185,23 @@ In the Example below we will prepend `Hi!` to each file and folder name in the l
<!-- {% endraw %} -->
In the example below we will show capitalised custom name for a column:
<!-- {% raw %} -->
```html
<data-column title="Name" key="name" sortable="true" class="full-width ellipsis-cell">
<adf-data-column-header>
<ng-template let-entry="$implicit">
<span>NAME</span>
</ng-template>
</adf-data-column-header>
</data-column>
```
<!-- {% endraw %} -->
In the Example below we will integrate the [adf-tag-node-list](../../content-services/components/tag-node-list.component.md) component
within the document list.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 33 KiB

View File

@ -74,14 +74,13 @@ describe('Permissions Component', () => {
});
const groupBody = {
id: StringUtil.generateRandomString(),
id: `GROUP_${StringUtil.generateRandomString()}`,
displayName: StringUtil.generateRandomString()
};
const fileOwnerUser = new UserModel();
const filePermissionUser = new UserModel();
const duplicateUserPermissionMessage = 'One or more of the permissions you have set is already present : authority -> ' + filePermissionUser.username + ' / role -> Contributor';
const roleConsumerFolderModel = new FolderModel({ name: 'roleConsumer' + StringUtil.generateRandomString() });
const roleCoordinatorFolderModel = new FolderModel({ name: 'roleCoordinator' + StringUtil.generateRandomString() });
const roleCollaboratorFolderModel = new FolderModel({ name: 'roleCollaborator' + StringUtil.generateRandomString() });
@ -135,7 +134,7 @@ describe('Permissions Component', () => {
await contentList.rightClickOnRow(fileModel.name);
await contentServicesPage.pressContextMenuActionNamed('Permission');
await permissionsPage.addPermissionsDialog.checkPermissionContainerIsDisplayed();
await permissionsPage.checkPermissionManagerDisplayed();
});
afterEach(async () => {
@ -163,8 +162,11 @@ describe('Permissions Component', () => {
await permissionsPage.addPermissionsDialog.checkSearchUserInputIsDisplayed();
await permissionsPage.addPermissionsDialog.searchUserOrGroup(groupBody.id);
await permissionsPage.addPermissionsDialog.clickUserOrGroup(groupBody.displayName);
await permissionsPage.addPermissionsDialog.checkGroupIsAdded(groupBody.id);
await permissionsPage.addPermissionsDialog.selectRole(groupBody.displayName, 'Consumer');
await expect(await permissionsPage.addPermissionsDialog.addButtonIsEnabled()).toBe(true, 'button should be enabled');
await permissionsPage.addPermissionsDialog.clickAddButton();
await expect(await notificationPage.getSnackBarMessage()).toEqual('Added 0 user(s) 1 group(s)');
await permissionsPage.checkUserIsAdded(groupBody.id);
});
it('[C277100] Should display EVERYONE group in the search result set', async () => {
@ -179,6 +181,15 @@ describe('Permissions Component', () => {
await permissionsPage.addPermissionsDialog.checkResultListIsDisplayed();
await permissionsPage.addPermissionsDialog.checkUserOrGroupIsDisplayed('EVERYONE');
});
it('should be able to toggle the inherited permission', async () => {
await permissionsPage.checkPermissionListDisplayed();
await expect(await permissionsPage.isInherited()).toBe(true, 'Inherited permission should be on');
await permissionsPage.toggleInheritPermission();
await expect(await notificationPage.getSnackBarMessage()).toContain('Disabled inherited permission', 'Disabled notification not shown');
await notificationPage.waitForSnackBarToClose();
await expect(await permissionsPage.isInherited()).toBe(false, 'Inherited permission should be off');
});
});
describe('Changing and duplicate Permissions', () => {
@ -192,13 +203,19 @@ describe('Permissions Component', () => {
await contentServicesPage.checkSelectedSiteIsDisplayed('My files');
await contentList.rightClickOnRow(fileModel.name);
await contentServicesPage.pressContextMenuActionNamed('Permission');
await permissionsPage.checkPermissionManagerDisplayed();
await permissionsPage.addPermissionButton.waitVisible();
await permissionsPage.addPermissionsDialog.clickAddPermissionButton();
await permissionsPage.addPermissionsDialog.checkAddPermissionDialogIsDisplayed();
await permissionsPage.addPermissionsDialog.checkSearchUserInputIsDisplayed();
await permissionsPage.addPermissionsDialog.searchUserOrGroup(filePermissionUser.firstName);
await permissionsPage.addPermissionsDialog.clickUserOrGroup(filePermissionUser.firstName);
await permissionsPage.addPermissionsDialog.checkUserIsAdded(filePermissionUser.username);
await permissionsPage.addPermissionsDialog.selectRole(filePermissionUser.fullName, 'Contributor');
await expect(await permissionsPage.addPermissionsDialog.addButtonIsEnabled()).toBe(true, 'button should be enabled');
await permissionsPage.addPermissionsDialog.clickAddButton();
await expect(await notificationPage.getSnackBarMessage()).toEqual('Added 1 user(s) 0 group(s)');
await notificationPage.waitForSnackBarToClose();
await permissionsPage.checkUserIsAdded(filePermissionUser.username);
});
afterEach(async () => {
@ -207,8 +224,8 @@ describe('Permissions Component', () => {
});
it('[C274691] Should be able to add a new User with permission to the file and also change locally set permissions', async () => {
await expect(await permissionsPage.addPermissionsDialog.getRoleCellValue(filePermissionUser.username)).toEqual('Contributor');
await permissionsPage.addPermissionsDialog.clickRoleDropdownByUserOrGroupName(filePermissionUser.username);
await expect(await permissionsPage.getRoleCellValue(filePermissionUser.username)).toEqual('Contributor');
await permissionsPage.clickRoleDropdownByUserOrGroupName(filePermissionUser.username);
const roleDropdownOptions = permissionsPage.addPermissionsDialog.getRoleDropdownOptions();
await expect(await roleDropdownOptions.count()).toBe(5);
@ -220,16 +237,20 @@ describe('Permissions Component', () => {
await BrowserActions.closeMenuAndDialogs();
await permissionsPage.changePermission(filePermissionUser.username, 'Collaborator');
await expect(await permissionsPage.addPermissionsDialog.getRoleCellValue(filePermissionUser.username)).toEqual('Collaborator');
await notificationPage.waitForSnackBarToClose();
await expect(await permissionsPage.getRoleCellValue(filePermissionUser.username)).toEqual('Collaborator');
await permissionsPage.changePermission(filePermissionUser.username, 'Coordinator');
await expect(await permissionsPage.addPermissionsDialog.getRoleCellValue(filePermissionUser.username)).toEqual('Coordinator');
await notificationPage.waitForSnackBarToClose();
await expect(await permissionsPage.getRoleCellValue(filePermissionUser.username)).toEqual('Coordinator');
await permissionsPage.changePermission(filePermissionUser.username, 'Editor');
await expect(await permissionsPage.addPermissionsDialog.getRoleCellValue(filePermissionUser.username)).toEqual('Editor');
await notificationPage.waitForSnackBarToClose();
await expect(await permissionsPage.getRoleCellValue(filePermissionUser.username)).toEqual('Editor');
await permissionsPage.changePermission(filePermissionUser.username, 'Consumer');
await expect(await permissionsPage.addPermissionsDialog.getRoleCellValue(filePermissionUser.username)).toEqual('Consumer');
await notificationPage.waitForSnackBarToClose();
await expect(await permissionsPage.getRoleCellValue(filePermissionUser.username)).toEqual('Consumer');
});
it('[C276980] Should not be able to duplicate User or Group to the locally set permissions', async () => {
@ -239,15 +260,15 @@ describe('Permissions Component', () => {
await permissionsPage.addPermissionsDialog.checkSearchUserInputIsDisplayed();
await permissionsPage.addPermissionsDialog.searchUserOrGroup(filePermissionUser.firstName);
await permissionsPage.addPermissionsDialog.clickUserOrGroup(filePermissionUser.firstName);
await expect(await notificationPage.getSnackBarMessage()).toEqual(duplicateUserPermissionMessage);
await notificationHistoryPage.checkNotifyContains(duplicateUserPermissionMessage);
await expect(await permissionsPage.addPermissionsDialog.getRoleCellValue(filePermissionUser.fullName)).toEqual('Contributor');
await expect(await permissionsPage.addPermissionsDialog.addButtonIsEnabled()).toBe(false, 'button should not be enabled');
});
it('[C276982] Should be able to remove User or Group from the locally set permissions', async () => {
await expect(await permissionsPage.addPermissionsDialog.getRoleCellValue(filePermissionUser.username)).toEqual('Contributor');
await permissionsPage.addPermissionsDialog.clickDeletePermissionButton();
await permissionsPage.addPermissionsDialog.checkUserIsDeleted(filePermissionUser.username);
await expect(await permissionsPage.getRoleCellValue(filePermissionUser.username)).toEqual('Contributor');
await permissionsPage.clickDeletePermissionButton(filePermissionUser.username);
await permissionsPage.checkUserIsDeleted(filePermissionUser.username);
await expect(await notificationPage.getSnackBarMessage()).toEqual('User/Group deleted');
});
});
@ -376,13 +397,9 @@ describe('Permissions Component', () => {
await contentServicesPage.checkSelectedSiteIsDisplayed('My files');
await contentList.rightClickOnRow('RoleConsumer' + fileModel.name);
await contentServicesPage.pressContextMenuActionNamed('Permission');
await permissionsPage.addPermissionsDialog.checkPermissionInheritedButtonIsDisplayed();
await permissionsPage.addPermissionButton.waitVisible();
await permissionsPage.addPermissionsDialog.clickPermissionInheritedButton();
await expect(await notificationPage.getSnackBarMessage()).toEqual('You are not allowed to change permissions');
await permissionsPage.addPermissionsDialog.clickAddPermissionButton();
await expect(await notificationPage.getSnackBarMessage()).toEqual('You are not allowed to change permissions');
await notificationHistoryPage.checkNotifyContains('You are not allowed to change permissions');
await permissionsPage.checkPermissionManagerDisplayed();
await permissionsPage.errorElement.waitPresent();
await expect(await permissionsPage.noPermissionContent()).toContain('This item no longer exists or you don\'t have permission to view it.');
});
});
});

View File

@ -36,6 +36,7 @@ import { NavigationBarPage } from '../../core/pages/navigation-bar.page';
import { VersionManagePage } from '../../core/pages/version-manager.page';
import CONSTANTS = require('../../util/constants');
import { SitesApi } from '@alfresco/js-api';
import { NotificationDemoPage } from '../../core/pages/notification.page';
describe('Permissions Component', () => {
@ -49,6 +50,7 @@ describe('Permissions Component', () => {
const navigationBarPage = new NavigationBarPage();
const metadataViewPage = new MetadataViewPage();
const notificationHistoryPage = new NotificationHistoryPage();
const notificationPage = new NotificationDemoPage();
const uploadDialog = new UploadDialogPage();
const versionManagePage = new VersionManagePage();
@ -182,11 +184,9 @@ describe('Permissions Component', () => {
await contentServicesPage.pressContextMenuActionNamed('Permission');
await permissionsPage.addPermissionsDialog.checkPermissionInheritedButtonIsDisplayed();
await permissionsPage.checkPermissionManagerDisplayed();
await permissionsPage.addPermissionButton.waitVisible();
await browser.sleep(5000);
await permissionsPage.addPermissionsDialog.clickAddPermissionButton();
await permissionsPage.addPermissionsDialog.checkAddPermissionDialogIsDisplayed();
await permissionsPage.addPermissionsDialog.checkSearchUserInputIsDisplayed();
@ -194,11 +194,18 @@ describe('Permissions Component', () => {
await permissionsPage.addPermissionsDialog.searchUserOrGroup(consumerUser.username);
await permissionsPage.addPermissionsDialog.clickUserOrGroup(consumerUser.firstName);
await permissionsPage.addPermissionsDialog.checkUserIsAdded(consumerUser.username);
await permissionsPage.addPermissionsDialog.selectRole(consumerUser.fullName, 'Site Collaborator');
await expect(await permissionsPage.addPermissionsDialog.getRoleCellValue(consumerUser.fullName)).toEqual('Site Collaborator');
await expect(await permissionsPage.addPermissionsDialog.addButtonIsEnabled()).toBe(true, 'Add button should be enabled');
await permissionsPage.addPermissionsDialog.clickAddButton();
await expect(await notificationPage.getSnackBarMessage()).toEqual('Added 1 user(s) 0 group(s)');
await notificationPage.waitForSnackBarToClose();
await expect(await permissionsPage.addPermissionsDialog.getRoleCellValue(consumerUser.username)).toEqual(CONSTANTS.CS_USER_ROLES_I18N.COLLABORATOR);
await permissionsPage.checkUserIsAdded(consumerUser.username);
await permissionsPage.addPermissionsDialog.clickRoleDropdownByUserOrGroupName(consumerUser.username);
await expect(await permissionsPage.getRoleCellValue(consumerUser.username)).toEqual(CONSTANTS.CS_USER_ROLES_I18N.COLLABORATOR);
await permissionsPage.clickRoleDropdownByUserOrGroupName(consumerUser.username);
const roleDropdownOptions = permissionsPage.addPermissionsDialog.getRoleDropdownOptions();

View File

@ -15,29 +15,84 @@
* limitations under the License.
*/
import { DataTableComponentPage, AddPermissionsDialogPage, TestElement } from '@alfresco/adf-testing';
import { browser, by, element } from 'protractor';
import {
AddPermissionsDialogPage,
BrowserActions,
DataTableComponentPage,
DropdownPage,
TestElement
} from '@alfresco/adf-testing';
import { browser, by } from 'protractor';
export class PermissionsPage {
dataTableComponentPage = new DataTableComponentPage();
addPermissionsDialog = new AddPermissionsDialogPage();
rootElement = 'adf-permission-manager-card';
inheritedButton = '[data-automation-id="adf-inherit-toggle-button"]';
errorElement = TestElement.byId('adf-permission-manager-error');
localPermissionList = TestElement.byCss('[data-automation-id="adf-locally-set-permission"]');
addPermissionButton = TestElement.byCss("button[data-automation-id='adf-add-permission-button']");
addPermissionDialog = element(by.css('adf-add-permission-dialog'));
searchUserInput = element(by.id('searchInput'));
searchResults = element(by.css('#adf-add-permission-authority-results #adf-search-results-content'));
addButton = element(by.id('add-permission-dialog-confirm-button'));
permissionInheritedButton = element.all(by.css('.app-inherit_permission_button button')).first();
noPermissions = element(by.id('adf-no-permissions-template'));
deletePermissionButton = element(by.css(`button[data-automation-id='adf-delete-permission-button']`));
permissionDisplayContainer = element(by.id('adf-permission-display-container'));
closeButton = TestElement.byCss('#add-permission-dialog-close-button');
async changePermission(name: string, role: string): Promise<void> {
await this.addPermissionsDialog.clickRoleDropdownByUserOrGroupName(name);
await this.addPermissionsDialog.selectOption(role);
await browser.sleep(1000);
await this.clickRoleDropdownByUserOrGroupName(name);
await new DropdownPage().selectOption(role);
await this.dataTableComponentPage.checkRowByContentIsNotSelected(name);
}
async checkUserIsAdded(id: string) {
const userOrGroupName = TestElement.byCss('div[data-automation-id="' + id + '"]');
await userOrGroupName.waitPresent();
}
async getRoleCellValue(username: string): Promise<string> {
const locator = this.dataTableComponentPage.getCellByRowContentAndColumn('Users and Groups', username, 'Role');
return BrowserActions.getText(locator);
}
async clickRoleDropdownByUserOrGroupName(name: string): Promise<void> {
const row = this.dataTableComponentPage.getRow('Users and Groups', name);
await row.click();
await BrowserActions.click(row.element(by.css('[id="adf-select-role-permission"] .mat-select-trigger')));
await TestElement.byCss('.mat-select-panel').waitVisible();
}
async clickDeletePermissionButton(username: string): Promise<void> {
const userOrGroupName = TestElement.byCss(`[data-automation-id="adf-delete-permission-button-${username}"]`);
await userOrGroupName.waitPresent();
await userOrGroupName.click();
}
async checkUserIsDeleted(username: string): Promise<void> {
const userOrGroupName = TestElement.byCss('div[data-automation-id="' + username + '"]');
await userOrGroupName.waitNotPresent();
}
async noPermissionContent(): Promise<string> {
const noPermission = TestElement.byCss('.adf-no-permission__template--text');
return noPermission.getText();
}
async checkPermissionManagerDisplayed(): Promise<void> {
await TestElement.byId(this.rootElement).waitVisible();
}
async checkPermissionListDisplayed(): Promise<void> {
await browser.sleep(500);
await this.dataTableComponentPage.checkRowIsNotSelected('Authority ID', name);
await this.localPermissionList.waitVisible();
}
async isInherited(): Promise<boolean> {
const inheritButton = TestElement.byCss(this.inheritedButton);
await inheritButton.waitVisible();
const appliedStyles = await inheritButton.getAttribute('class');
return appliedStyles.indexOf('mat-checked') !== -1;
}
async toggleInheritPermission(): Promise<void> {
const inheritButton = TestElement.byCss(`${this.inheritedButton} label`);
await inheritButton.click();
}
}

View File

@ -35,7 +35,7 @@ async function checkEnv() {
await alfrescoJsApi.login(program.username, program.password);
} catch (error) {
if (error?.error.code === 'ETIMEDOUT') {
if (error?.error?.code === 'ETIMEDOUT') {
logger.error('The env is not reachable. Terminating');
process.exit(1);
}

View File

@ -17,10 +17,8 @@
* limitations under the License.
*/
import * as docker from './docker';
export default function (args: any) {
docker.default(args);
}

View File

@ -115,8 +115,8 @@ function main(args) {
process.exit(1);
}
if(args.pathProject === undefined) {
args.pathProject = resolve('./')
if (args.pathProject === undefined) {
args.pathProject = resolve('./');
}
if (args.loginCheck === true) {

View File

@ -17,3 +17,4 @@
export * from './content-directive.module';
export * from './node-lock.directive';
export * from './node-counter.directive';

View File

@ -385,7 +385,8 @@
"NO_PERMISSIONS": "No permissions"
},
"ADD-PERMISSION": {
"SEARCH": "Search",
"SEARCH": "Search for users or groups to add",
"TITLE": "Add user or group to",
"TYPE-MESSAGE": "Type something to start searching groups or people",
"NO-RESULT": "No result found for this search",
"ADD-ACTION": "ADD",
@ -393,7 +394,41 @@
"BASE-DIALOG-TITLE": "Search a group or people to add...",
"EVERYONE": "EVERYONE"
},
"COLUMN": {
"NAME": "Users and Groups ({{ count }})",
"LOCATION": "Location",
"BULK-ROLE": "Set all role to"
},
"LABELS": {
"ON": "On",
"OFF": "Off",
"DIRECT-PERMISSIONS": "Direct Applied Permission",
"INHERITED-PERMISSIONS": "Inherited Permission",
"INHERITED-SUBTITLE": "{{count}} users or groups are inheriting permission from a parent folder",
"SELECT-ROLE": "Select role"
},
"MESSAGE": {
"EMPTY-PERMISSION": "No users/groups",
"EMPTY-SUBTITLE": "Add user/group to manage permission.",
"NO-MEMBERS": "Add groups or people to manage roles",
"PERMISSION-BULK-UPDATE-SUCCESS": "Updated {{user}} user(s) {{group}} group(s)",
"PERMISSION-UPDATE-SUCCESS": "User/Group updated",
"PERMISSION-UPDATE-FAIL": "Failed to update user/group",
"PERMISSION-BULK-DELETE-SUCCESS": "Deleted {{user}} user(s_ {{group}} group(s)",
"PERMISSION-DELETE-SUCCESS": "User/Group deleted",
"PERMISSION-DELETE-FAIL": "Failed to delete user/group",
"PERMISSION-ADD-SUCCESS": "Added {{user}} user(s) {{group}} group(s)",
"PERMISSION-ADD-FAIL": "Failed to add user/group",
"INHERIT-ENABLE-SUCCESS": "Enabled inherited permission",
"INHERIT-DISABLE-SUCCESS": "Disabled inherited permission",
"TOGGLE-PERMISSION-FAILED": "Failed to toggle inherit Permission"
},
"ACTION": {
"DELETE": "Delete",
"ADD-PERMISSION": "Add User or Group"
},
"ERROR": {
"NOT-FOUND": "This item no longer exists or you don't have permission to view it.",
"DUPLICATE-PERMISSION": "One or more of the permissions you have set is already present : {{list}}",
"NOT-ALLOWED": "You are not allowed to change permissions"
}

View File

@ -100,7 +100,86 @@ export const fakeNodeWithPermissions: any = {
}
};
export const fakeNodeInheritedOnly: any = {
export const fakeNodeInheritedOnly = {
'allowableOperations': [ 'updatePermissions' ],
'aspectNames': [
'cm:auditable',
'cm:taggable',
'cm:author',
'cm:titled',
'app:uifacets'
],
'createdAt': '2017-11-16T16:29:38.638+0000',
'path': {
'name': '/Company Home/Sites/testsite/documentLibrary',
'isComplete': true,
'elements': [
{
'id': '2be275a1-b00d-4e45-83d8-66af43ac2252',
'name': 'Company Home'
},
{
'id': '1be10a97-6eb9-4b60-b6c6-1673900e9631',
'name': 'Sites'
},
{
'id': 'e002c740-b8f9-482a-a554-8fff4e4c9dc0',
'name': 'testsite'
},
{
'id': '71626fae-0c04-4d0c-a129-20fa4c178716',
'name': 'documentLibrary'
}
]
},
'isFolder': true,
'isFile': false,
'createdByUser': {
'id': 'System',
'displayName': 'System'
},
'modifiedAt': '2018-03-21T03:17:58.783+0000',
'permissions': {
'inherited': [
{
'authorityId': 'guest',
'name': 'Read',
'accessStatus': 'ALLOWED'
},
{
'authorityId': 'GROUP_EVERYONE',
'name': 'Read',
'accessStatus': 'ALLOWED'
}
],
'settable': [
'Contributor',
'Collaborator',
'Coordinator',
'Editor',
'Consumer'
],
'isInheritanceEnabled': true
},
'modifiedByUser': {
'id': 'admin',
'displayName': 'PedroH Hernandez'
},
'name': 'test',
'id': 'f472543f-7218-403d-917b-7a5861257244',
'nodeType': 'cm:folder',
'properties': {
'cm:title': 'test',
'cm:author': 'yagud',
'cm:taggable': [
'e8c8fbba-03ba-4fa6-86b1-f7ad7c296409'
],
'cm:description': 'sleepery',
'app:icon': 'space-icon-default'
}
};
export const fakeReadOnlyNodeInherited = {
'aspectNames': [
'cm:auditable',
'cm:taggable',
@ -231,7 +310,7 @@ export const fakeNodeWithOnlyLocally: any = {
'Editor',
'Consumer'
],
'isInheritanceEnabled': true
'isInheritanceEnabled': false
},
'modifiedByUser': {
'id': 'admin',

View File

@ -15,11 +15,13 @@
* limitations under the License.
*/
import { NodeEntry } from '@alfresco/js-api';
import { Node, PermissionElement } from '@alfresco/js-api';
import { Subject } from 'rxjs';
import { RoleModel } from '../../models/role.model';
export interface AddPermissionDialogData {
title?: string;
nodeId: string;
confirm: Subject<NodeEntry[]>;
node: Node;
roles: RoleModel[];
confirm: Subject<PermissionElement[]>;
}

View File

@ -1,15 +1,109 @@
<h2 mat-dialog-title id="add-permission-dialog-title">
{{(data?.title ? data?.title : 'PERMISSION_MANAGER.ADD-PERMISSION.BASE-DIALOG-TITLE') | translate}}
{{ (data?.title ? data?.title : "PERMISSION_MANAGER.ADD-PERMISSION.BASE-DIALOG-TITLE") | translate }}
</h2>
<mat-dialog-content>
<adf-add-permission-panel
(select)="onSelect($event)">
</adf-add-permission-panel>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button mat-dialog-close id="add-permission-dialog-close-button">{{'PERMISSION_MANAGER.ADD-PERMISSION.CLOSE-ACTION' | translate}}</button>
<button mat-button id="add-permission-dialog-confirm-button" [mat-dialog-close]="true"
class="adf-choose-action"
[disabled]="currentSelection?.length === 0"
(click)="onAddClicked()">{{'PERMISSION_MANAGER.ADD-PERMISSION.ADD-ACTION' | translate}}</button>
</mat-dialog-actions>
<ng-container *ngIf="!isSearchActive">
<mat-dialog-content>
<button mat-button (click)="enableSearch()" class="adf-search-user-button">
{{ "PERMISSION_MANAGER.ADD-PERMISSION.SEARCH" | translate }}
<span class="adf-toolbar--spacer"></span>
<mat-icon>search</mat-icon>
</button>
<adf-datatable [rows]="selectedMembers"
selectionMode="none"
stickyHeader="true"
data-automation-id="adf-user-role-selection-table"
*ngIf="selectedMembers.length">
<data-columns>
<data-column class="adf-key-icon" key="icon" type="icon" [sortable]="false">
<ng-template let-context>
<adf-user-icon-column [context]="context"></adf-user-icon-column>
</ng-template>
</data-column>
<data-column class="adf-authorityId-label adf-ellipsis-cell adf-expand-cell-2"
[title]="'Users and Groups (' + selectedMembers.length + ')'"
key="id">
<ng-template let-context>
<adf-user-name-column [context]="context"></adf-user-name-column>
</ng-template>
</data-column>
<data-column class="adf-ellipsis-cell adf-expand-cell-1"
title="PERMISSION_MANAGER.PERMISSION_DISPLAY.ROLE"
key="role">
<ng-template let-entry="$implicit">
<adf-user-role-column [readonly]="entry.row.obj.readonly"
[placeholder]="entry.data.getValue(entry.row, entry.col)"
[value]="entry.data.getValue(entry.row, entry.col)"
[roles]="data.roles"
id="adf-select-role-permission"
(roleChanged)="onMemberUpdate($event, entry.row.obj)">
</adf-user-role-column>
</ng-template>
<adf-data-column-header>
<ng-template>
<adf-user-role-column class="adf-permission-role-column-header"
placeholder="PERMISSION_MANAGER.COLUMN.BULK-ROLE"
[roles]="data.roles"
id="adf-bulk-select-role-permission"
(roleChanged)="onBulkUpdate($event)">
</adf-user-role-column>
</ng-template>
</adf-data-column-header>
</data-column>
<data-column class="adf-delete-permission" key="" sortable="false">
<ng-template let-entry="$implicit">
<button mat-icon-button
class="adf-add-member-action"
[style.display]="entry.row.obj.readonly ? 'none': 'block'"
(click)="onMemberDelete(entry.row.obj)"
data-automation-id="adf-delete-permission-button">
<mat-icon>highlight_off</mat-icon>
</button>
</ng-template>
</data-column>
</data-columns>
</adf-datatable>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button
mat-dialog-close
data-automation-id="add-permission-dialog-close-button">
{{ "PERMISSION_MANAGER.ADD-PERMISSION.CLOSE-ACTION" | translate }}
</button>
<button mat-button
data-automation-id="add-permission-dialog-confirm-button"
[mat-dialog-close]="true"
class="adf-choose-action"
[disabled]="!isValid()"
(click)="onAddClicked()">
{{ "PERMISSION_MANAGER.ADD-PERMISSION.ADD-ACTION" | translate }}
</button>
</mat-dialog-actions>
</ng-container>
<ng-container *ngIf="isSearchActive">
<mat-dialog-content>
<adf-add-permission-panel (select)="onSelect($event)"></adf-add-permission-panel>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button
(click)="canCloseDialog()"
data-automation-id="add-permission-dialog-close-button">
{{ "PERMISSION_MANAGER.ADD-PERMISSION.CLOSE-ACTION" | translate }}
</button>
<button mat-button
data-automation-id="add-permission-dialog-confirm-button"
[disabled]="!currentSelection.length"
(click)="onSearchAddClicked()">
{{ "PERMISSION_MANAGER.ADD-PERMISSION.ADD-ACTION" | translate }}
</button>
</mat-dialog-actions>
</ng-container>

View File

@ -25,7 +25,7 @@
.mat-dialog-content {
margin: 0;
overflow: hidden;
overflow: auto;
}
.mat-dialog-actions {
@ -51,5 +51,19 @@
}
}
}
.adf {
&-search-user-button {
width: 100%;
.mat-button-wrapper {
display: flex;
align-items: center;
}
}
&-add-member-action {
padding: 0 15px;
}
}
}
}

View File

@ -15,28 +15,46 @@
* limitations under the License.
*/
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { ContentTestingModule } from '../../../testing/content.testing.module';
import { By } from '@angular/platform-browser';
import { setupTestBed } from '@alfresco/adf-core';
import { AddPermissionDialogComponent } from './add-permission-dialog.component';
import { NodeEntry } from '@alfresco/js-api';
import { Node, PermissionElement } from '@alfresco/js-api';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { TranslateModule } from '@ngx-translate/core';
import { Subject } from 'rxjs';
import { AddPermissionPanelComponent } from './add-permission-panel.component';
import { ContentTestingModule } from '../../../testing/content.testing.module';
import { AddPermissionDialogComponent } from './add-permission-dialog.component';
import { AddPermissionDialogData } from './add-permission-dialog-data.interface';
import { fakeAuthorityResults } from '../../../mock/add-permission.component.mock';
import { AddPermissionPanelComponent } from './add-permission-panel.component';
import { TranslateModule } from '@ngx-translate/core';
describe('AddPermissionDialog', () => {
let fixture: ComponentFixture<AddPermissionDialogComponent>;
let component: AddPermissionDialogComponent;
let element: HTMLElement;
const data: AddPermissionDialogData = {
title: 'dead or alive you are coming with me',
nodeId: 'fake-node-id',
confirm: new Subject<NodeEntry[]> ()
node: {
id: 'fake-node-id',
aspectNames: [],
isFile: true,
name: 'fake-node.pdf',
permissions: {
locallySet: []
}
} as Node,
roles: [
{
label: 'test',
role: 'Test'
},
{
label: 'consumer',
role: 'Consumer'
}
],
confirm: new Subject<PermissionElement[]> ()
};
const dialogRef = {
close: jasmine.createSpy('close')
@ -50,13 +68,12 @@ describe('AddPermissionDialog', () => {
providers: [
{ provide: MatDialogRef, useValue: dialogRef },
{ provide: MAT_DIALOG_DATA, useValue: data }
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
]
});
beforeEach(() => {
fixture = TestBed.createComponent(AddPermissionDialogComponent);
component = fixture.componentInstance;
element = fixture.nativeElement;
fixture.detectChanges();
});
@ -72,44 +89,139 @@ describe('AddPermissionDialog', () => {
});
it('should close the dialog when close button is clicked', () => {
const closeButton: HTMLButtonElement = <HTMLButtonElement> element.querySelector('#add-permission-dialog-close-button');
const closeButton: HTMLButtonElement = <HTMLButtonElement> element.querySelector('[data-automation-id="add-permission-dialog-close-button"]');
expect(closeButton).not.toBeNull();
closeButton.click();
expect(dialogRef.close).toHaveBeenCalled();
});
it('should disable the confirm button when no selection is applied', () => {
const confirmButton: HTMLButtonElement = <HTMLButtonElement> element.querySelector('#add-permission-dialog-confirm-button');
const confirmButton: HTMLButtonElement = <HTMLButtonElement> element.querySelector('[data-automation-id="add-permission-dialog-confirm-button"]');
expect(confirmButton.disabled).toBeTruthy();
});
it('should enable the button when a selection is done', async(() => {
it('should enable the button when a selection is done', async() => {
const addPermissionPanelComponent: AddPermissionPanelComponent = fixture.debugElement.query(By.directive(AddPermissionPanelComponent)).componentInstance;
addPermissionPanelComponent.select.emit(fakeAuthorityResults);
let confirmButton: HTMLButtonElement = <HTMLButtonElement> element.querySelector('#add-permission-dialog-confirm-button');
let confirmButton: HTMLButtonElement = <HTMLButtonElement> element.querySelector('[data-automation-id="add-permission-dialog-confirm-button"]');
expect(confirmButton.disabled).toBeTruthy();
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
confirmButton = <HTMLButtonElement> element.querySelector('#add-permission-dialog-confirm-button');
expect(confirmButton.disabled).toBeFalsy();
});
}));
await fixture.detectChanges();
confirmButton = <HTMLButtonElement> element.querySelector('[data-automation-id="add-permission-dialog-confirm-button"]');
expect(confirmButton.disabled).toBe(false);
});
it('should stream the confirmed selection on the confirm subject', async(() => {
it('should update the role after selection', async (done) => {
spyOn(component, 'onMemberUpdate').and.callThrough();
const addPermissionPanelComponent: AddPermissionPanelComponent = fixture.debugElement.query(By.directive(AddPermissionPanelComponent)).componentInstance;
let confirmButton = <HTMLButtonElement> element.querySelector('[data-automation-id="add-permission-dialog-confirm-button"]');
expect(confirmButton.disabled).toBe(true);
addPermissionPanelComponent.select.emit([fakeAuthorityResults[0]]);
await fixture.detectChanges();
expect(confirmButton.disabled).toBe(false);
confirmButton.click();
await fixture.detectChanges();
const selectBox = fixture.debugElement.query(By.css(('[id="adf-select-role-permission"] .mat-select-trigger')));
selectBox.triggerEventHandler('click', null);
fixture.detectChanges();
const options = fixture.debugElement.queryAll(By.css('mat-option'));
expect(options).not.toBeNull();
expect(options.length).toBe(2);
options[0].triggerEventHandler('click', {});
await fixture.detectChanges();
expect(component.onMemberUpdate).toHaveBeenCalled();
data.confirm.subscribe((selection) => {
expect(selection.length).toBe(1);
done();
});
confirmButton = <HTMLButtonElement> element.querySelector('[data-automation-id="add-permission-dialog-confirm-button"]');
expect(confirmButton.disabled).toBe(false);
confirmButton.click();
});
it('should update all the user role on header column update', async () => {
spyOn(component, 'onBulkUpdate').and.callThrough();
const addPermissionPanelComponent: AddPermissionPanelComponent = fixture.debugElement.query(By.directive(AddPermissionPanelComponent)).componentInstance;
let confirmButton = <HTMLButtonElement> element.querySelector('[data-automation-id="add-permission-dialog-confirm-button"]');
expect(confirmButton.disabled).toBe(true);
addPermissionPanelComponent.select.emit(fakeAuthorityResults);
await fixture.detectChanges();
expect(confirmButton.disabled).toBe(false);
confirmButton.click();
await fixture.detectChanges();
const selectBox = fixture.debugElement.query(By.css(('[id="adf-bulk-select-role-permission"] .mat-select-trigger')));
selectBox.triggerEventHandler('click', null);
fixture.detectChanges();
const options = fixture.debugElement.queryAll(By.css('mat-option'));
expect(options).not.toBeNull();
expect(options.length).toBe(2);
options[0].triggerEventHandler('click', {});
await fixture.detectChanges();
expect(component.onBulkUpdate).toHaveBeenCalled();
data.confirm.subscribe((selection) => {
expect(selection.length).toBe(3);
});
confirmButton = <HTMLButtonElement> element.querySelector('[data-automation-id="add-permission-dialog-confirm-button"]');
expect(confirmButton.disabled).toBe(false);
confirmButton.click();
});
it('should delete the user after selection', async () => {
spyOn(component, 'onMemberUpdate').and.callThrough();
spyOn(component, 'onMemberDelete').and.callThrough();
const addPermissionPanelComponent: AddPermissionPanelComponent = fixture.debugElement.query(By.directive(AddPermissionPanelComponent)).componentInstance;
let confirmButton = <HTMLButtonElement> element.querySelector('[data-automation-id="add-permission-dialog-confirm-button"]');
expect(confirmButton.disabled).toBe(true);
addPermissionPanelComponent.select.emit(fakeAuthorityResults);
await fixture.detectChanges();
expect(confirmButton.disabled).toBe(false);
confirmButton.click();
await fixture.detectChanges();
const selectBox = fixture.debugElement.query(By.css(('[id="adf-select-role-permission"] .mat-select-trigger')));
selectBox.triggerEventHandler('click', null);
fixture.detectChanges();
const options = fixture.debugElement.queryAll(By.css('mat-option'));
expect(options).not.toBeNull();
expect(options.length).toBe(2);
options[0].triggerEventHandler('click', {});
await fixture.detectChanges();
expect(component.onMemberUpdate).toHaveBeenCalled();
confirmButton = <HTMLButtonElement> element.querySelector('[data-automation-id="add-permission-dialog-confirm-button"]');
expect(confirmButton.disabled).toBe(true);
const deleteButton = element.querySelectorAll('[data-automation-id="adf-delete-permission-button"]') as any;
deleteButton[1].click();
deleteButton[2].click();
await fixture.detectChanges();
expect(confirmButton.disabled).toBe(false);
expect(component.onMemberDelete).toHaveBeenCalled();
data.confirm.subscribe((selection) => {
expect(selection.length).toBe(1);
});
confirmButton.click();
});
it('should stream the confirmed selection on the confirm subject', async() => {
const addPermissionPanelComponent: AddPermissionPanelComponent = fixture.debugElement.query(By.directive(AddPermissionPanelComponent)).componentInstance;
addPermissionPanelComponent.select.emit(fakeAuthorityResults);
data.confirm.subscribe((selection) => {
expect(selection[0]).not.toBeNull();
expect(selection[0].entry.id).not.toBeNull();
expect(fakeAuthorityResults[0].entry.id).toBe(selection[0].entry.id);
expect(fakeAuthorityResults[0].entry.id).toBe(selection[0].authorityId);
});
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
const confirmButton = <HTMLButtonElement> element.querySelector('#add-permission-dialog-confirm-button');
confirmButton.click();
});
}));
await fixture.detectChanges();
const confirmButton = <HTMLButtonElement> element.querySelector('[data-automation-id="add-permission-dialog-confirm-button"]');
confirmButton.click();
});
});

View File

@ -15,11 +15,11 @@
* limitations under the License.
*/
import { Component, ViewEncapsulation, Inject, ViewChild } from '@angular/core';
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
import { NodeEntry } from '@alfresco/js-api';
import { Component, Inject, ViewEncapsulation } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { NodeEntry, PermissionElement } from '@alfresco/js-api';
import { AddPermissionDialogData } from './add-permission-dialog-data.interface';
import { AddPermissionComponent } from '../add-permission/add-permission.component';
import { MemberModel } from '../../models/member.model';
@Component({
selector: 'adf-add-permission-dialog',
@ -28,13 +28,15 @@ import { AddPermissionComponent } from '../add-permission/add-permission.compone
encapsulation: ViewEncapsulation.None
})
export class AddPermissionDialogComponent {
isSearchActive = true;
selectedMembers: MemberModel[] = [];
@ViewChild('addPermission')
addPermissionComponent: AddPermissionComponent;
private existingMembers: PermissionElement[] = [];
currentSelection: NodeEntry[] = [];
constructor(@Inject(MAT_DIALOG_DATA) public data: AddPermissionDialogData) {
constructor(@Inject(MAT_DIALOG_DATA) public data: AddPermissionDialogData,
private dialogRef: MatDialogRef<AddPermissionDialogComponent>) {
this.existingMembers = this.data.node.permissions.locallySet || [];
}
onSelect(items: NodeEntry[]) {
@ -42,7 +44,63 @@ export class AddPermissionDialogComponent {
}
onAddClicked() {
this.data.confirm.next(this.currentSelection);
const selection = this.selectedMembers.filter(member => !member.readonly).map(member => member.toPermissionElement());
this.data.confirm.next(selection);
this.data.confirm.complete();
}
onSearchAddClicked() {
const newMembers = this.currentSelection.map(item => MemberModel.parseFromSearchResult(item))
.filter(({id}) => !this.selectedMembers.find((member) => member.id === id));
this.selectedMembers = this.selectedMembers.concat(newMembers);
this.selectedMembers.forEach((member) => {
const existingMember = this.existingMembers.find(({authorityId}) => authorityId === member.id);
if (!!existingMember) {
member.role = existingMember.name;
member.accessStatus = existingMember.accessStatus;
member.readonly = true; // make role non editable
}
});
this.disableSearch();
}
canCloseDialog() {
if (!!this.selectedMembers.length) {
this.disableSearch();
} else {
this.dialogRef.close();
}
}
enableSearch() {
this.isSearchActive = true;
}
disableSearch() {
this.isSearchActive = false;
}
onBulkUpdate(role: string) {
this.selectedMembers.filter(member => !member.readonly)
.forEach(member => (member.role = role));
}
onMemberDelete({ id }: MemberModel) {
const index = this.selectedMembers.findIndex((member) => member.id === id);
this.selectedMembers.splice(index, 1);
if (this.selectedMembers.length === 0) {
this.enableSearch();
this.currentSelection = [];
}
}
onMemberUpdate(role: string, member: MemberModel) {
const _member = this.selectedMembers.find(({ id }) => id === member.id);
_member.role = role;
}
isValid(): boolean {
return this.selectedMembers.filter(({readonly}) => !readonly).length && this.selectedMembers.every(({role}) => !!role);
}
}

View File

@ -33,15 +33,12 @@
class="adf-permission-result-list"
[class.adf-permission-result-list-search]="searchedWord.length === 0">
<ng-template let-data>
<mat-selection-list class="adf-permission-result-list-elements">
<mat-selection-list class="adf-permission-result-list-elements" (keydown.control.a)="selectAll( data?.list?.entries)">
<mat-list-option id="adf-add-permission-group-everyone"
class="adf-list-option-item"
(click)="elementClicked(EVERYONE)">
<mat-icon mat-list-icon
id="add-group-icon">
group_add
</mat-icon>
<p>
<adf-user-icon-column [node]="EVERYONE" id="add-group-icon"></adf-user-icon-column>
<p class="adf-result-name">
{{'PERMISSION_MANAGER.ADD-PERMISSION.EVERYONE' | translate}}
</p>
</mat-list-option>
@ -50,26 +47,19 @@
(click)="elementClicked(item)"
class="adf-list-option-item"
id="result_option_{{idx}}">
<mat-icon mat-list-icon
id="add-group-icon"
*ngIf="item?.entry?.nodeType === 'cm:authorityContainer' else show_person_icon">
group_add
</mat-icon>
<ng-template #show_person_icon>
<mat-icon id="add-person-icon"
mat-list-icon>person_add</mat-icon>
</ng-template>
<p>
<adf-user-icon-column [node]="item"></adf-user-icon-column>
<p class="adf-result-name">
<ng-container *ngIf="item.entry?.properties['cm:authorityDisplayName']; else authorityName">
{{item.entry?.properties['cm:authorityDisplayName']}}
{{item.entry.properties['cm:authorityDisplayName']}}
</ng-container>
<ng-template #authorityName>
<ng-container *ngIf="item.entry?.properties['cm:authorityName']; else owner">
{{item.entry?.properties['cm:authorityName']}}
{{item.entry.properties['cm:authorityName']}}
</ng-container>
</ng-template>
<ng-template #owner>
{{item.entry?.properties['cm:owner']?.displayName}}
{{item.entry?.properties['cm:firstName'] ? item.entry?.properties['cm:firstName'] : '' }}
{{item.entry?.properties['cm:lastName'] ? item.entry?.properties['cm:lastName']: ''}}
</ng-template>
</p>
</mat-list-option>

View File

@ -58,6 +58,10 @@
display: flex;
flex-direction: row !important;
align-items: center;
.adf-result-name {
padding-left: 16px !important;
}
}
&-permission-action {

View File

@ -15,13 +15,13 @@
* limitations under the License.
*/
import { Component, ViewEncapsulation, EventEmitter, Output, ViewChild } from '@angular/core';
import { SearchPermissionConfigurationService } from './search-config-permission.service';
import { SearchService, SearchConfigurationService } from '@alfresco/adf-core';
import { SearchComponent } from '../../../search/components/search.component';
import { NodeEntry } from '@alfresco/js-api';
import { Component, ViewEncapsulation, EventEmitter, Output, ViewChild } from '@angular/core';
import { FormControl } from '@angular/forms';
import { debounceTime } from 'rxjs/operators';
import { NodeEntry } from '@alfresco/js-api';
import { SearchPermissionConfigurationService } from './search-config-permission.service';
import { SearchComponent } from '../../../search/components/search.component';
@Component({
selector: 'adf-add-permission-panel',
@ -48,7 +48,7 @@ export class AddPermissionPanelComponent {
selectedItems: NodeEntry[] = [];
EVERYONE: NodeEntry = new NodeEntry({ entry: { properties: {'cm:authorityName': 'GROUP_EVERYONE'}}});
EVERYONE: NodeEntry = new NodeEntry({ entry: { nodeType: 'cm:authorityContainer', properties: {'cm:authorityName': 'GROUP_EVERYONE'}}});
constructor() {
this.searchInput.valueChanges
@ -72,6 +72,13 @@ export class AddPermissionPanelComponent {
this.select.emit(this.selectedItems);
}
selectAll(items: NodeEntry[]) {
if (items?.length > 0) {
this.selectedItems = items;
this.select.emit(this.selectedItems);
}
}
private isAlreadySelected(item: NodeEntry): boolean {
return this.selectedItems.indexOf(item) >= 0;
}

View File

@ -15,24 +15,23 @@
* limitations under the License.
*/
import { setupTestBed } from '@alfresco/adf-core';
import { Node } from '@alfresco/js-api';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AddPermissionComponent } from './add-permission.component';
import { AddPermissionPanelComponent } from './add-permission-panel.component';
import { By } from '@angular/platform-browser';
import { setupTestBed, NodesApiService } from '@alfresco/adf-core';
import { TranslateModule } from '@ngx-translate/core';
import { of, throwError } from 'rxjs';
import { fakeAuthorityResults } from '../../../mock/add-permission.component.mock';
import { ContentTestingModule } from '../../../testing/content.testing.module';
import { NodePermissionService } from '../../services/node-permission.service';
import { Node } from '@alfresco/js-api';
import { TranslateModule } from '@ngx-translate/core';
import { ContentTestingModule } from '../../../testing/content.testing.module';
describe('AddPermissionComponent', () => {
let fixture: ComponentFixture<AddPermissionComponent>;
let element: HTMLElement;
let nodePermissionService: NodePermissionService;
let nodeApiService: NodesApiService;
setupTestBed({
imports: [
@ -42,11 +41,12 @@ describe('AddPermissionComponent', () => {
});
beforeEach(() => {
nodeApiService = TestBed.inject(NodesApiService);
spyOn(nodeApiService, 'getNode').and.returnValue(of({ id: 'fake-node', allowableOperations: ['updatePermissions']}));
nodePermissionService = TestBed.inject(NodePermissionService);
spyOn(nodePermissionService, 'getNodeWithRoles').and.returnValue(
of({ node: { id: 'fake-node', allowableOperations: ['updatePermissions']}, roles: [{ label: 'Test' , role: 'test'}] })
);
fixture = TestBed.createComponent(AddPermissionComponent);
element = fixture.nativeElement;
nodePermissionService = TestBed.inject(NodePermissionService);
fixture.detectChanges();
});
@ -84,7 +84,7 @@ describe('AddPermissionComponent', () => {
});
}));
it('should emit a success event when the node is updated', (done) => {
it('should emit a success event when the node is updated', async (done) => {
fixture.componentInstance.selectedItems = fakeAuthorityResults;
spyOn(nodePermissionService, 'updateNodePermissions').and.returnValue(of({ id: 'fake-node-id'}));
@ -93,12 +93,9 @@ describe('AddPermissionComponent', () => {
done();
});
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
const addButton: HTMLButtonElement = <HTMLButtonElement> element.querySelector('#adf-add-permission-action-button');
addButton.click();
});
await fixture.detectChanges();
const addButton: HTMLButtonElement = <HTMLButtonElement> element.querySelector('#adf-add-permission-action-button');
addButton.click();
});
it('should NOT emit a success event when the user does not have permission to update the node', () => {
@ -111,7 +108,7 @@ describe('AddPermissionComponent', () => {
expect(spySuccess).not.toHaveBeenCalled();
});
it('should emit an error event when the node update fail', (done) => {
it('should emit an error event when the node update fail', async (done) => {
fixture.componentInstance.selectedItems = fakeAuthorityResults;
spyOn(nodePermissionService, 'updateNodePermissions').and.returnValue(throwError({ error: 'err'}));
@ -120,11 +117,8 @@ describe('AddPermissionComponent', () => {
done();
});
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
const addButton: HTMLButtonElement = <HTMLButtonElement> element.querySelector('#adf-add-permission-action-button');
addButton.click();
});
await fixture.detectChanges();
const addButton: HTMLButtonElement = <HTMLButtonElement> element.querySelector('#adf-add-permission-action-button');
addButton.click();
});
});

View File

@ -15,10 +15,11 @@
* limitations under the License.
*/
import { Component, ViewEncapsulation, EventEmitter, Input, Output, OnInit } from '@angular/core';
import { NodeEntry, Node } from '@alfresco/js-api';
import { AllowableOperationsEnum, ContentService } from '@alfresco/adf-core';
import { Node, NodeEntry, PermissionElement } from '@alfresco/js-api';
import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core';
import { NodePermissionService } from '../../services/node-permission.service';
import { NodesApiService, ContentService, AllowableOperationsEnum } from '@alfresco/adf-core';
import { RoleModel } from '../../models/role.model';
@Component({
selector: 'adf-add-permission',
@ -26,6 +27,9 @@ import { NodesApiService, ContentService, AllowableOperationsEnum } from '@alfre
styleUrls: ['./add-permission.component.scss'],
encapsulation: ViewEncapsulation.None
})
/*
* @deprecated in 4.4.0, use adf-add-permission-panel instead.
*/
export class AddPermissionComponent implements OnInit {
/** ID of the target node. */
@ -42,14 +46,16 @@ export class AddPermissionComponent implements OnInit {
selectedItems: NodeEntry[] = [];
currentNode: Node;
currentNodeRoles: string[];
currentNodeRoles: RoleModel[];
constructor(private nodePermissionService: NodePermissionService,
private nodeApiService: NodesApiService,
private contentService: ContentService) { }
ngOnInit(): void {
this.nodeApiService.getNode(this.nodeId).subscribe((node) => this.currentNode = node);
this.nodePermissionService.getNodeWithRoles(this.nodeId).subscribe(({node, roles }) => {
this.currentNode = node;
this.currentNodeRoles = roles;
});
}
onSelect(selection: NodeEntry[]) {
@ -63,9 +69,9 @@ export class AddPermissionComponent implements OnInit {
applySelection() {
if (this.contentService.hasAllowableOperations(this.currentNode, AllowableOperationsEnum.UPDATEPERMISSIONS)) {
this.nodePermissionService.updateNodePermissions(this.nodeId, this.selectedItems)
.subscribe(
(node) => {
const permissions = this.transformNodeToPermissionElement(this.selectedItems, this.currentNodeRoles[0].role);
this.nodePermissionService.updateNodePermissions(this.nodeId, permissions)
.subscribe((node) => {
this.success.emit(node);
},
(error) => {
@ -74,4 +80,13 @@ export class AddPermissionComponent implements OnInit {
}
}
private transformNodeToPermissionElement(nodes: NodeEntry[], role: string): PermissionElement[] {
return nodes.map((node) => {
return {
'authorityId': node.entry.properties['cm:authorityName'] ?? node.entry.properties['cm:userName'],
'name': role,
'accessStatus': 'ALLOWED'
};
});
}
}

View File

@ -57,7 +57,7 @@ export class SearchPermissionConfigurationService implements SearchConfiguration
query = this.queryProvider.query.replace(
new RegExp(/\${([^}]+)}/g), searchTerm);
} else {
query = `authorityName:*${searchTerm}* OR userName:*${searchTerm}*`;
query = `(email:*${searchTerm}* OR firstName:*${searchTerm}* OR lastName:*${searchTerm}* OR displayName:*${searchTerm}* OR authorityName:*${searchTerm}* OR authorityDisplayName:*${searchTerm}*) AND ANAME:(\"0/APP.DEFAULT\")`;
}
return query;
}

View File

@ -0,0 +1,44 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Node } from '@alfresco/js-api';
import { Component, Input, OnInit } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
@Component({
selector: 'adf-node-path-column',
template: `
<span class="adf-user-name-column adf-datatable-cell-value" title="{{ displayText$ | async }}">
{{ displayText$ | async }}
</span>
`,
host: { class: 'adf-node-path-column adf-datatable-content-cell' }
})
export class NodePathColumnComponent implements OnInit {
@Input()
node: Node;
displayText$ = new BehaviorSubject<string>('');
ngOnInit() {
this.updateValue();
}
protected updateValue() {
this.displayText$.next(this.node.path.name);
}
}

View File

@ -0,0 +1,84 @@
<adf-datatable class="adf-datatable-permission"
id="adf-permission-display-container"
[rows]="permissions"
stickyHeader="true"
selectionMode="multiple">
<data-columns>
<data-column class="adf-key-icon"
key="icon"
type="icon"
[sortable]="false">
<ng-template let-context>
<adf-user-icon-column [context]="context"></adf-user-icon-column>
</ng-template>
</data-column>
<data-column class="adf-authorityId-label adf-ellipsis-cell adf-expand-cell-2"
[title]="'PERMISSION_MANAGER.COLUMN.NAME' | translate:{count:permissions.length}"
key="authorityId">
<ng-template let-context>
<adf-user-name-column [context]="context"></adf-user-name-column>
</ng-template>
</data-column>
<data-column class="adf-authorityId-label adf-ellipsis-cell adf-expand-cell-2"
title="PERMISSION_MANAGER.COLUMN.LOCATION"
key="location"
*ngIf="node && showLocation">
<ng-template>
<adf-node-path-column [node]="node"></adf-node-path-column>
</ng-template>
</data-column>
<data-column
class="adf-ellipsis-cell adf-expand-cell-1"
title="PERMISSION_MANAGER.PERMISSION_DISPLAY.ROLE"
key="name"
sortable="false">
<ng-template let-entry="$implicit">
<adf-user-role-column [readonly]="isReadOnly"
[placeholder]="entry.data.getValue(entry.row, entry.col)"
[value]="entry.data.getValue(entry.row, entry.col)"
[roles]="roles"
id="adf-select-role-permission"
(roleChanged)="updateRole($event, entry.row.obj)">
</adf-user-role-column>
</ng-template>
<adf-data-column-header *ngIf="!isReadOnly">
<ng-template>
<adf-user-role-column class="adf-permission-role-column-header"
placeholder="PERMISSION_MANAGER.COLUMN.BULK-ROLE"
[roles]="roles"
[value]="bulkSelectionRole"
id="adf-bulk-select-role-permission"
(roleChanged)="bulkRoleUpdate($event)">
</adf-user-role-column>
</ng-template>
</adf-data-column-header>
</data-column>
<data-column class="adf-delete-permission" key="delete" *ngIf="!isReadOnly" sortable="false">
<ng-template let-entry="$implicit">
<button mat-icon-button
(click)="removePermission($event, entry.row.obj)"
[attr.data-automation-id]="'adf-delete-permission-button-' + entry.row.obj.authorityId">
<mat-icon>delete_outline</mat-icon>
</button>
</ng-template>
</data-column>
</data-columns>
<adf-no-content-template>
<ng-template>
<adf-empty-content
id="adf-no-permissions-template"
icon="supervisor_account"
title="PERMISSION_MANAGER.MESSAGE.EMPTY-PERMISSION"
subtitle="PERMISSION_MANAGER.MESSAGE.EMPTY-SUBTITLE">
</adf-empty-content>
</ng-template>
</adf-no-content-template>
</adf-datatable>

View File

@ -0,0 +1,45 @@
@mixin adf-permission-container-theme($theme) {
$adf-permission-list-width: 100% !default;
.adf {
&-permission-label {
max-width: 130px;
min-width: 100px;
margin-left: 50px;
}
&-delete-permission {
max-width: 50px;
}
&-authorityId-label {
min-width: 100px;
}
&-key-icon {
max-width: 50px;
}
&-ellipsis-cell {
position: sticky;
text-overflow: ellipsis;
white-space: nowrap;
}
&-display-permission-container {
display: flex;
justify-content: space-around;
flex: 1;
}
&-datatable-permission {
display: flex;
min-width: 450px;
width: $adf-permission-list-width;
&.adf-datatable {
overflow: hidden;
}
}
}
}

View File

@ -0,0 +1,99 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { setupTestBed } from '@alfresco/adf-core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { TranslateModule } from '@ngx-translate/core';
import { PermissionContainerComponent } from './permission-container.component';
import { ContentTestingModule } from '../../../testing/content.testing.module';
describe('PermissionContainerComponent', () => {
let fixture: ComponentFixture<PermissionContainerComponent>;
let component: PermissionContainerComponent;
let element: HTMLElement;
setupTestBed({
imports: [
TranslateModule.forRoot(),
ContentTestingModule
]
});
beforeEach(() => {
fixture = TestBed.createComponent(PermissionContainerComponent);
component = fixture.componentInstance;
element = fixture.nativeElement;
component.permissions = [
{
authorityId: 'GROUP_EVERYONE',
accessStatus: 'ALLOWED',
isInherited: true,
name: 'consumer',
icon: null
}
];
component.roles = [
{
label: 'test',
role: 'Test'
},
{
label: 'consumr',
role: 'Consumer'
}
];
fixture.detectChanges();
});
afterEach(() => {
fixture.destroy();
});
it('Should render the layout with details', () => {
expect(element.querySelectorAll('.adf-datatable-permission .adf-datatable-row').length).toBe(2);
expect(element.querySelector('adf-user-name-column').textContent).toContain('GROUP_EVERYONE');
expect(element.querySelector('#adf-select-role-permission').textContent).toContain('consumer');
});
it('should emit update event on role change', () => {
spyOn(component.update, 'emit');
const selectBox = fixture.debugElement.query(By.css(('[id="adf-select-role-permission"] .mat-select-trigger')));
selectBox.triggerEventHandler('click', null);
fixture.detectChanges();
const options = fixture.debugElement.queryAll(By.css('mat-option'));
expect(options).not.toBeNull();
expect(options.length).toBe(2);
options[0].triggerEventHandler('click', {});
fixture.detectChanges();
expect(component.update.emit).toHaveBeenCalledWith({ role: 'Test', permission: component.permissions[0] });
});
it('should delete update event on row delete', () => {
spyOn(component.delete, 'emit');
const deleteButton: HTMLButtonElement = element.querySelector('[data-automation-id="adf-delete-permission-button-GROUP_EVERYONE"]');
deleteButton.click();
fixture.detectChanges();
expect(component.delete.emit).toHaveBeenCalledWith(component.permissions[0]);
});
});

View File

@ -0,0 +1,78 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Component, EventEmitter, Input, OnChanges, Output } from '@angular/core';
import { Node, PermissionElement } from '@alfresco/js-api';
import { PermissionDisplayModel } from '../../models/permission.model';
import { RoleModel } from '../../models/role.model';
@Component({
selector: 'adf-permission-container',
templateUrl: './permission-container.component.html',
styleUrls: ['./permission-container.component.scss']
})
export class PermissionContainerComponent implements OnChanges {
@Input()
node: Node;
@Input()
permissions: PermissionDisplayModel[] = [];
@Input()
roles!: RoleModel[];
@Input()
isReadOnly = false;
@Input()
showLocation = false;
/** Emitted when the permission is updated. */
@Output()
update = new EventEmitter<{role: string, permission: PermissionDisplayModel}>();
@Output()
updateAll = new EventEmitter<string>();
/** Emitted when the permission is updated. */
@Output()
delete = new EventEmitter<PermissionElement>();
/** Emitted when an error occurs. */
@Output()
error = new EventEmitter<any>();
bulkSelectionRole: string;
ngOnChanges(): void {
this.bulkSelectionRole = '';
}
updateRole(role: string, permission: PermissionDisplayModel) {
this.update.emit({ role, permission });
}
bulkRoleUpdate(role: string) {
this.updateAll.emit(role);
}
removePermission(event: MouseEvent, permissionRow: PermissionDisplayModel) {
event.stopPropagation();
this.delete.emit(permissionRow);
}
}

View File

@ -1,84 +1,106 @@
<div id="adf-permission-display-container" class="adf-display-permission-container">
<div *ngIf="!permissionList || !permissionList.length" id="adf-no-permissions-template">
<div #ref>
<ng-content select="adf-no-permission-template"></ng-content>
</div>
<p *ngIf="ref.children.length == 0">
{{ 'PERMISSION_MANAGER.PERMISSION_DISPLAY.NO_PERMISSIONS' | translate }}
</p>
<mat-card class="adf-permission-card" id="adf-permission-manager-card">
<div *ngIf="!(permissionList.data$ | async) && permissionList.loading$ | async" class="adf-permission-loader">
<mat-progress-spinner [color]="'primary'"
[mode]="'indeterminate'">
</mat-progress-spinner>
</div>
<adf-datatable [rows]="permissionList" class="adf-datatable-permission" *ngIf="permissionList && permissionList.length">
<data-columns>
<data-column
class="adf-key-icon"
key="icon" type="icon"
[sortable]="false">
</data-column>
<data-column
class="adf-authorityId-label adf-ellipsis-cell adf-expand-cell-2"
title="PERMISSION_MANAGER.PERMISSION_DISPLAY.AUTHORITY_ID"
key="authorityId">
</data-column>
<data-column
class="adf-ellipsis-cell adf-expand-cell-1"
title="PERMISSION_MANAGER.PERMISSION_DISPLAY.ROLE"
key="name">
<ng-template let-entry="$implicit">
<mat-form-field *ngIf="!entry.row.getValue('isInherited') else show_only_label">
<mat-select
id="adf-select-role-permission"
[placeholder]="entry.data.getValue(entry.row, entry.col)"
value="{{entry.data.getValue(entry.row, entry.col)}}"
(selectionChange)="saveNewRole($event, entry.row.obj)">
<mat-option *ngFor="let role of settableRoles" [value]="role">{{ role | adfLocalizedRole }}</mat-option>
</mat-select>
</mat-form-field>
<ng-template #show_only_label>
<span>{{ entry.data.getValue(entry.row, entry.col) | adfLocalizedRole }}</span>
</ng-template>
</ng-template>
</data-column>
<data-column
class="adf-permission-label"
title="PERMISSION_MANAGER.PERMISSION_DISPLAY.INHERITED"
key="isInherited"
[focus]="false">
<ng-template let-entry="$implicit">
<mat-chip-list>
<mat-chip
*ngIf="!!entry.data.getValue(entry.row, entry.col) else locally_set_chip"
class="mat-chip mat-primary mat-standard-chip mat-chip-selected adf-inherited-label"
id="adf-permission-inherited-label"
color="primary"
selected="true">{{'PERMISSION_MANAGER.PERMISSION_DISPLAY.INHERITED' | translate}}</mat-chip>
</mat-chip-list>
<ng-template #locally_set_chip>
<mat-chip-list class="adf-locallyset-label">
<mat-chip
id="adf-permission-locallyset-label"
color="accent"
selected="true">
{{'PERMISSION_MANAGER.PERMISSION_DISPLAY.LOCALLY_SET' | translate}}
</mat-chip>
</mat-chip-list>
</ng-template>
</ng-template>
</data-column>
<data-column class="adf-delete-permission" key="delete">
<ng-template let-entry="$implicit">
<button
*ngIf="!entry.row.getValue('isInherited')"
mat-icon-button
color="primary"
(click)="removePermission(entry.row.obj)"
data-automation-id="adf-delete-permission-button">
<mat-icon>highlight_off</mat-icon>
</button>
</ng-template>
</data-column>
</data-columns>
</adf-datatable>
</div>
<ng-container *ngIf="permissionList.error$ | async">
<div class="adf-no-permission__template" id="adf-permission-manager-error">
<mat-icon [color]="'error'">error</mat-icon>
<p class="adf-no-permission__template--text">{{ 'PERMISSION_MANAGER.ERROR.NOT-FOUND'| translate }}</p>
</div>
</ng-container>
<ng-container *ngIf="permissionList.data$ | async as model">
<div class="adf-permission-container" #target>
<div class="adf-permission-header">
<span class="adf-inherit-container">
<h3 class="adf-inherit-container-header">
{{'PERMISSION_MANAGER.LABELS.INHERITED-PERMISSIONS' | translate }}
{{ (model.node.permissions.isInheritanceEnabled ? "PERMISSION_MANAGER.LABELS.ON" : "PERMISSION_MANAGER.LABELS.OFF") | translate }}
</h3>
<mat-slide-toggle
class="adf-inherit-toggle"
data-automation-id="adf-inherit-toggle-button"
[checked]="model.node.permissions.isInheritanceEnabled"
(change)="permissionList.toggleInherited($event)">
</mat-slide-toggle>
</span>
<span class="adf-inherit-subtitle" title="total">
{{'PERMISSION_MANAGER.LABELS.INHERITED-SUBTITLE' | translate: { count: model.inheritedPermissions.length } }}
</span>
</div>
<button
mat-button
data-automation-id="permission-info-button"
[adf-pop-over]="inheritedPermission"
[target]="target"
#popOver="adfPopOver"
*ngIf="model.node.permissions.isInheritanceEnabled">
{{ popOver.open ? "Hide" : "Show" }}
<mat-icon *ngIf="popOver.open"> keyboard_arrow_up </mat-icon>
<mat-icon *ngIf="!popOver.open"> keyboard_arrow_down </mat-icon>
</button>
</div>
<ng-template #inheritedPermission>
<div class="adf-pop-over-card">
<adf-permission-container
data-automation-id="adf-inherited-permission"
[isReadOnly]="true"
[node]="model.node"
[permissions]="model.inheritedPermissions"
[showLocation]="true"
[roles]="model.roles">
</adf-permission-container>
</div>
</ng-template>
<mat-card-content style="overflow: hidden">
<section class="adf-permission-content-header">
<h3>{{'PERMISSION_MANAGER.LABELS.DIRECT-PERMISSIONS' | translate }}</h3>
<div class="adf-toolbar--spacer"></div>
<button
mat-button
[matTooltip]="'PERMISSION_MANAGER.ACTION.DELETE' | translate"
[disabled]="!selectedPermissions?.length"
(click)="deleteSelection()"
data-automation-id="adf-delete-selected-permission">
<mat-icon>delete_outline</mat-icon>
</button>
<adf-toolbar-divider></adf-toolbar-divider>
<button
mat-button
[matTooltip]="'PERMISSION_MANAGER.ACTION.ADD-PERMISSION' | translate"
(click)="openAddPermissionDialog()"
data-automation-id="adf-add-permission-button">
<mat-icon>person_add_outline</mat-icon>
</button>
</section>
<adf-permission-container
class="adf-permission-list"
data-automation-id="adf-locally-set-permission"
[node]="model.node"
[permissions]="model.localPermissions"
(update)="updatePermission($event)"
(delete)="deletePermission($event)"
(updateAll)="updateAllPermission($event)"
(row-select)="onSelect($event.detail.selection)"
(row-unselect)="onSelect($event.detail.selection)"
[roles]="model.roles">
</adf-permission-container>
</mat-card-content>
</ng-container>
</mat-card>

View File

@ -1,50 +1,92 @@
@mixin adf-permission-list-theme($theme) {
$adf-permission-list-width: 70% !default;
$primary: map-get($theme, primary);
$accent: map-get($theme, accent);
$warn: map-get($theme, warn);
$foreground: map-get($theme, foreground);
$background: map-get($theme, background);
.adf {
&-permission-label {
max-width: 130px;
min-width: 100px;
margin-left: 50px;
&-permission-card {
height: 100%;
box-sizing: border-box;
display: flex !important;
flex-direction: column;
overflow: hidden;
}
&-delete-permission {
max-width: 50px;
&-permission-loader {
margin-left: 45%;
overflow: hidden;
}
&-authorityId-label {
min-width: 100px;
}
&-key-icon {
max-width: 50px;
}
&-ellipsis-cell {
position: sticky;
text-overflow: ellipsis;
white-space: nowrap;
}
&-display-permission-container {
&-permission-container {
display: flex;
justify-content: space-around;
flex: 1;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 10px 15px;
border: 1px solid mat-color($foreground, divider);
}
&-datatable-permission {
&-inherit-container {
display: flex;
min-width: 450px;
width: $adf-permission-list-width;
flex-direction: row;
align-items: center;
&-header {
margin-bottom: 10px;
margin-top: 10px;
}
}
&-locallyset-label {
padding: 4px;
&-inherit-toggle {
padding-left: 30px;
}
&-inherited-label {
width: 92.13px;
justify-content: center;
&-inherit-subtitle {
padding-bottom: 5px;
}
&-permission-content-header {
display: flex;
flex-direction: row;
align-items: center;
padding: 5px 15px;
}
&-permission-role-column-header {
position: relative !important;
height: 40px;
.mat-form-field-infix {
border: none;
}
}
&-permission-header {
@include flex-column;
}
&-permission-list {
display: flex;
height: calc(100% - 63px);
}
}
[aria-sort='Ascending'] adf-user-role-column,
[aria-sort='Descending'] adf-user-role-column {
padding-left: 10px;
padding-right: 10px;
}
.adf-permission-pop-over {
padding-right: 15px;
width: 100%;
.adf-pop-over-card {
width: 100%;
@include mat-elevation(16, mat-color($foreground, divider), 0.8);
}
}
}

View File

@ -15,21 +15,24 @@
* limitations under the License.
*/
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { PermissionListComponent } from './permission-list.component';
import { By } from '@angular/platform-browser';
import { NodesApiService, SearchService, setupTestBed } from '@alfresco/adf-core';
import { of } from 'rxjs';
import { NodePermissionService } from '../../services/node-permission.service';
import { fakeNodeWithPermissions,
fakeNodeInheritedOnly,
fakeNodeWithOnlyLocally,
fakeSiteNodeResponse,
fakeSiteRoles,
fakeNodeWithoutPermissions,
fakeEmptyResponse } from '../../../mock/permission-list.component.mock';
import { ContentTestingModule } from '../../../testing/content.testing.module';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { TranslateModule } from '@ngx-translate/core';
import { of, throwError } from 'rxjs';
import { PermissionListComponent } from './permission-list.component';
import { NodePermissionService } from '../../services/node-permission.service';
import {
fakeEmptyResponse,
fakeNodeInheritedOnly,
fakeNodeWithOnlyLocally,
fakeNodeWithoutPermissions,
fakeNodeWithPermissions,
fakeReadOnlyNodeInherited,
fakeSiteNodeResponse,
fakeSiteRoles
} from '../../../mock/permission-list.component.mock';
import { ContentTestingModule } from '../../../testing/content.testing.module';
describe('PermissionListComponent', () => {
@ -39,6 +42,9 @@ describe('PermissionListComponent', () => {
let nodeService: NodesApiService;
let nodePermissionService: NodePermissionService;
let searchApiService: SearchService;
let getNodeSpy: jasmine.Spy;
let searchQuerySpy: jasmine.Spy;
const fakeLocalPermission = JSON.parse(JSON.stringify(fakeNodeWithOnlyLocally));
setupTestBed({
imports: [
@ -54,142 +60,188 @@ describe('PermissionListComponent', () => {
nodeService = TestBed.inject(NodesApiService);
nodePermissionService = TestBed.inject(NodePermissionService);
searchApiService = TestBed.inject(SearchService);
spyOn(nodePermissionService, 'getGroupMemberByGroupName').and.returnValue(of(fakeSiteRoles));
getNodeSpy = spyOn(nodeService, 'getNode').and.returnValue(of(fakeNodeWithoutPermissions));
searchQuerySpy = spyOn(searchApiService, 'searchByQueryBody').and.returnValue(of(fakeEmptyResponse));
component.nodeId = 'fake-node-id';
fixture.detectChanges();
});
afterEach(() => {
fixture.destroy();
});
it('should be able to render the component', () => {
spyOn(nodeService, 'getNode').and.returnValue(of(fakeNodeWithOnlyLocally));
spyOn(nodePermissionService, 'getNodeRoles').and.returnValue(of([]));
fixture.detectChanges();
expect(element.querySelector('#adf-permission-display-container')).not.toBeNull();
it('should render default layout', async () => {
component.nodeId = 'fake-node-id';
getNodeSpy.and.returnValue(of(fakeNodeWithoutPermissions));
component.ngOnInit();
await fixture.detectChanges();
expect(element.querySelector('.adf-permission-container')).not.toBeNull();
expect(element.querySelector('[data-automation-id="adf-locally-set-permission"]')).not.toBeNull();
});
it('should render default empty template when no permissions', () => {
it('should render error template', async () => {
component.nodeId = 'fake-node-id';
spyOn(nodeService, 'getNode').and.returnValue(of(fakeNodeWithoutPermissions));
spyOn(searchApiService, 'searchByQueryBody').and.returnValue(of(fakeEmptyResponse));
fixture.detectChanges();
getNodeSpy.and.returnValue(throwError(null));
component.ngOnInit();
await fixture.detectChanges();
expect(element.querySelector('#adf-no-permissions-template')).not.toBeNull();
expect(element.querySelector('#adf-permission-display-container .adf-datatable-permission')).toBeNull();
expect(element.querySelector('.adf-no-permission__template')).not.toBeNull();
expect(element.querySelector('.adf-no-permission__template p').textContent).toContain('PERMISSION_MANAGER.ERROR.NOT-FOUND');
});
it('should show the node permissions', () => {
component.nodeId = 'fake-node-id';
spyOn(nodeService, 'getNode').and.returnValue(of(fakeNodeWithPermissions));
spyOn(searchApiService, 'searchByQueryBody').and.returnValue(of(fakeEmptyResponse));
getNodeSpy.and.returnValue(of(fakeNodeWithPermissions));
component.ngOnInit();
fixture.detectChanges();
expect(element.querySelector('#adf-permission-display-container')).not.toBeNull();
expect(element.querySelectorAll('.adf-datatable-row').length).toBe(4);
expect(element.querySelectorAll('[data-automation-id="adf-locally-set-permission"] .adf-datatable-row').length).toBe(2);
const showButton: HTMLButtonElement = element.querySelector('[data-automation-id="permission-info-button"]');
showButton.click();
fixture.detectChanges();
expect(document.querySelectorAll('[data-automation-id="adf-inherited-permission"] .adf-datatable-row').length).toBe(3);
});
it('should show inherited label for inherited permissions', () => {
component.nodeId = 'fake-node-id';
spyOn(nodeService, 'getNode').and.returnValue(of(fakeNodeInheritedOnly));
spyOn(searchApiService, 'searchByQueryBody').and.returnValue(of(fakeEmptyResponse));
fixture.detectChanges();
expect(element.querySelector('#adf-permission-display-container')).not.toBeNull();
expect(element.querySelector('#adf-permission-inherited-label')).toBeDefined();
expect(element.querySelector('#adf-permission-inherited-label')).not.toBeNull();
});
describe('Inherited Permission', () => {
it('should show inherited details', async() => {
getNodeSpy.and.returnValue(of(fakeNodeInheritedOnly));
component.ngOnInit();
await fixture.detectChanges();
describe('when it is a locally set permission', () => {
it('should show locally set label for locally set permissions', () => {
component.nodeId = 'fake-node-id';
spyOn(nodeService, 'getNode').and.returnValue(of(fakeNodeWithOnlyLocally));
spyOn(nodePermissionService, 'getGroupMemberByGroupName').and.returnValue(of(fakeSiteRoles));
spyOn(searchApiService, 'searchByQueryBody').and.returnValue(of(fakeSiteNodeResponse));
fixture.detectChanges();
expect(element.querySelector('#adf-permission-display-container')).not.toBeNull();
expect(element.querySelector('#adf-permission-locallyset-label')).toBeDefined();
expect(element.querySelector('#adf-permission-locallyset-label')).not.toBeNull();
expect(element.querySelector('.adf-inherit-container .mat-checked')).toBeDefined();
expect(element.querySelector('.adf-inherit-container h3').textContent.trim())
.toBe('PERMISSION_MANAGER.LABELS.INHERITED-PERMISSIONS PERMISSION_MANAGER.LABELS.ON');
expect(element.querySelector('span[title="total"]').textContent.trim())
.toBe('PERMISSION_MANAGER.LABELS.INHERITED-SUBTITLE');
});
it('should show a dropdown with the possible roles', async(() => {
component.nodeId = 'fake-node-id';
spyOn(nodeService, 'getNode').and.returnValue(of(fakeNodeWithOnlyLocally));
spyOn(nodePermissionService, 'getGroupMemberByGroupName').and.returnValue(of(fakeSiteRoles));
spyOn(searchApiService, 'searchByQueryBody').and.returnValue(of(fakeSiteNodeResponse));
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(element.querySelector('#adf-select-role-permission')).toBeDefined();
expect(element.querySelector('#adf-select-role-permission')).not.toBeNull();
const selectBox = fixture.debugElement.query(By.css(('#adf-select-role-permission .mat-select-trigger')));
selectBox.triggerEventHandler('click', null);
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
const options: any = fixture.debugElement.queryAll(By.css('mat-option'));
expect(options).not.toBeNull();
expect(options.length).toBe(4);
expect(options[0].nativeElement.innerText).toContain('ADF.ROLES.SITECOLLABORATOR');
expect(options[1].nativeElement.innerText).toContain('ADF.ROLES.SITECONSUMER');
expect(options[2].nativeElement.innerText).toContain('ADF.ROLES.SITECONTRIBUTOR');
expect(options[3].nativeElement.innerText).toContain('ADF.ROLES.SITEMANAGER');
});
});
}));
it('should toggle the inherited button', async() => {
getNodeSpy.and.returnValue(of(fakeNodeInheritedOnly));
component.ngOnInit();
await fixture.detectChanges();
it('should show the settable roles if the node is not in any site', async(() => {
component.nodeId = 'fake-node-id';
spyOn(nodeService, 'getNode').and.returnValue(of(fakeNodeWithOnlyLocally));
spyOn(searchApiService, 'searchByQueryBody').and.returnValue(of(fakeEmptyResponse));
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(element.querySelector('#adf-select-role-permission')).toBeDefined();
expect(element.querySelector('#adf-select-role-permission')).not.toBeNull();
const selectBox = fixture.debugElement.query(By.css(('#adf-select-role-permission .mat-select-trigger')));
selectBox.triggerEventHandler('click', null);
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
const options: any = fixture.debugElement.queryAll(By.css('mat-option'));
expect(options).not.toBeNull();
expect(options.length).toBe(5);
expect(options[0].nativeElement.innerText).toContain('ADF.ROLES.CONTRIBUTOR');
expect(options[1].nativeElement.innerText).toContain('ADF.ROLES.COLLABORATOR');
expect(options[2].nativeElement.innerText).toContain('ADF.ROLES.COORDINATOR');
expect(options[3].nativeElement.innerText).toContain('ADF.ROLES.EDITOR');
expect(options[4].nativeElement.innerText).toContain('ADF.ROLES.CONSUMER');
});
});
}));
expect(element.querySelector('.adf-inherit-container .mat-checked')).toBeDefined();
expect(element.querySelector('.adf-inherit-container h3').textContent.trim())
.toBe('PERMISSION_MANAGER.LABELS.INHERITED-PERMISSIONS PERMISSION_MANAGER.LABELS.ON');
expect(element.querySelector('span[title="total"]').textContent.trim())
.toBe('PERMISSION_MANAGER.LABELS.INHERITED-SUBTITLE');
it('should update the role when another value is chosen', async(() => {
component.nodeId = 'fake-node-id';
spyOn(nodeService, 'getNode').and.returnValue(of(fakeNodeWithOnlyLocally));
spyOn(nodeService, 'updateNode').and.returnValue(of({id: 'fake-updated-node'}));
spyOn(searchApiService, 'searchByQueryBody').and.returnValue(of(fakeEmptyResponse));
component.update.subscribe((updatedPermission) => {
expect(updatedPermission).not.toBeNull();
expect(updatedPermission.name).toBe('Editor');
expect(updatedPermission.authorityId).toBe('GROUP_EVERYONE');
expect(updatedPermission.accessStatus).toBe('ALLOWED');
});
spyOn(nodeService, 'updateNode').and.returnValue(of(fakeLocalPermission));
const slider = fixture.debugElement.query(By.css('mat-slide-toggle'));
slider.triggerEventHandler('change', { source: { checked: false } });
await fixture.detectChanges();
expect(element.querySelector('.adf-inherit-container .mat-checked')).toBe(null);
expect(element.querySelector('.adf-inherit-container h3').textContent.trim())
.toBe('PERMISSION_MANAGER.LABELS.INHERITED-PERMISSIONS PERMISSION_MANAGER.LABELS.OFF');
expect(element.querySelector('span[title="total"]').textContent.trim())
.toBe('PERMISSION_MANAGER.LABELS.INHERITED-SUBTITLE');
});
it('should not toggle inherited button for read only users', async () => {
getNodeSpy.and.returnValue(of(fakeReadOnlyNodeInherited));
component.ngOnInit();
await fixture.detectChanges();
expect(element.querySelector('.adf-inherit-container .mat-checked')).toBeDefined();
expect(element.querySelector('.adf-inherit-container h3').textContent.trim())
.toBe('PERMISSION_MANAGER.LABELS.INHERITED-PERMISSIONS PERMISSION_MANAGER.LABELS.ON');
expect(element.querySelector('span[title="total"]').textContent.trim())
.toBe('PERMISSION_MANAGER.LABELS.INHERITED-SUBTITLE');
spyOn(nodeService, 'updateNode').and.returnValue(of(fakeLocalPermission));
const slider = fixture.debugElement.query(By.css('mat-slide-toggle'));
slider.triggerEventHandler('change', { source: { checked: false } });
await fixture.detectChanges();
expect(element.querySelector('.adf-inherit-container .mat-checked')).toBeDefined();
expect(element.querySelector('.adf-inherit-container h3').textContent.trim())
.toBe('PERMISSION_MANAGER.LABELS.INHERITED-PERMISSIONS PERMISSION_MANAGER.LABELS.ON');
expect(element.querySelector('span[title="total"]').textContent.trim())
.toBe('PERMISSION_MANAGER.LABELS.INHERITED-SUBTITLE');
expect(document.querySelector('simple-snack-bar').textContent)
.toContain('PERMISSION_MANAGER.ERROR.NOT-ALLOWED');
});
});
describe('locally set permission', () => {
beforeEach(() => {
getNodeSpy.and.returnValue(of(fakeLocalPermission));
});
it('should show locally set permissions', async() => {
searchQuerySpy.and.returnValue(of(fakeSiteNodeResponse));
component.ngOnInit();
await fixture.detectChanges();
expect(element.querySelector('adf-user-name-column').textContent).toContain('GROUP_EVERYONE');
expect(element.querySelector('#adf-select-role-permission').textContent).toContain('Contributor');
});
it('should see the settable roles if the node is not in any site', async() => {
searchQuerySpy.and.returnValue(of(fakeSiteNodeResponse));
component.ngOnInit();
await fixture.detectChanges();
expect(element.querySelector('adf-user-name-column').textContent).toContain('GROUP_EVERYONE');
expect(element.querySelector('#adf-select-role-permission').textContent).toContain('Contributor');
const selectBox = fixture.debugElement.query(By.css(('[id="adf-select-role-permission"] .mat-select-trigger')));
selectBox.triggerEventHandler('click', null);
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(element.querySelector('#adf-select-role-permission')).toBeDefined();
expect(element.querySelector('#adf-select-role-permission')).not.toBeNull();
const selectBox = fixture.debugElement.query(By.css(('#adf-select-role-permission .mat-select-trigger')));
selectBox.triggerEventHandler('click', null);
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
const options: any = fixture.debugElement.queryAll(By.css('mat-option'));
expect(options).not.toBeNull();
expect(options.length).toBe(5);
options[3].triggerEventHandler('click', {});
fixture.detectChanges();
expect(nodeService.updateNode).toHaveBeenCalled();
});
});
}));
});
const options: any = fixture.debugElement.queryAll(By.css('mat-option'));
expect(options).not.toBeNull();
expect(options.length).toBe(4);
expect(options[0].nativeElement.innerText).toContain('ADF.ROLES.SITECOLLABORATOR');
expect(options[1].nativeElement.innerText).toContain('ADF.ROLES.SITECONSUMER');
expect(options[2].nativeElement.innerText).toContain('ADF.ROLES.SITECONTRIBUTOR');
expect(options[3].nativeElement.innerText).toContain('ADF.ROLES.SITEMANAGER');
});
it('should update the role when another value is chosen', async () => {
spyOn(nodeService, 'updateNode').and.returnValue(of({id: 'fake-uwpdated-node'}));
searchQuerySpy.and.returnValue(of(fakeEmptyResponse));
component.ngOnInit();
await fixture.detectChanges();
expect(element.querySelector('adf-user-name-column').textContent).toContain('GROUP_EVERYONE');
expect(element.querySelector('#adf-select-role-permission').textContent).toContain('Contributor');
const selectBox = fixture.debugElement.query(By.css(('[id="adf-select-role-permission"] .mat-select-trigger')));
selectBox.triggerEventHandler('click', null);
fixture.detectChanges();
const options: any = fixture.debugElement.queryAll(By.css('mat-option'));
expect(options).not.toBeNull();
expect(options.length).toBe(5);
options[3].triggerEventHandler('click', {});
fixture.detectChanges();
expect(nodeService.updateNode).toHaveBeenCalledWith('f472543f-7218-403d-917b-7a5861257244', { permissions: { locallySet: [ { accessStatus: 'ALLOWED', name: 'Editor', authorityId: 'GROUP_EVERYONE' } ] } });
});
it('should delete the person', async () => {
spyOn(nodeService, 'updateNode').and.returnValue(of({id: 'fake-uwpdated-node'}));
searchQuerySpy.and.returnValue(of(fakeEmptyResponse));
component.ngOnInit();
await fixture.detectChanges();
expect(element.querySelector('adf-user-name-column').textContent).toContain('GROUP_EVERYONE');
expect(element.querySelector('#adf-select-role-permission').textContent).toContain('Contributor');
const showButton: HTMLButtonElement = element.querySelector('[data-automation-id="adf-delete-permission-button-GROUP_EVERYONE"]');
showButton.click();
fixture.detectChanges();
expect(nodeService.updateNode).toHaveBeenCalledWith('f472543f-7218-403d-917b-7a5861257244', { permissions: { locallySet: [ ] } });
});
});
});

View File

@ -15,11 +15,11 @@
* limitations under the License.
*/
import { Component, ViewEncapsulation, Input, OnInit, EventEmitter, Output } from '@angular/core';
import { NodesApiService } from '@alfresco/adf-core';
import { Node, PermissionElement } from '@alfresco/js-api';
import { ObjectDataRow } from '@alfresco/adf-core';
import { PermissionElement } from '@alfresco/js-api';
import { Component, EventEmitter, Input, Output, ViewEncapsulation } from '@angular/core';
import { PermissionDisplayModel } from '../../models/permission.model';
import { NodePermissionService } from '../../services/node-permission.service';
import { PermissionListService } from './permission-list.service';
@Component({
selector: 'adf-permission-list',
@ -27,70 +27,54 @@ import { NodePermissionService } from '../../services/node-permission.service';
styleUrls: ['./permission-list.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class PermissionListComponent implements OnInit {
export class PermissionListComponent {
/** ID of the node whose permissions you want to show. */
@Input()
nodeId: string = '';
nodeId: string;
/** Emitted when the permission is updated. */
@Output()
update = new EventEmitter<PermissionElement>();
update: EventEmitter<PermissionElement>;
/** Emitted when an error occurs. */
@Output()
error = new EventEmitter<any>();
error: EventEmitter<any>;
permissionList: PermissionDisplayModel[];
settableRoles: any[];
actualNode: Node;
selectedPermissions: PermissionDisplayModel[] = [];
constructor(private nodeService: NodesApiService,
private nodePermissionService: NodePermissionService) {
constructor(public readonly permissionList: PermissionListService) {
this.error = this.permissionList.errored;
this.update = this.permissionList.updated;
}
ngOnInit() {
this.fetchNodePermissions();
ngOnInit(): void {
this.permissionList.fetchPermission(this.nodeId);
}
reload() {
this.fetchNodePermissions();
openAddPermissionDialog() {
this.permissionList.updateNodePermissionByDialog();
}
private fetchNodePermissions() {
this.nodeService.getNode(this.nodeId).subscribe((node: Node) => {
this.actualNode = node;
this.permissionList = this.nodePermissionService.getNodePermissions(node);
this.nodePermissionService.getNodeRoles(node).subscribe((settableList: string[]) => {
this.settableRoles = settableList;
});
});
onSelect(selections: ObjectDataRow[]) {
this.selectedPermissions = selections.map((selection) => selection['obj']);
}
saveNewRole(event: any, permissionRow: PermissionDisplayModel) {
const updatedPermissionRole = this.buildUpdatedPermission(event.value, permissionRow);
this.nodePermissionService.updatePermissionRole(this.actualNode, updatedPermissionRole)
.subscribe(() => {
this.update.emit(updatedPermissionRole);
});
deleteSelection() {
this.permissionList.deletePermissions(this.selectedPermissions);
this.selectedPermissions = [];
}
private buildUpdatedPermission(newRole: string, permissionRow: PermissionDisplayModel): PermissionElement {
return {
accessStatus: permissionRow.accessStatus,
name: newRole,
authorityId: permissionRow.authorityId
};
updatePermission({role, permission}) {
this.permissionList.updateRole(role, permission);
}
removePermission(permissionRow: PermissionDisplayModel) {
this.nodePermissionService
.removePermission(this.actualNode, permissionRow)
.subscribe(
node => this.update.emit(node),
error => this.error.emit(error)
);
deletePermission(permission: PermissionDisplayModel) {
this.selectedPermissions = [];
this.permissionList.deletePermission(permission);
}
updateAllPermission(role: string) {
this.permissionList.bulkRoleUpdate(role);
this.selectedPermissions = [];
}
}

View File

@ -0,0 +1,149 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { NodesApiService, NotificationService, setupTestBed } from '@alfresco/adf-core';
import { TestBed } from '@angular/core/testing';
import { TranslateModule } from '@ngx-translate/core';
import { of, throwError } from 'rxjs';
import { PermissionListService } from './permission-list.service';
import { ContentTestingModule } from '../../../testing/content.testing.module';
import { NodePermissionService } from '../../services/node-permission.service';
import { fakeNodeInheritedOnly, fakeNodeWithOnlyLocally } from '../../../mock/permission-list.component.mock';
import { PermissionDisplayModel } from '../../models/permission.model';
describe('PermissionListService', () => {
let service: PermissionListService;
let nodePermissionService: NodePermissionService;
let notificationService: NotificationService;
let nodesApiService: NodesApiService;
const localPermission = [new PermissionDisplayModel({
authorityId: 'GROUP_EVERYONE',
name: 'Contributor',
accessStatus: 'ALLOWED'
})
];
setupTestBed({
imports: [
TranslateModule.forRoot(),
ContentTestingModule
]
});
beforeEach(() => {
service = TestBed.inject(PermissionListService);
nodePermissionService = TestBed.inject(NodePermissionService);
notificationService = TestBed.inject(NotificationService);
nodesApiService = TestBed.inject(NodesApiService);
spyOn(notificationService, 'showInfo').and.stub();
spyOn(notificationService, 'showWarning').and.stub();
spyOn(notificationService, 'showError').and.stub();
});
it('fetch Permission', (done) => {
spyOn(nodePermissionService, 'getNodeWithRoles').and.returnValue(of({node: fakeNodeWithOnlyLocally , roles: []}));
const subscription = service.data$.subscribe(({ node, inheritedPermissions, localPermissions, roles }) => {
expect(node).toBe(fakeNodeWithOnlyLocally);
expect(inheritedPermissions).toEqual([]);
expect(roles).toEqual([]);
expect(localPermissions).toEqual(localPermission);
subscription.unsubscribe();
done();
});
service.fetchPermission('fake node');
});
describe('toggle permission', () => {
it('should show error if user doesn\'t have permission to update node', () => {
const node = JSON.parse(JSON.stringify(fakeNodeInheritedOnly)), event = { source: { checked: false } };
node.allowableOperations = [];
spyOn(nodePermissionService, 'getNodeWithRoles').and.returnValue(of({node , roles: []}));
spyOn(nodesApiService, 'updateNode').and.stub();
service.fetchPermission('fetch node');
service.toggleInherited(event as any);
expect(nodesApiService.updateNode).not.toHaveBeenCalled();
expect(notificationService.showError).toHaveBeenCalledWith('PERMISSION_MANAGER.ERROR.NOT-ALLOWED');
});
it('should show message after success toggle', () => {
const node = JSON.parse(JSON.stringify(fakeNodeInheritedOnly)), event = { source: { checked: false } };
const updateNode = JSON.parse(JSON.stringify(fakeNodeInheritedOnly));
updateNode.permissions.isInheritanceEnabled = false;
spyOn(nodePermissionService, 'getNodeWithRoles').and.returnValue(of({node , roles: []}));
spyOn(nodesApiService, 'updateNode').and.returnValue(of(updateNode));
service.fetchPermission('fetch node');
service.toggleInherited(event as any);
expect(nodesApiService.updateNode).toHaveBeenCalled();
expect(notificationService.showInfo).toHaveBeenCalledWith('PERMISSION_MANAGER.MESSAGE.INHERIT-DISABLE-SUCCESS');
});
it('should show message for errored toggle', () => {
const node = JSON.parse(JSON.stringify(fakeNodeInheritedOnly)), event = { source: { checked: false } };
spyOn(nodesApiService, 'updateNode').and.returnValue(throwError('Failed to update'));
spyOn(nodePermissionService, 'getNodeWithRoles').and.returnValue(of({node , roles: []}));
service.fetchPermission('fetch node');
service.toggleInherited(event as any);
expect(nodesApiService.updateNode).toHaveBeenCalled();
expect(notificationService.showWarning).toHaveBeenCalledWith('PERMISSION_MANAGER.MESSAGE.TOGGLE-PERMISSION-FAILED');
});
});
describe('delete permission', () => {
const node = JSON.parse(JSON.stringify(fakeNodeWithOnlyLocally));
beforeEach(() => {
spyOn(nodePermissionService, 'getNodeWithRoles').and.returnValue(of({node , roles: []}));
service.fetchPermission('fetch node');
});
it('should be able to delete a permission', () => {
spyOn(nodePermissionService, 'removePermissions').and.returnValue(of(node));
service.deletePermissions(localPermission);
expect(notificationService.showInfo).toHaveBeenCalledWith('PERMISSION_MANAGER.MESSAGE.PERMISSION-BULK-DELETE-SUCCESS', null, { user: 0, group: 1 });
});
it('should show error message for errored delete operation', () => {
spyOn(nodePermissionService, 'removePermissions').and.returnValue(throwError('Failed operation'));
service.deletePermissions(localPermission);
expect(notificationService.showError).toHaveBeenCalledWith('PERMISSION_MANAGER.MESSAGE.PERMISSION-DELETE-FAIL');
});
});
describe('Bulk Role', () => {
const node = JSON.parse(JSON.stringify(fakeNodeWithOnlyLocally));
beforeEach(() => {
spyOn(nodePermissionService, 'getNodeWithRoles').and.returnValue(of({node , roles: []}));
service.fetchPermission('fetch node');
});
it('should be able to update bulk permission', () => {
spyOn(nodePermissionService, 'updatePermissions').and.returnValue(of(node));
service.bulkRoleUpdate('fake-role');
expect(notificationService.showInfo).toHaveBeenCalledWith('PERMISSION_MANAGER.MESSAGE.PERMISSION-BULK-UPDATE-SUCCESS', null, { user: 0, group: 1 });
});
it('should show error message for errored operation', () => {
spyOn(nodePermissionService, 'updatePermissions').and.returnValue(throwError('Error'));
service.bulkRoleUpdate('fake-role');
expect(notificationService.showError).toHaveBeenCalledWith('PERMISSION_MANAGER.MESSAGE.PERMISSION-UPDATE-FAIL');
});
});
});

View File

@ -0,0 +1,207 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { AllowableOperationsEnum, ContentService, NodesApiService, NotificationService } from '@alfresco/adf-core';
import { Node, PermissionElement } from '@alfresco/js-api';
import { EventEmitter, Injectable } from '@angular/core';
import { MatSlideToggleChange } from '@angular/material/slide-toggle';
import { BehaviorSubject, forkJoin, Observable, of, Subject } from 'rxjs';
import { finalize, map, switchMap } from 'rxjs/operators';
import { RoleModel } from '../../models/role.model';
import { PermissionDisplayModel } from '../../models/permission.model';
import { NodePermissionsModel } from '../../models/member.model';
import { NodePermissionService } from '../../services/node-permission.service';
import { NodePermissionDialogService } from '../../services/node-permission-dialog.service';
@Injectable({
providedIn: 'root'
})
export class PermissionListService {
updated = new EventEmitter<PermissionDisplayModel>();
errored = new EventEmitter<PermissionDisplayModel>();
loading$: BehaviorSubject<boolean> = new BehaviorSubject(true);
error$: Subject<boolean> = new Subject();
nodeWithRoles$: Subject<{ node: Node, roles: RoleModel[] }> = new Subject();
data$: Observable<NodePermissionsModel> = this.nodeWithRoles$.pipe(
map(({ node, roles}) => {
return {
node,
roles,
inheritedPermissions: this.nodePermissionService.getInheritedPermission(node),
localPermissions: this.nodePermissionService.getLocalPermissions(node),
allPermission: this.nodePermissionService.getNodePermissions(node)
};
})
);
private node: Node;
private roles: RoleModel[];
constructor(
private nodeService: NodesApiService,
private nodePermissionService: NodePermissionService,
private nodePermissionDialogService: NodePermissionDialogService,
private contentService: ContentService,
private notificationService: NotificationService
) {}
fetchPermission(nodeId: string) {
this.loading$.next(true);
this.nodePermissionService.getNodeWithRoles(nodeId)
.pipe(finalize(() => this.loading$.next(false)))
.subscribe(
({ node, roles }) => {
this.node = node;
this.roles = roles;
this.nodeWithRoles$.next({ node, roles });
},
() => this.error$.next(true)
);
}
toggleInherited(change: MatSlideToggleChange) {
if (this.contentService.hasAllowableOperations(this.node, AllowableOperationsEnum.UPDATEPERMISSIONS)) {
const nodeBody = {
permissions: {
isInheritanceEnabled: !this.node.permissions.isInheritanceEnabled
}
};
this.nodeService.updateNode(this.node.id, nodeBody, {include: ['permissions']})
.subscribe(
(nodeUpdated: Node) => {
const message = nodeUpdated.permissions.isInheritanceEnabled ? 'PERMISSION_MANAGER.MESSAGE.INHERIT-ENABLE-SUCCESS' : 'PERMISSION_MANAGER.MESSAGE.INHERIT-DISABLE-SUCCESS';
this.notificationService.showInfo(message);
nodeUpdated.permissions.inherited = nodeUpdated.permissions?.inherited ?? [];
this.reloadNode(nodeUpdated);
},
() => {
change.source.checked = this.node.permissions.isInheritanceEnabled;
this.notificationService.showWarning('PERMISSION_MANAGER.MESSAGE.TOGGLE-PERMISSION-FAILED');
}
);
} else {
change.source.checked = this.node.permissions.isInheritanceEnabled;
this.notificationService.showError('PERMISSION_MANAGER.ERROR.NOT-ALLOWED');
}
}
updateNodePermissionByDialog() {
this.nodePermissionDialogService
.openAddPermissionDialog(this.node, this.roles)
.pipe(
switchMap(selection => {
const total = selection.length;
const group = selection.filter(({authorityId}) => this.isGroup(authorityId)).length;
return forkJoin({
user: of(total - group),
group: of(group),
node: this.nodePermissionService.updateNodePermissions(this.node.id, selection)
});
})
)
.subscribe(({ user, group, node}) => {
this.notificationService.showInfo( 'PERMISSION_MANAGER.MESSAGE.PERMISSION-ADD-SUCCESS', null, { user, group });
this.reloadNode(node);
},
() => {
this.notificationService.showError( 'PERMISSION_MANAGER.MESSAGE.PERMISSION-ADD-FAIL');
this.reloadNode();
}
);
}
deletePermissions(permissions: PermissionElement[]) {
this.nodePermissionService.removePermissions(this.node, permissions)
.subscribe((node) => {
const total = permissions.length;
const group = permissions.filter(({authorityId}) => this.isGroup(authorityId)).length;
this.notificationService.showInfo('PERMISSION_MANAGER.MESSAGE.PERMISSION-BULK-DELETE-SUCCESS', null, {user: total - group, group});
this.reloadNode(node);
},
() => {
this.notificationService.showError('PERMISSION_MANAGER.MESSAGE.PERMISSION-DELETE-FAIL');
this.reloadNode();
}
);
}
updateRole(role: string, permission: PermissionDisplayModel) {
const updatedPermissionRole = this.buildUpdatedPermission(role, permission);
this.nodePermissionService.updatePermissionRole(this.node, updatedPermissionRole)
.subscribe((node) => {
this.notificationService.showInfo('PERMISSION_MANAGER.MESSAGE.PERMISSION-UPDATE-SUCCESS');
this.reloadNode(node);
this.updated.emit(permission);
},
() => {
this.notificationService.showError('PERMISSION_MANAGER.MESSAGE.PERMISSION-UPDATE-FAIL');
this.reloadNode();
this.errored.emit(permission);
}
);
}
bulkRoleUpdate(role: string) {
const permissions = [...this.node.permissions.locallySet] .map((permission) => this.buildUpdatedPermission(role, permission));
this.nodePermissionService.updatePermissions(this.node, permissions)
.subscribe((node) => {
const total = permissions.length;
const group = permissions.filter(({authorityId}) => this.isGroup(authorityId)).length;
this.notificationService.showInfo('PERMISSION_MANAGER.MESSAGE.PERMISSION-BULK-UPDATE-SUCCESS', null, {user: total - group, group});
this.reloadNode(node);
},
() => {
this.notificationService.showError('PERMISSION_MANAGER.MESSAGE.PERMISSION-UPDATE-FAIL');
this.reloadNode();
}
);
}
deletePermission(permission: PermissionDisplayModel) {
this.nodePermissionService
.removePermission(this.node, permission)
.subscribe((node) => {
this.notificationService.showInfo('PERMISSION_MANAGER.MESSAGE.PERMISSION-DELETE-SUCCESS');
this.reloadNode(node);
},
() => {
this.notificationService.showError('PERMISSION_MANAGER.MESSAGE.PERMISSION-DELETE-FAIL');
this.reloadNode();
}
);
}
private buildUpdatedPermission(role: string, permission: PermissionElement): PermissionElement {
return {
accessStatus: permission.accessStatus,
name: role,
authorityId: permission.authorityId
};
}
private reloadNode(node?: Node) {
if (node != null) {
Object.assign(this.node.permissions, node.permissions);
}
this.nodeWithRoles$.next({ node: this.node, roles: this.roles });
}
private isGroup(authorityId) {
return authorityId.startsWith('GROUP_') || authorityId.startsWith('ROLE_');
}
}

View File

@ -0,0 +1,104 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { AfterViewInit, Directive, ElementRef, Input, OnDestroy, OnInit, TemplateRef, ViewContainerRef } from '@angular/core';
import { ConnectionPositionPair, Overlay, OverlayRef } from '@angular/cdk/overlay';
import { TemplatePortal } from '@angular/cdk/portal';
import { takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs';
@Directive({
selector: '[adf-pop-over]',
exportAs: 'adfPopOver'
})
export class PopOverDirective implements OnInit, OnDestroy, AfterViewInit {
get open(): boolean {
return this._open;
}
@Input('adf-pop-over') popOver!: TemplateRef<object>;
@Input() target!: HTMLElement;
@Input() panelClass = 'adf-permission-pop-over';
private _open = false;
private destroy$ = new Subject();
private overlayRef!: OverlayRef;
constructor(
private element: ElementRef,
private overlay: Overlay,
private vcr: ViewContainerRef
) { }
ngOnInit(): void {
this.createOverlay();
}
ngAfterViewInit(): void {
this.element.nativeElement.addEventListener('click', () => this.attachOverlay());
}
ngOnDestroy(): void {
this.detachOverlay();
this.destroy$.next();
this.destroy$.complete();
}
private createOverlay(): void {
const scrollStrategy = this.overlay.scrollStrategies.reposition();
const positionStrategy = this.overlay
.position()
.flexibleConnectedTo(this.target)
.withPositions([
new ConnectionPositionPair({ originX: 'start', originY: 'bottom' }, { overlayX: 'start', overlayY: 'top' }),
new ConnectionPositionPair({ originX: 'start', originY: 'top' }, { overlayX: 'start', overlayY: 'bottom' })
])
.withPush(false);
this.overlayRef = this.overlay.create({
positionStrategy,
scrollStrategy,
hasBackdrop: true,
backdropClass: 'cdk-overlay-transparent-backdrop',
panelClass: this.panelClass
});
this.overlayRef
.backdropClick()
.pipe(takeUntil(this.destroy$))
.subscribe(() => {
this._open = false;
this.detachOverlay();
});
}
private attachOverlay(): void {
if (!this.overlayRef.hasAttached()) {
const periodSelectorPortal = new TemplatePortal(this.popOver, this.vcr);
this.overlayRef.attach(periodSelectorPortal);
this._open = true;
}
}
private detachOverlay(): void {
if (this.overlayRef.hasAttached()) {
this.overlayRef.detach();
}
}
}

View File

@ -0,0 +1,34 @@
@mixin adf-user-icon-column-theme($theme) {
$primary: map-get($theme, primary);
$foreground: map-get($theme, foreground);
.adf {
&-people-initial {
background: mat-color($primary);
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
width: 40px;
height: 40px;
color: mat-color($foreground, text);
font-weight: bolder;
font-size: 18px;
text-transform: uppercase;
}
&-people-icon {
height: 20px !important;
width: 20px !important;
background: mat-color($primary);
border-radius: 50%;
padding: 10px;
color: mat-color($foreground, text);
font-weight: bolder;
font-size: 20px;
}
&-people-select-icon {
padding: 10px;
}
}
}

View File

@ -0,0 +1,118 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { setupTestBed } from '@alfresco/adf-core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { TranslateModule } from '@ngx-translate/core';
import { ContentTestingModule } from '../../../testing/content.testing.module';
import { UserIconColumnComponent } from './user-icon-column.component';
import { NodeEntry } from '@alfresco/js-api';
describe('UserIconColumnComponent', () => {
let fixture: ComponentFixture<UserIconColumnComponent>;
let component: UserIconColumnComponent;
let element: HTMLElement;
const person = {
firstName: 'fake',
lastName: 'user',
email: 'fake@test.com'
};
const group = {
id: 'fake-id',
displayName: 'fake authority'
};
setupTestBed({
imports: [
TranslateModule.forRoot(),
ContentTestingModule
]
});
beforeEach(() => {
fixture = TestBed.createComponent(UserIconColumnComponent);
component = fixture.componentInstance;
element = fixture.nativeElement;
fixture.detectChanges();
});
describe('person initial', () => {
it('should render person value from context', () => {
component.context = {
row: {
obj: {
person
}
}
};
component.ngOnInit();
fixture.detectChanges();
expect(element.querySelector('[id="user-initials-image"]').textContent).toContain('fu');
});
it('should render person value from node', () => {
component.node = {
entry: {
nodeType: 'cm:person',
properties: {
'cm:firstName': 'Fake',
'cm:lastName': 'User',
'cm:email': 'fake-user@test.com',
'cm:userName': 'fake-user'
}
}
} as NodeEntry;
component.ngOnInit();
fixture.detectChanges();
expect(element.querySelector('[id="user-initials-image"]').textContent).toContain('FU');
});
});
describe('group initial', () => {
it('should render group value from context', () => {
component.context = {
row: {
obj: {
group
}
}
};
component.ngOnInit();
fixture.detectChanges();
expect(element.querySelector('[id="group-icon"] mat-icon')).toBeDefined();
expect(element.querySelector('[id="group-icon"] mat-icon').textContent).toContain('people_alt_outline');
});
it('should render person value from node', () => {
component.node = {
entry: {
nodeType: 'cm:authorityContainer',
properties: {
'cm:authorityName': 'Fake authorityN'
}
}
} as NodeEntry;
component.ngOnInit();
fixture.detectChanges();
expect(element.querySelector('[id="group-icon"] mat-icon')).toBeDefined();
expect(element.querySelector('[id="group-icon"] mat-icon').textContent).toContain('people_alt_outline');
});
});
});

View File

@ -0,0 +1,69 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { User } from '@alfresco/adf-core';
import { NodeEntry } from '@alfresco/js-api';
import { Component, Input, OnInit } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { NodePermissionService } from '../../services/node-permission.service';
@Component({
selector: 'adf-user-icon-column',
template: `
<div class="adf-cell-value" [attr.id]="group ? 'group-icon' : 'person-icon'" *ngIf="!context?.row?.isSelected">
<ng-container *ngIf="displayText$ | async as user">
<mat-icon *ngIf="group" class="adf-people-icon">people_alt_outline</mat-icon>
<div *ngIf="!group" [outerHTML]="user | usernameInitials: 'adf-people-initial'"></div>
</ng-container>
</div>
<div class="adf-cell-value" *ngIf="context?.row?.isSelected">
<mat-icon class="adf-people-select-icon" svgIcon="selected"></mat-icon>
</div>
`,
styleUrls: ['./user-icon-column.component.scss'],
host: { class: 'adf-user-icon-column adf-datatable-content-cell' }
})
export class UserIconColumnComponent implements OnInit {
@Input()
context: any;
@Input()
node: NodeEntry;
displayText$ = new BehaviorSubject<User>(null);
group = false;
constructor(private nodePermissionService: NodePermissionService) {}
ngOnInit() {
if (this.context) {
const { person, group, authorityId } = this.context.row.obj?.entry ?? this.context.row.obj;
this.group = this.isGroup(group, authorityId);
this.displayText$.next(person || group || { displayName: authorityId });
}
if (this.node) {
const { person, group } = this.nodePermissionService.transformNodeToUserPerson(this.node.entry);
this.group = this.isGroup(group, null);
this.displayText$.next(person || group);
}
}
private isGroup(group, authorityId): boolean {
return !!group || authorityId?.startsWith('GROUP_') || authorityId?.startsWith('ROLE_');
}
}

View File

@ -0,0 +1,24 @@
@mixin member-theme($theme) {
$primary: map-get($theme, primary);
$foreground: map-get($theme, foreground);
.adf-user {
display: flex;
flex-direction: column;
&-name-column {
padding: 2px 10px;
font-weight: 600;
font-size: 14px;
}
&-email-column {
padding: 2px 10px;
font-size: 14px;
letter-spacing: -0.2px;
line-height: 1.43;
color: mat-color($foreground, text, 0.72);
}
}
}

View File

@ -0,0 +1,136 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { setupTestBed } from '@alfresco/adf-core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { TranslateModule } from '@ngx-translate/core';
import { ContentTestingModule } from '../../../testing/content.testing.module';
import { UserNameColumnComponent } from './user-name-column.component';
import { NodeEntry } from '@alfresco/js-api';
describe('UserNameColumnComponent', () => {
let fixture: ComponentFixture<UserNameColumnComponent>;
let component: UserNameColumnComponent;
let element: HTMLElement;
const person = {
firstName: 'fake',
lastName: 'user',
email: 'fake@test.com'
};
const group = {
id: 'fake-id',
displayName: 'fake authority'
};
setupTestBed({
imports: [
TranslateModule.forRoot(),
ContentTestingModule
]
});
beforeEach(() => {
fixture = TestBed.createComponent(UserNameColumnComponent);
component = fixture.componentInstance;
element = fixture.nativeElement;
fixture.detectChanges();
});
describe('person name', () => {
it('should render person value from context', () => {
component.context = {
row: {
obj: {
person
}
}
};
component.ngOnInit();
fixture.detectChanges();
expect(element.querySelector('[title="fake user"]').textContent).toContain('fake user');
expect(element.querySelector('[title="fake@test.com"]').textContent).toContain('fake@test.com');
});
it('should render person value from node', (done) => {
component.node = {
entry: {
nodeType: 'cm:person',
properties: {
'cm:firstName': 'Fake',
'cm:lastName': 'User',
'cm:email': 'fake-user@test.com',
'cm:userName': 'fake-user'
}
}
} as NodeEntry;
const subscription = component.displayText$.subscribe((fullName) => {
if (fullName) {
expect(fullName).toBe('Fake User');
subscription.unsubscribe();
done();
}
});
component.ngOnInit();
});
});
describe('group name', () => {
it('should render group value from context', () => {
component.context = {
row: {
obj: {
group
}
}
};
component.ngOnInit();
fixture.detectChanges();
expect(element.querySelector('[title="fake authority"]').textContent.trim()).toBe('fake authority');
});
it('should render group for authorityId', () => {
component.context = {
row: {
obj: {
authorityId: 'fake-id'
}
}
};
component.ngOnInit();
fixture.detectChanges();
expect(element.querySelector('[title=fake-id]').textContent.trim()).toBe('fake-id');
});
it('should render person value from node', () => {
component.node = {
entry: {
nodeType: 'cm:authorityContainer',
properties: {
'cm:authorityName': 'Fake authority'
}
}
} as NodeEntry;
component.ngOnInit();
fixture.detectChanges();
expect(element.querySelector('[title="Fake authority"]').textContent.trim()).toBe('Fake authority');
});
});
});

View File

@ -0,0 +1,74 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Component, Input, OnInit } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { Group, NodeEntry, Person } from '@alfresco/js-api';
import { NodePermissionService } from '../../services/node-permission.service';
@Component({
selector: 'adf-user-name-column',
template: `
<div class="adf-ellipsis-cell adf-user" [attr.data-automation-id]="displayText$ | async">
<span title="{{ displayText$ | async }}"> {{ displayText$ | async }}</span>
<br/>
<span title="{{ subTitleText$ | async }}">{{ subTitleText$ | async }}</span>
</div>
`,
host: { class: 'adf-user-name-column adf-datatable-content-cell adf-expand-cell-5 adf-ellipsis-cell' },
styleUrls: [ './user-name-column.component.scss' ]
})
export class UserNameColumnComponent implements OnInit {
@Input()
context: any;
@Input()
node: NodeEntry;
displayText$ = new BehaviorSubject<string>('');
subTitleText$ = new BehaviorSubject<string>('');
constructor(private nodePermissionService: NodePermissionService) {}
ngOnInit() {
if (this.context != null) {
const { person, group, authorityId } = this.context.row.obj?.entry ?? this.context.row.obj;
const permissionGroup = authorityId ? { displayName: authorityId } as Group : null;
this.updatePerson(person);
this.updateGroup(group || permissionGroup);
}
if (this.node) {
const { person, group } = this.nodePermissionService.transformNodeToUserPerson(this.node.entry);
this.updatePerson(person);
this.updateGroup(group);
}
}
private updatePerson(person: Person) {
if (person) {
this.displayText$.next(`${person.firstName ?? ''} ${person.lastName ?? ''}`);
this.subTitleText$.next(person.email ?? '');
}
}
private updateGroup(group: Group) {
if (group) {
this.displayText$.next(group.displayName);
}
}
}

View File

@ -0,0 +1,69 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { RoleModel } from '../../models/role.model';
@Component({
selector: 'adf-user-role-column',
template: `
<mat-form-field floatLabel="never" class="adf-role-selector-field" *ngIf="!readonly">
<mat-select
(click)="$event.stopPropagation()"
[placeholder]="placeholder | translate"
[value]="value"
(selectionChange)="onRoleChanged($event.value)">
<mat-option *ngFor="let role of roles" [value]="role.role">
{{ role.label | adfLocalizedRole }}
</mat-option>
</mat-select>
</mat-form-field>
<span class="adf-datatable-cell-value" [title]="value | adfLocalizedRole" *ngIf="readonly">
{{value | adfLocalizedRole}}
</span>
`,
host: { class: 'adf-user-role-column adf-datatable-content-cell adf-expand-cell-4' },
styles: [
`.adf-role-selector-field {
width: 100%;
}
`
]
})
export class UserRoleColumnComponent {
@Input()
roles: RoleModel[];
@Input()
value: string;
@Input()
readonly = false;
@Input()
placeholder: string = 'PERMISSION_MANAGER.LABELS.SELECT-ROLE';
@Output()
roleChanged: EventEmitter<string> = new EventEmitter<string>();
onRoleChanged(newRole: string) {
this.value = newRole;
this.roleChanged.emit(newRole);
}
}

View File

@ -0,0 +1,85 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Group, Node, NodeEntry, PermissionElement, Person } from '@alfresco/js-api';
import { PermissionDisplayModel } from './permission.model';
import { RoleModel } from './role.model';
export interface NodePermissionsModel {
node: Node;
roles: RoleModel[];
inheritedPermissions: PermissionDisplayModel[];
localPermissions: PermissionDisplayModel[];
}
export class MemberModel {
id: string;
role: string;
accessStatus: PermissionElement.AccessStatusEnum | string;
entry: {
person?: Person;
group?: Group;
};
readonly: boolean = false;
constructor(input?) {
if (input) {
Object.assign(this, input);
}
}
static parseFromSearchResult({ entry }: NodeEntry): MemberModel {
const result = new MemberModel();
if (entry.nodeType === 'cm:person') {
const person = new Person({
firstName: entry.properties['cm:firstName'],
lastName: entry.properties['cm:lastName'],
email: entry.properties['cm:email'],
id: entry.properties['cm:userName']
});
result.id = person.id;
result.entry = { person };
result.accessStatus = 'ALLOWED';
return result;
}
if (entry.nodeType === 'cm:authorityContainer') {
const group = new Group({
id: entry.properties['cm:authorityName'],
displayName: entry.properties['cm:authorityDisplayName'] || entry.properties['cm:authorityName']
});
result.id = group.id;
result.entry = { group };
result.accessStatus = 'ALLOWED';
return result;
}
return null;
}
toPermissionElement(): PermissionElement {
return {
authorityId: this.id,
name: this.role,
accessStatus: this.accessStatus
};
}
}

View File

@ -15,12 +15,7 @@
* limitations under the License.
*/
/* tslint:disable:no-input-rename */
import { Component } from '@angular/core';
@Component({
selector: 'adf-no-permission-template',
template: '<ng-content></ng-content>'
})
export class NoPermissionTemplateComponent {}
export interface RoleModel {
label: string;
role: string;
}

View File

@ -15,6 +15,7 @@
* limitations under the License.
*/
import { CoreModule, PipeModule } from '@alfresco/adf-core';
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
@ -22,11 +23,15 @@ import { MaterialModule } from '../material.module';
import { PermissionListComponent } from './components/permission-list/permission-list.component';
import { AddPermissionComponent } from './components/add-permission/add-permission.component';
import { AddPermissionDialogComponent } from './components/add-permission/add-permission-dialog.component';
import { CoreModule, PipeModule } from '@alfresco/adf-core';
import { InheritPermissionDirective } from './components/inherited-button.directive';
import { NoPermissionTemplateComponent } from './components/permission-list/no-permission.component';
import { AddPermissionPanelComponent } from './components/add-permission/add-permission-panel.component';
import { SearchModule } from '../search/search.module';
import { UserNameColumnComponent } from './components/user-name-column/user-name-column.component';
import { UserIconColumnComponent } from './components/user-icon-column/user-icon-column.component';
import { UserRoleColumnComponent } from './components/user-role-column/user-role-column.component';
import { NodePathColumnComponent } from './components/node-path-column/node-path-column.component';
import { PopOverDirective } from './components/pop-over.directive';
import { PermissionContainerComponent } from './components/permission-container/permission-container.component';
@NgModule({
imports: [
@ -40,19 +45,28 @@ import { SearchModule } from '../search/search.module';
],
declarations: [
PermissionListComponent,
NoPermissionTemplateComponent,
AddPermissionPanelComponent,
InheritPermissionDirective,
AddPermissionComponent,
AddPermissionDialogComponent
AddPermissionDialogComponent,
UserNameColumnComponent,
UserIconColumnComponent,
UserRoleColumnComponent,
PopOverDirective,
NodePathColumnComponent,
PermissionContainerComponent
],
exports: [
PermissionListComponent,
NoPermissionTemplateComponent,
AddPermissionPanelComponent,
InheritPermissionDirective,
AddPermissionComponent,
AddPermissionDialogComponent
AddPermissionDialogComponent,
UserNameColumnComponent,
UserIconColumnComponent,
UserRoleColumnComponent,
PopOverDirective,
NodePathColumnComponent
]
})
export class PermissionManagerModule {}

View File

@ -16,7 +16,6 @@
*/
export * from './components/permission-list/permission-list.component';
export * from './components/permission-list/no-permission.component';
export * from './components/inherited-button.directive';
export * from './models/permission.model';
export * from './services/node-permission-dialog.service';
@ -26,5 +25,13 @@ export * from './components/add-permission/add-permission-panel.component';
export * from './components/add-permission/add-permission.component';
export * from './components/add-permission/add-permission-dialog.component';
export * from './components/add-permission/search-config-permission.service';
export * from './components/user-icon-column/user-icon-column.component';
export * from './components/user-name-column/user-name-column.component';
export * from './components/user-role-column/user-role-column.component';
export * from './components/node-path-column/node-path-column.component';
export * from './components/permission-container/permission-container.component';
export * from './components/pop-over.directive';
export * from './models/member.model';
export * from './models/role.model';
export * from './permission-manager.module';

View File

@ -16,10 +16,10 @@
*/
import { TestBed } from '@angular/core/testing';
import { AppConfigService, setupTestBed, ContentService } from '@alfresco/adf-core';
import { AppConfigService, setupTestBed } from '@alfresco/adf-core';
import { NodePermissionDialogService } from './node-permission-dialog.service';
import { MatDialog } from '@angular/material/dialog';
import { Subject, of, throwError } from 'rxjs';
import { of, Subject, throwError } from 'rxjs';
import { ContentTestingModule } from '../../testing/content.testing.module';
import { NodePermissionService } from './node-permission.service';
import { Node } from '@alfresco/js-api';
@ -32,7 +32,6 @@ describe('NodePermissionDialogService', () => {
let spyOnDialogOpen: jasmine.Spy;
let afterOpenObservable: Subject<any>;
let nodePermissionService: NodePermissionService;
let contentService: ContentService;
setupTestBed({
imports: [
@ -48,7 +47,6 @@ describe('NodePermissionDialogService', () => {
materialDialog = TestBed.inject(MatDialog);
afterOpenObservable = new Subject<any>();
nodePermissionService = TestBed.inject(NodePermissionService);
contentService = TestBed.inject(ContentService);
spyOnDialogOpen = spyOn(materialDialog, 'open').and.returnValue({
afterOpen: () => afterOpenObservable,
afterClosed: () => of({}),
@ -67,14 +65,14 @@ describe('NodePermissionDialogService', () => {
});
it('should be able to open the dialog showing node permissions', () => {
service.openAddPermissionDialog(fakePermissionNode, 'fake-title');
service.openAddPermissionDialog(fakePermissionNode, [], 'fake-title');
expect(spyOnDialogOpen).toHaveBeenCalled();
});
it('should return the updated node', (done) => {
spyOn(nodePermissionService, 'updateNodePermissions').and.returnValue(of({id : 'fake-node-updated'}));
spyOn(service, 'openAddPermissionDialog').and.returnValue(of({}));
spyOn(contentService, 'getNode').and.returnValue(of(fakePermissionNode));
spyOn(nodePermissionService, 'getNodeWithRoles').and.returnValue(of({ node: fakePermissionNode, roles: [] }));
service.updateNodePermissionByDialog('fake-node-id', 'fake-title').subscribe((node) => {
expect(node.id).toBe('fake-node-updated');
done();
@ -84,7 +82,7 @@ describe('NodePermissionDialogService', () => {
it('should throw an error if the update of the node fails', (done) => {
spyOn(nodePermissionService, 'updateNodePermissions').and.returnValue(throwError({error : 'error'}));
spyOn(service, 'openAddPermissionDialog').and.returnValue(of({}));
spyOn(contentService, 'getNode').and.returnValue(of(fakePermissionNode));
spyOn(nodePermissionService, 'getNodeWithRoles').and.returnValue(of({ node: fakePermissionNode, roles: [] }));
service.updateNodePermissionByDialog('fake-node-id', 'fake-title').subscribe(() => {
throwError('This call should fail');
}, (error) => {
@ -103,12 +101,12 @@ describe('NodePermissionDialogService', () => {
});
it('should not be able to open the dialog showing node permissions', () => {
service.openAddPermissionDialog(fakeForbiddenNode, 'fake-title');
service.openAddPermissionDialog(fakeForbiddenNode, [], 'fake-title');
expect(spyOnDialogOpen).not.toHaveBeenCalled();
});
it('should return the updated node', (done) => {
spyOn(contentService, 'getNode').and.returnValue(of(fakeForbiddenNode));
spyOn(nodePermissionService, 'getNodeWithRoles').and.returnValue(of({ node: fakeForbiddenNode, roles: [] }));
service.updateNodePermissionByDialog('fake-node-id', 'fake-title').subscribe(() => {
throwError('This call should fail');
},

View File

@ -15,15 +15,16 @@
* limitations under the License.
*/
import { AllowableOperationsEnum, ContentService } from '@alfresco/adf-core';
import { Node, PermissionElement } from '@alfresco/js-api';
import { MatDialog } from '@angular/material/dialog';
import { Injectable } from '@angular/core';
import { Subject, Observable, throwError } from 'rxjs';
import { Observable, Subject, throwError } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import { NodePermissionService } from './node-permission.service';
import { AddPermissionDialogComponent } from '../components/add-permission/add-permission-dialog.component';
import { AddPermissionDialogData } from '../components/add-permission/add-permission-dialog-data.interface';
import { NodeEntry, Node } from '@alfresco/js-api';
import { NodePermissionService } from './node-permission.service';
import { ContentService, AllowableOperationsEnum } from '@alfresco/adf-core';
import { switchMap } from 'rxjs/operators';
import { RoleModel } from '../models/role.model';
@Injectable({
providedIn: 'root'
@ -37,25 +38,27 @@ export class NodePermissionDialogService {
/**
* Opens a dialog to add permissions to a node.
* @param node ID of the target node
* @param node target node
* @param roles settable roles for the node
* @param title Dialog title
* @returns Node with updated permissions
*/
openAddPermissionDialog(node: Node, title?: string): Observable<NodeEntry[]> {
openAddPermissionDialog(node: Node, roles: RoleModel[], title?: string): Observable<PermissionElement[]> {
if (this.contentService.hasAllowableOperations(node, AllowableOperationsEnum.UPDATEPERMISSIONS)) {
const confirm = new Subject<NodeEntry[]>();
const confirm = new Subject<PermissionElement[]>();
confirm.subscribe({
complete: this.close.bind(this)
});
const data: AddPermissionDialogData = {
nodeId: node.id,
node: node,
title: title,
confirm: confirm
confirm: confirm,
roles
};
this.openDialog(data, 'adf-add-permission-dialog', '630px');
this.openDialog(data, 'adf-add-permission-dialog', '800px');
return confirm;
} else {
const errors = new Error(JSON.stringify({ error: { statusCode: 403 } }));
@ -65,7 +68,7 @@ export class NodePermissionDialogService {
}
private openDialog(data: any, currentPanelClass: string, chosenWidth: string) {
this.dialog.open(AddPermissionDialogComponent, { data, panelClass: currentPanelClass, width: chosenWidth });
this.dialog.open(AddPermissionDialogComponent, { data, panelClass: currentPanelClass, width: chosenWidth, restoreFocus: true });
}
/**
@ -82,10 +85,10 @@ export class NodePermissionDialogService {
* @returns Node with updated permissions
*/
updateNodePermissionByDialog(nodeId?: string, title?: string): Observable<Node> {
return this.contentService.getNode(nodeId, { include: ['allowableOperations'] })
return this.nodePermissionService.getNodeWithRoles(nodeId)
.pipe(
switchMap((node) => {
return this.openAddPermissionDialog(node.entry, title)
switchMap(({node, roles}) => {
return this.openAddPermissionDialog(node, roles, title)
.pipe(
switchMap((selection) => {
return this.nodePermissionService.updateNodePermissions(nodeId, selection);

View File

@ -31,6 +31,23 @@ describe('NodePermissionService', () => {
let service: NodePermissionService;
let nodeService: NodesApiService;
let searchApiService: SearchService;
const fakePermissionElements: PermissionElement[] = [
{
authorityId: fakeAuthorityResults[0].entry.properties['cm:userName'],
name: 'Consumer',
accessStatus: 'ALLOWED'
},
{
authorityId: fakeAuthorityResults[1].entry.properties['cm:userName'],
name: 'Consumer',
accessStatus: 'ALLOWED'
},
{
authorityId: fakeAuthorityResults[2].entry.properties['cm:authorityName'],
name: 'Consumer',
accessStatus: 'ALLOWED'
}
];
setupTestBed({
imports: [
@ -87,7 +104,7 @@ describe('NodePermissionService', () => {
spyOn(nodeService, 'updateNode').and.callFake((nodeId, permissionBody) => returnUpdatedNode(nodeId, permissionBody));
service.updatePermissionRole(fakeNodeWithOnlyLocally, fakePermission).subscribe((node: Node) => {
service.updatePermissionRole(JSON.parse(JSON.stringify(fakeNodeWithOnlyLocally)), fakePermission).subscribe((node: Node) => {
expect(node).not.toBeNull();
expect(node.id).toBe('fake-updated-node');
expect(node.permissions.locallySet.length).toBe(1);
@ -119,10 +136,8 @@ describe('NodePermissionService', () => {
const fakeNodeCopy = JSON.parse(JSON.stringify(fakeNodeWithOnlyLocally));
spyOn(nodeService, 'getNode').and.returnValue(of(fakeNodeCopy));
spyOn(nodeService, 'updateNode').and.callFake((nodeId, permissionBody) => returnUpdatedNode(nodeId, permissionBody));
spyOn(searchApiService, 'searchByQueryBody').and.returnValue(of(fakeSiteNodeResponse));
spyOn(service, 'getGroupMemberByGroupName').and.returnValue(of(fakeSiteRoles));
service.updateNodePermissions('fake-node-id', fakeAuthorityResults).subscribe((node: Node) => {
service.updateNodePermissions('fake-node-id', fakePermissionElements).subscribe((node: Node) => {
expect(node).not.toBeNull();
expect(node.id).toBe('fake-updated-node');
expect(node.permissions.locallySet.length).toBe(4);
@ -136,7 +151,7 @@ describe('NodePermissionService', () => {
const fakeNodeCopy = JSON.parse(JSON.stringify(fakeNodeWithOnlyLocally));
spyOn(nodeService, 'updateNode').and.callFake((nodeId, permissionBody) => returnUpdatedNode(nodeId, permissionBody));
service.updateLocallySetPermissions(fakeNodeCopy, fakeAuthorityResults, fakeSiteRoles).subscribe((node: Node) => {
service.updateLocallySetPermissions(fakeNodeCopy, fakePermissionElements).subscribe((node: Node) => {
expect(node).not.toBeNull();
expect(node.id).toBe('fake-updated-node');
expect(node.permissions.locallySet.length).toBe(4);
@ -150,8 +165,7 @@ describe('NodePermissionService', () => {
const fakeNodeCopy = JSON.parse(JSON.stringify(fakeNodeWithoutPermissions));
fakeNodeCopy.permissions.locallySet = undefined;
spyOn(nodeService, 'updateNode').and.callFake((nodeId, permissionBody) => returnUpdatedNode(nodeId, permissionBody));
service.updateLocallySetPermissions(fakeNodeCopy, fakeAuthorityResults, fakeSiteRoles).subscribe((node: Node) => {
service.updateLocallySetPermissions(fakeNodeCopy, fakePermissionElements).subscribe((node: Node) => {
expect(node).not.toBeNull();
expect(node.id).toBe('fake-updated-node');
expect(node.permissions.locallySet.length).toBe(3);
@ -164,31 +178,58 @@ describe('NodePermissionService', () => {
it('should fail when user select the same authority and role to add', async(() => {
const fakeNodeCopy = JSON.parse(JSON.stringify(fakeNodeWithOnlyLocally));
const fakeDuplicateAuthority: any = [{
'entry': {
'isFolder': false,
'search': {
'score': 0.3541112
},
'isFile': false,
'name': 'GROUP_EVERYONE',
'location': 'nodes',
'id': 'GROUP_EVERYONE',
'nodeType': 'cm:authorityContainer',
'properties': {
'cm:authorityName': 'GROUP_EVERYONE'
},
'parentId': '030d833e-da8e-4f5c-8ef9-d809638bd04b'
}
const fakeDuplicateAuthority: PermissionElement [] = [{
authorityId: 'GROUP_EVERYONE',
accessStatus: 'ALLOWED',
name: 'Contributor'
}];
service.updateLocallySetPermissions(fakeNodeCopy, fakeDuplicateAuthority, ['Contributor'])
service.updateLocallySetPermissions(fakeNodeCopy, fakeDuplicateAuthority)
.subscribe(() => {
fail('should throw exception');
}, (errorMessage) => {
expect(errorMessage).not.toBeNull();
expect(errorMessage).toBeDefined();
expect(errorMessage).toBe('PERMISSION_MANAGER.ERROR.DUPLICATE-PERMISSION');
});
}));
it('should be able to remove the locallyset permission', async(() => {
const fakeNodeCopy = JSON.parse(JSON.stringify(fakeNodeWithoutPermissions));
fakeNodeCopy.permissions.locallySet = [...fakePermissionElements];
spyOn(nodeService, 'updateNode').and.callFake((nodeId, permissionBody) => returnUpdatedNode(nodeId, permissionBody));
service.removePermissions(fakeNodeCopy, [fakePermissionElements[2]]).subscribe((node: Node) => {
expect(node).not.toBeNull();
expect(node.id).toBe('fake-updated-node');
expect(node.permissions.locallySet.length).toBe(2);
expect(node.permissions.locallySet[0].authorityId).toBe(fakePermissionElements[0].authorityId);
expect(node.permissions.locallySet[1].authorityId).toBe(fakePermissionElements[1].authorityId);
});
}));
it('should be able to replace the locally set', async(() => {
const fakeNodeCopy = JSON.parse(JSON.stringify(fakeNodeWithOnlyLocally));
fakeNodeCopy.permissions.locallySet = [];
spyOn(nodeService, 'updateNode').and.callFake((nodeId, permissionBody) => returnUpdatedNode(nodeId, permissionBody));
service.updatePermissions(fakeNodeCopy, fakePermissionElements).subscribe((node: Node) => {
expect(node).not.toBeNull();
expect(node.id).toBe('fake-updated-node');
expect(node.permissions.locallySet.length).toBe(3);
expect(node.permissions.locallySet[0].authorityId).toBe(fakePermissionElements[0].authorityId);
expect(node.permissions.locallySet[1].authorityId).toBe(fakePermissionElements[1].authorityId);
expect(node.permissions.locallySet[2].authorityId).toBe(fakePermissionElements[2].authorityId);
});
}));
it('should be able to get node and it\'s roles', async(() => {
const fakeNodeCopy = JSON.parse(JSON.stringify(fakeNodeWithOnlyLocally));
spyOn(nodeService, 'getNode').and.returnValue(of(fakeNodeCopy));
spyOn(searchApiService, 'searchByQueryBody').and.returnValue(of(fakeSiteNodeResponse));
spyOn(service, 'getGroupMemberByGroupName').and.returnValue(of(fakeSiteRoles));
service.getNodeWithRoles('node-id').subscribe(({ node, roles }) => {
expect(node).toBe(fakeNodeCopy);
expect(roles.length).toBe(4);
expect(roles[0].role).toBe('SiteCollaborator');
});
}));
});

View File

@ -15,12 +15,13 @@
* limitations under the License.
*/
import { AlfrescoApiService, NodesApiService, SearchService, TranslationService } from '@alfresco/adf-core';
import { Group, GroupMemberEntry, GroupMemberPaging, Node, PathElement, PermissionElement, Person, QueryBody } from '@alfresco/js-api';
import { Injectable } from '@angular/core';
import { Observable, of, from, throwError } from 'rxjs';
import { AlfrescoApiService, SearchService, NodesApiService, TranslationService } from '@alfresco/adf-core';
import { QueryBody, Node, NodeEntry, PathElement, GroupMemberEntry, GroupMemberPaging, PermissionElement } from '@alfresco/js-api';
import { switchMap, map } from 'rxjs/operators';
import { forkJoin, from, Observable, of, throwError } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';
import { PermissionDisplayModel } from '../models/permission.model';
import { RoleModel } from '../models/role.model';
@Injectable({
providedIn: 'root'
@ -96,27 +97,21 @@ export class NodePermissionService {
* @param permissionList New permission settings
* @returns Node with updated permissions
*/
updateNodePermissions(nodeId: string, permissionList: NodeEntry[]): Observable<Node> {
updateNodePermissions(nodeId: string, permissionList: PermissionElement[]): Observable<Node> {
return this.nodeService.getNode(nodeId).pipe(
switchMap((node) => {
return this.getNodeRoles(node).pipe(
switchMap((nodeRoles) => of({node, nodeRoles}) )
);
}),
switchMap(({node, nodeRoles}) => this.updateLocallySetPermissions(node, permissionList, nodeRoles))
switchMap((node) => this.updateLocallySetPermissions(node, permissionList))
);
}
/**
* Updates the locally set permissions for a node.
* @param node ID of the target node
* @param nodes Permission settings
* @param nodeRole Permission role
* @param permissions Permission settings
* @returns Node with updated permissions
*/
updateLocallySetPermissions(node: Node, nodes: NodeEntry[], nodeRole: string[]): Observable<Node> {
updateLocallySetPermissions(node: Node, permissions: PermissionElement[]): Observable<Node> {
const permissionBody = { permissions: { locallySet: []} };
const permissionList = this.transformNodeToPermissionElement(nodes, nodeRole[0]);
const permissionList = permissions;
const duplicatedPermissions = this.getDuplicatedPermissions(node.permissions.locallySet, permissionList);
if (duplicatedPermissions.length > 0) {
const list = duplicatedPermissions.map((permission) => 'authority -> ' + permission.authorityId + ' / role -> ' + permission.name).join(', ');
@ -146,18 +141,6 @@ export class NodePermissionService {
oldPermission.name === newPermission.name;
}
private transformNodeToPermissionElement(nodes: NodeEntry[], nodeRole: any): PermissionElement[] {
return nodes.map((node) => {
return {
'authorityId': node.entry.properties['cm:authorityName'] ?
node.entry.properties['cm:authorityName'] :
node.entry.properties['cm:userName'],
'name': nodeRole,
'accessStatus': 'ALLOWED'
};
});
}
/**
* Removes a permission setting from a node.
* @param node ID of the target node
@ -227,4 +210,97 @@ export class NodePermissionService {
};
}
getLocalPermissions(node: Node): PermissionDisplayModel[] {
const result: PermissionDisplayModel[] = [];
if (node?.permissions?.locallySet) {
node.permissions.locallySet.forEach((permissionElement) => {
result.push(new PermissionDisplayModel(permissionElement));
});
}
return result;
}
getInheritedPermission(node: Node): PermissionDisplayModel[] {
const result: PermissionDisplayModel[] = [];
if (node?.permissions?.inherited) {
node.permissions.inherited.forEach((permissionElement) => {
const permissionInherited = new PermissionDisplayModel(permissionElement);
permissionInherited.isInherited = true;
result.push(permissionInherited);
});
}
return result;
}
/**
* Removes permissions setting from a node.
* @param node target node with permission
* @param permissions Permissions to remove
* @returns Node with modified permissions
*/
removePermissions(node: Node, permissions: PermissionElement[]): Observable<Node> {
const permissionBody = { permissions: { locallySet: [] } };
permissions.forEach((permission) => {
const index = node.permissions.locallySet.findIndex((locallySet) => locallySet.authorityId === permission.authorityId);
if (index !== -1) {
node.permissions.locallySet.splice(index, 1);
}
});
permissionBody.permissions.locallySet = node.permissions.locallySet;
return this.nodeService.updateNode(node.id, permissionBody);
}
/**
* updates permissions setting from a node.
* @param node target node with permission
* @param permissions Permissions to update
* @returns Node with modified permissions
*/
updatePermissions(node: Node, permissions: PermissionElement[]): Observable<Node> {
const permissionBody = { permissions: { locallySet: [] } };
permissionBody.permissions.locallySet = permissions;
return this.nodeService.updateNode(node.id, permissionBody);
}
/**
* Gets all node detail for nodeId along with settable permissions.
* @param nodeId Id of the node
* @returns node and it's associated roles { node: Node; roles: RoleModel[] }
*/
getNodeWithRoles(nodeId: string): Observable<{ node: Node; roles: RoleModel[] }> {
return this.nodeService.getNode(nodeId).pipe(
switchMap(node => {
return forkJoin({
node: of(node),
roles: this.getNodeRoles(node)
.pipe(
map(_roles => _roles.map(role => ({ role, label: role }))
)
)
});
})
);
}
transformNodeToUserPerson(node: Node): { person: Person, group: Group } {
let person = null, group = null;
if (node.nodeType === 'cm:person') {
const firstName = node.properties['cm:firstName'];
const lastName = node.properties['cm:lastName'];
const email = node.properties['cm:email'];
const id = node.properties['cm:userName'];
person = new Person({ id, firstName, lastName, email});
}
if (node.nodeType === 'cm:authorityContainer') {
const displayName = node.properties['cm:authorityDisplayName'] || node.properties['cm:authorityName'];
const id = node.properties['cm:authorityName'];
group = new Group({ displayName, id });
}
return { person, group };
}
}

View File

@ -28,7 +28,9 @@
@import '../version-manager/version-comparison.component';
@import '../content-type/content-type-dialog.component';
@import '../aspect-list/aspect-list.component';
@import '../aspect-list//aspect-list-dialog.component';
@import '../aspect-list/aspect-list-dialog.component';
@import '../permission-manager/components/permission-container/permission-container.component';
@import '../permission-manager/components/user-icon-column/user-icon-column.component';
@mixin adf-content-services-theme($theme) {
@include adf-breadcrumb-theme($theme);
@ -48,6 +50,8 @@
@include adf-content-node-selector-dialog-theme($theme);
@include adf-content-metadata-module-theme($theme);
@include adf-permission-list-theme($theme);
@include adf-permission-container-theme($theme);
@include adf-user-icon-column-theme($theme);
@include adf-add-permission-theme($theme);
@include adf-add-permission-dialog-theme($theme);
@include adf-add-permission-panel-theme($theme);

View File

@ -0,0 +1,39 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* tslint:disable:component-selector no-input-rename */
import { Component, ContentChild, TemplateRef } from '@angular/core';
import { DataColumnComponent } from './data-column.component';
@Component({
selector: 'adf-data-column-header',
template: ''
})
export class DateColumnHeaderComponent {
@ContentChild(TemplateRef)
public template: TemplateRef<any>;
constructor(private columnComponent: DataColumnComponent) {}
ngAfterContentInit() {
if (this.columnComponent) {
this.columnComponent.header = this.template;
}
}
}

View File

@ -82,6 +82,9 @@ export class DataColumnComponent implements OnInit {
@Input()
sortingKey: string;
/** Data column header template */
header?: TemplateRef<any>;
ngOnInit() {
if (!this.srTitle && this.key === '$thumbnail') {
this.srTitle = 'Thumbnail';

View File

@ -20,6 +20,7 @@ import { NgModule } from '@angular/core';
import { DataColumnListComponent } from './data-column-list.component';
import { DataColumnComponent } from './data-column.component';
import { DateColumnHeaderComponent } from './data-column-header.component';
@NgModule({
imports: [
@ -27,11 +28,13 @@ import { DataColumnComponent } from './data-column.component';
],
declarations: [
DataColumnComponent,
DataColumnListComponent
DataColumnListComponent,
DateColumnHeaderComponent
],
exports: [
DataColumnComponent,
DataColumnListComponent
DataColumnListComponent,
DateColumnHeaderComponent
]
})
export class DataColumnModule {}

View File

@ -17,5 +17,6 @@
export * from './data-column-list.component';
export * from './data-column.component';
export * from './data-column-header.component';
export * from './data-column.module';

View File

@ -33,9 +33,12 @@
[attr.tabindex]="isHeaderVisible() ? 0 : null"
[attr.aria-sort]="col.sortable ? (getAriaSort(col) | translate) : null"
adf-drop-zone dropTarget="header" [dropColumn]="col">
<span *ngIf="col.title" class="adf-datatable-cell-value">{{ col.title | translate}}</span>
<span *ngIf="col.title && col.sortable" class="adf-sr-only" aria-live="polite">{{ getSortLiveAnnouncement(col) | translate: { string: col.title | translate } }}</span>
<ng-template *ngIf="allowFiltering" [ngTemplateOutlet]="headerFilterTemplate" [ngTemplateOutletContext]="{$implicit: col}"></ng-template>
<ng-container *ngIf="!col.header">
<span *ngIf="col.title" class="adf-datatable-cell-value">{{ col.title | translate}}</span>
<span *ngIf="col.title && col.sortable" class="adf-sr-only" aria-live="polite">{{ getSortLiveAnnouncement(col) | translate: { string: col.title | translate } }}</span>
<ng-template *ngIf="allowFiltering" [ngTemplateOutlet]="headerFilterTemplate" [ngTemplateOutletContext]="{$implicit: col}"></ng-template>
</ng-container>
<ng-template *ngIf="col.header" [ngTemplateOutlet]="col.header" [ngTemplateOutletContext]="{$implicit: col}"></ng-template>
</div>
<!-- Actions (right) -->
<div *ngIf="actions && actionsPosition === 'right'" class="adf-actions-column adf-datatable-cell-header adf-datatable__actions-cell">

View File

@ -43,4 +43,5 @@ export interface DataColumn {
editable?: boolean;
focus?: boolean;
sortingKey?: string;
header?: TemplateRef<any>;
}

View File

@ -32,6 +32,7 @@ export class ObjectDataColumn implements DataColumn {
copyContent?: boolean;
focus?: boolean;
sortingKey?: string;
header?: TemplateRef<any>;
constructor(input: any) {
this.key = input.key;
@ -45,5 +46,6 @@ export class ObjectDataColumn implements DataColumn {
this.copyContent = input.copyContent;
this.focus = input.focus;
this.sortingKey = input.sortingKey;
this.header = input.template;
}
}

View File

@ -28,15 +28,12 @@ const column = {
export class AddPermissionsDialogPage {
dataTableComponentPage: DataTableComponentPage = new DataTableComponentPage();
userRoleDataTableComponentPage: DataTableComponentPage = new DataTableComponentPage(element(by.css('[data-automation-id="adf-user-role-selection-table"]')));
addPermissionDialog = element(by.css('adf-add-permission-dialog'));
searchUserInput = element(by.id('searchInput'));
searchResults = element(by.css('#adf-add-permission-authority-results #adf-search-results-content'));
addButton = element(by.id('add-permission-dialog-confirm-button'));
permissionInheritedButton = element.all(by.css('div[class="app-inherit_permission_button"] button')).first();
noPermissions = element(by.id('adf-no-permissions-template'));
deletePermissionButton = element(by.css('button[data-automation-id="adf-delete-permission-button"]'));
permissionDisplayContainer = element(by.id('adf-permission-display-container'));
addButton = element(by.css('[data-automation-id="add-permission-dialog-confirm-button"]'));
closeButton = element(by.id('add-permission-dialog-close-button'));
async clickCloseButton(): Promise<void> {
@ -70,52 +67,17 @@ export class AddPermissionsDialogPage {
await BrowserActions.click(this.addButton);
}
async checkUserIsAdded(name: string): Promise<void> {
const userOrGroupName = element(by.css('div[data-automation-id="text_' + name + '"]'));
await BrowserVisibility.waitUntilElementIsVisible(userOrGroupName);
}
async checkGroupIsAdded(name: string): Promise<void> {
const userOrGroupName = element(by.css('div[data-automation-id="text_GROUP_' + name + '"]'));
await BrowserVisibility.waitUntilElementIsVisible(userOrGroupName);
}
async checkUserIsDeleted(name: string): Promise<void> {
const userOrGroupName = element(by.css('div[data-automation-id="text_' + name + '"]'));
await BrowserVisibility.waitUntilElementIsNotVisible(userOrGroupName);
}
async checkPermissionInheritedButtonIsDisplayed() {
await BrowserVisibility.waitUntilElementIsVisible(this.permissionInheritedButton);
}
async clickPermissionInheritedButton(): Promise<void> {
await BrowserActions.click(this.permissionInheritedButton);
}
async clickDeletePermissionButton(): Promise<void> {
await BrowserActions.click(this.deletePermissionButton);
}
async checkNoPermissionsIsDisplayed(): Promise<void> {
await BrowserVisibility.waitUntilElementIsVisible(this.noPermissions);
}
async getPermissionInheritedButtonText(text: string): Promise<void> {
await BrowserVisibility.waitUntilElementHasText(this.permissionInheritedButton, text);
}
async checkPermissionsDatatableIsDisplayed(): Promise<void> {
await BrowserVisibility.waitUntilElementIsVisible(element(by.css('[class*="adf-datatable-permission"]')));
}
async getRoleCellValue(rowName: string): Promise<string> {
const locator = this.dataTableComponentPage.getCellByRowContentAndColumn('Authority ID', rowName, column.role);
const locator = this.dataTableComponentPage.getCellByRowContentAndColumn('Users and Groups', rowName, column.role);
return BrowserActions.getText(locator);
}
async clickRoleDropdownByUserOrGroupName(name: string): Promise<void> {
const row = this.dataTableComponentPage.getRow('Authority ID', name);
const row = this.dataTableComponentPage.getRow('Users and Groups', name);
await BrowserActions.click(row.element(by.id('adf-select-role-permission')));
}
@ -127,12 +89,23 @@ export class AddPermissionsDialogPage {
await new DropdownPage().selectOption(name);
}
async checkPermissionContainerIsDisplayed(): Promise<void> {
await BrowserVisibility.waitUntilElementIsVisible(this.permissionDisplayContainer);
}
async checkUserOrGroupIsDisplayed(name: string): Promise<void> {
const userOrGroupName = element(by.cssContainingText('mat-list-option .mat-list-text', name));
await BrowserVisibility.waitUntilElementIsVisible(userOrGroupName);
}
async addButtonIsEnabled(): Promise<boolean> {
return this.addButton.isEnabled();
}
async clickAddButton(): Promise<void> {
await BrowserActions.click(this.addButton);
}
async selectRole(name: string, role) {
const row = this.userRoleDataTableComponentPage.getRow('Users and Groups', name);
await BrowserActions.click(row.element(by.css('[id="adf-select-role-permission"] .mat-select-trigger')));
await this.getRoleDropdownOptions();
await this.selectOption(role);
}
}

View File

@ -49,6 +49,10 @@ export class UserModel {
this.id = details.id ? details.id : this.id;
}
get fullName() {
return `${this.firstName ?? ''} ${this.lastName ?? ''}`;
}
getAPSModel() {
return new UserRepresentation({
firstName: this.firstName,

View File

@ -345,7 +345,7 @@ export class DataTableComponentPage {
}
getRow(columnName: string, columnValue: string): ElementFinder {
return this.rootElement.all(by.xpath(`//div[@title='${columnName}']//div[contains(@data-automation-id, '${columnValue}')]//ancestor::adf-datatable-row[contains(@class, 'adf-datatable-row')]`)).first();
return this.rootElement.all(by.xpath(`//div[starts-with(@title, '${columnName}')]//div[contains(@data-automation-id, '${columnValue}')]//ancestor::adf-datatable-row[contains(@class, 'adf-datatable-row')]`)).first();
}
getRowByIndex(index: number): ElementFinder {