mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-05-26 17:24:56 +00:00
[ADF-3850] Create new group cloud component (#4104)
* [ADF-3850] Create New Cloud Groups Component * Generated groupCloud component by cli * * Refactored GroupCloud component * Added unit tests * Changed service name and refactored * * Added Documentation to the groupCloud component * Rebased * * Create GroupSerach param model* Modified group component/service * Added Validation message * * Fixed comments* Refactored groupCLoud component* Created GroupInirial pipe * * Fixed selectedGroups bug * * Removed showHint flag and used formcontrol error instead * * After rebase* Change group-cloud to group * * Added data-automation-id * Improved fetching groups when appName defined * Added unit tests * * Rebased and updated callCustomApi call signature * Remove the springboot hardcoded name * Update group-cloud.component.md * * Fixed comments * Fixed return of js-api * Updated doc * Unify the doc and remove useless code
This commit is contained in:
parent
f08ad08d0f
commit
581f53da59
BIN
docs/docassets/images/group-cloud.component-multiple-mode.png
Normal file
BIN
docs/docassets/images/group-cloud.component-multiple-mode.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.9 KiB |
BIN
docs/docassets/images/group-cloud.component-single.png
Normal file
BIN
docs/docassets/images/group-cloud.component-single.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.0 KiB |
BIN
docs/docassets/images/group-cloud.component.png
Normal file
BIN
docs/docassets/images/group-cloud.component.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
92
docs/process-services-cloud/group-cloud.component.md
Normal file
92
docs/process-services-cloud/group-cloud.component.md
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
---
|
||||||
|
Title: Group Cloud component
|
||||||
|
Added: v3.0.0
|
||||||
|
Status: Active
|
||||||
|
Last reviewed: 2018-20-11
|
||||||
|
---
|
||||||
|
|
||||||
|
# [Group Cloud component](../../lib/process-services-cloud/src/lib/group-cloud/components/group-cloud.component.ts "Defined in group-cloud.component.ts")
|
||||||
|
|
||||||
|
Searches Groups.
|
||||||
|
|
||||||
|
## Basic Usage
|
||||||
|
|
||||||
|
```html
|
||||||
|
<adf-cloud-group
|
||||||
|
[applicationName]="'simple-app'"
|
||||||
|
[mode]="'multiple'">
|
||||||
|
</adf-cloud-group>
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Class members
|
||||||
|
|
||||||
|
### Properties
|
||||||
|
|
||||||
|
| Name | Type | Default value | Description |
|
||||||
|
| ---- | ---- | ------------- | ----------- |
|
||||||
|
| applicationName | `string` | | Name of the application. If specified, shows the groups who have access to the app. |
|
||||||
|
| mode | `string` | 'single' | Mode of the user selection (single/multiple). |
|
||||||
|
| preSelectGroups | `GroupModel[]` | Array of groups to be pre-selected. Pre-select all groups in `multiple` mode and only the first group of the array in `single` mode. |
|
||||||
|
|
||||||
|
### Events
|
||||||
|
|
||||||
|
| Name | Type | Description |
|
||||||
|
| ---- | ---- | ----------- |
|
||||||
|
| selectGroup | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`GroupModel`](../../lib/process-services-cloud/src/lib/group-cloud/models/group.model.ts)`>` | Emitted when a group selected. |
|
||||||
|
| removeGroup | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`GroupModel`](../../lib/process-services-cloud/src/lib/group-cloud/models/group.model.ts)`>` | Emitted when selected group is removed in `multiple` mode. |
|
||||||
|
| error | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<any>` |
|
||||||
|
|
||||||
|
|
||||||
|
## Details
|
||||||
|
|
||||||
|
### Selection Mode
|
||||||
|
|
||||||
|
You can provide selection mode singe(default)/multiple
|
||||||
|
|
||||||
|
## Single select
|
||||||
|
|
||||||
|
```html
|
||||||
|
<adf-cloud-group></adf-cloud-group>
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Multiple select
|
||||||
|
|
||||||
|
```html
|
||||||
|
<adf-cloud-group
|
||||||
|
[mode]="'multiple'">
|
||||||
|
</adf-cloud-group>
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Pre-select
|
||||||
|
|
||||||
|
Usage example:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { ObjectDataTableAdapter } from '@alfresco/adf-core';
|
||||||
|
|
||||||
|
@Component({...})
|
||||||
|
export class MyComponent {
|
||||||
|
groups: any;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.groups =
|
||||||
|
[
|
||||||
|
{id: 1, name: 'Group 1'},
|
||||||
|
{id: 2, name: 'Group 2'}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```html
|
||||||
|
<adf-cloud-group
|
||||||
|
[mode]="'multiple'"
|
||||||
|
[preSelectGroups]="groups">
|
||||||
|
</adf-cloud-group>
|
||||||
|
```
|
@ -0,0 +1,51 @@
|
|||||||
|
<div class="adf-cloud-group">
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-chip-list #groupChipList *ngIf="isMultipleMode()" data-automation-id="adf-cloud-group-chip-list" class="apa-group-chip-list">
|
||||||
|
<mat-chip
|
||||||
|
*ngFor="let group of selectedGroups$ | async"
|
||||||
|
(removed)="onRemove(group)"
|
||||||
|
[attr.data-automation-id]="'adf-cloud-group-chip-' + group.name">
|
||||||
|
{{group.name}}
|
||||||
|
<mat-icon matChipRemove>cancel</mat-icon>
|
||||||
|
</mat-chip>
|
||||||
|
<input
|
||||||
|
placeholder="{{ 'ADF_CLOUD_GROUPS.SEARCH-GROUP' | translate }}"
|
||||||
|
[formControl]="searchGroupsControl"
|
||||||
|
class="adf-group-input"
|
||||||
|
id="group-name"
|
||||||
|
data-automation-id="adf-cloud-group-search-input"
|
||||||
|
[matAutocomplete]="auto"
|
||||||
|
[matChipInputFor]="groupChipList" #groupInput>
|
||||||
|
</mat-chip-list>
|
||||||
|
|
||||||
|
<input *ngIf="!isMultipleMode()"
|
||||||
|
matInput
|
||||||
|
placeholder="{{ 'ADF_CLOUD_GROUPS.SEARCH-GROUP' | translate }}"
|
||||||
|
class="adf-group-input"
|
||||||
|
data-automation-id="adf-cloud-group-search-input"
|
||||||
|
[formControl]="searchGroupsControl"
|
||||||
|
[matAutocomplete]="auto" #groupInput>
|
||||||
|
|
||||||
|
<mat-autocomplete
|
||||||
|
#auto="matAutocomplete"
|
||||||
|
class="adf-cloud-group-list"
|
||||||
|
(optionSelected)="onSelect($event.option.value)"
|
||||||
|
[displayWith]="getDisplayName"
|
||||||
|
data-automation-id="adf-cloud-group-autocomplete">
|
||||||
|
<mat-option *ngFor="let group of searchGroups$ | async; let i = index" [value]="group" [attr.data-automation-id]="'adf-cloud-group-chip-' + group.name">
|
||||||
|
<div class="adf-cloud-group-row" id="adf-group-{{i}}" fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="20px">
|
||||||
|
<button class="adf-group-short-name" mat-fab>{{group | groupNameInitial }}</button>
|
||||||
|
<span>{{group.name}}</span>
|
||||||
|
</div>
|
||||||
|
</mat-option>
|
||||||
|
</mat-autocomplete>
|
||||||
|
</mat-form-field>
|
||||||
|
<div class="adf-cloud-group-error" *ngIf="hasError()">
|
||||||
|
<div fxLayout="row" fxLayoutAlign="start start" [@transitionMessages]="_subscriptAnimationState">
|
||||||
|
<div class="adf-cloud-group-error-message">
|
||||||
|
{{ 'ADF_CLOUD_GROUPS.ERROR.NOT_FOUND' | translate : { groupName : searchedValue } }}
|
||||||
|
</div>
|
||||||
|
<mat-icon class="adf-cloud-group-error-icon">warning</mat-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,56 @@
|
|||||||
|
@mixin adf-cloud-group-theme($theme) {
|
||||||
|
|
||||||
|
$warn: map-get($theme, warn);
|
||||||
|
$primary: map-get($theme, primary);
|
||||||
|
$background: map-get($theme, background);
|
||||||
|
$foreground: map-get($theme, foreground);
|
||||||
|
|
||||||
|
.adf {
|
||||||
|
&-cloud-group {
|
||||||
|
.mat-form-field {
|
||||||
|
padding-top: 8px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-error {
|
||||||
|
margin-top: -10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-error {
|
||||||
|
position: absolute;
|
||||||
|
height: 20px;
|
||||||
|
|
||||||
|
&-message {
|
||||||
|
padding-right: 8px;
|
||||||
|
height: 16px;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.33;
|
||||||
|
color: mat-color($warn);
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-icon {
|
||||||
|
font-size: 17px;
|
||||||
|
color: mat-color($warn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.mat-autocomplete-panel .mat-fab {
|
||||||
|
background: mat-color($primary);
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
font-weight: bolder;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-autocomplete-panel .mat-fab {
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-autocomplete-panel .mat-fab .mat-button-wrapper {
|
||||||
|
display: inline !important;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,258 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2016 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 { ComponentFixture, TestBed, async } from '@angular/core/testing';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
import { ProcessServiceCloudTestingModule } from './../../testing/process-service-cloud.testing.module';
|
||||||
|
|
||||||
|
import { GroupCloudModule } from '../group-cloud.module';
|
||||||
|
import { GroupCloudComponent } from './group-cloud.component';
|
||||||
|
import { GroupCloudService } from '../services/group-cloud.service';
|
||||||
|
import { setupTestBed, AlfrescoApiServiceMock } from '@alfresco/adf-core';
|
||||||
|
import { mockGroups } from '../mock/group-cloud.mock';
|
||||||
|
import { GroupModel } from '../models/group.model';
|
||||||
|
|
||||||
|
describe('GroupCloudComponent', () => {
|
||||||
|
let component: GroupCloudComponent;
|
||||||
|
let fixture: ComponentFixture<GroupCloudComponent>;
|
||||||
|
let element: HTMLElement;
|
||||||
|
let service: GroupCloudService;
|
||||||
|
let findGroupsByNameSpy: jasmine.Spy;
|
||||||
|
let getClientIdByApplicationNameSpy: jasmine.Spy;
|
||||||
|
let checkGroupHasClientRoleMappingSpy: jasmine.Spy;
|
||||||
|
|
||||||
|
setupTestBed({
|
||||||
|
imports: [ProcessServiceCloudTestingModule, GroupCloudModule],
|
||||||
|
providers: [AlfrescoApiServiceMock, GroupCloudService]
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(GroupCloudComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
element = fixture.nativeElement;
|
||||||
|
service = TestBed.get(GroupCloudService);
|
||||||
|
findGroupsByNameSpy = spyOn(service, 'findGroupsByName').and.returnValue(of(mockGroups));
|
||||||
|
getClientIdByApplicationNameSpy = spyOn(service, 'getClientIdByApplicationName').and.returnValue(of('mock-client-id'));
|
||||||
|
checkGroupHasClientRoleMappingSpy = spyOn(service, 'checkGroupHasClientRoleMapping').and.returnValue(of(true));
|
||||||
|
component.applicationName = 'mock-app-name';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create GroupCloudComponent', () => {
|
||||||
|
expect(component instanceof GroupCloudComponent).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to fetch client id', async(() => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
fixture.whenStable().then(() => {
|
||||||
|
expect(getClientIdByApplicationNameSpy).toHaveBeenCalled();
|
||||||
|
expect(component.clientId).toBe('mock-client-id');
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should show the groups if the typed result match', async(() => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
component.searchGroups$ = of(<GroupModel[]> mockGroups);
|
||||||
|
let inputHTMLElement: HTMLInputElement = <HTMLInputElement> element.querySelector('input');
|
||||||
|
inputHTMLElement.focus();
|
||||||
|
inputHTMLElement.dispatchEvent(new Event('input'));
|
||||||
|
inputHTMLElement.dispatchEvent(new Event('keyup'));
|
||||||
|
inputHTMLElement.dispatchEvent(new Event('keydown'));
|
||||||
|
inputHTMLElement.value = 'M';
|
||||||
|
fixture.detectChanges();
|
||||||
|
fixture.whenStable().then(() => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(fixture.debugElement.query(By.css('mat-option'))).toBeDefined();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should hide result list if input is empty', async(() => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
let inputHTMLElement: HTMLInputElement = <HTMLInputElement> element.querySelector('input');
|
||||||
|
inputHTMLElement.focus();
|
||||||
|
inputHTMLElement.value = '';
|
||||||
|
inputHTMLElement.dispatchEvent(new Event('keyup'));
|
||||||
|
inputHTMLElement.dispatchEvent(new Event('input'));
|
||||||
|
fixture.detectChanges();
|
||||||
|
fixture.whenStable().then(() => {
|
||||||
|
expect(fixture.debugElement.query(By.css('mat-option'))).toBeNull();
|
||||||
|
expect(fixture.debugElement.query(By.css('#adf-group-0'))).toBeNull();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should emit selectedGroup if option is valid', async(() => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
let selectEmitSpy = spyOn(component.selectGroup, 'emit');
|
||||||
|
component.onSelect(new GroupModel({ name: 'group name'}));
|
||||||
|
fixture.whenStable().then(() => {
|
||||||
|
expect(selectEmitSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should show an error message if the group is invalid', async(() => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
checkGroupHasClientRoleMappingSpy.and.returnValue(of(false));
|
||||||
|
findGroupsByNameSpy.and.returnValue(of([]));
|
||||||
|
fixture.detectChanges();
|
||||||
|
const inputHTMLElement: HTMLInputElement = <HTMLInputElement> element.querySelector('input');
|
||||||
|
inputHTMLElement.focus();
|
||||||
|
inputHTMLElement.value = 'ZZZ';
|
||||||
|
inputHTMLElement.dispatchEvent(new Event('input'));
|
||||||
|
fixture.detectChanges();
|
||||||
|
fixture.whenStable().then(() => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
const errorMessage = element.querySelector('.adf-cloud-group-error-message');
|
||||||
|
expect(errorMessage).not.toBeNull();
|
||||||
|
expect(errorMessage.textContent).toContain('ADF_CLOUD_GROUPS.ERROR.NOT_FOUN');
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should show chip list when mode=multiple', async(() => {
|
||||||
|
component.mode = 'multiple';
|
||||||
|
fixture.detectChanges();
|
||||||
|
fixture.whenStable().then(() => {
|
||||||
|
const chip = element.querySelector('mat-chip-list');
|
||||||
|
expect(chip).toBeDefined();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should not show chip list when mode=single', async(() => {
|
||||||
|
component.mode = 'single';
|
||||||
|
fixture.detectChanges();
|
||||||
|
fixture.whenStable().then(() => {
|
||||||
|
const chip = element.querySelector('mat-chip-list');
|
||||||
|
expect(chip).toBeNull();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should pre-select all preSelectGroups when mode=multiple', async(() => {
|
||||||
|
component.mode = 'multiple';
|
||||||
|
component.preSelectGroups = <any> [{id: mockGroups[1].id}, {id: mockGroups[2].id}];
|
||||||
|
fixture.detectChanges();
|
||||||
|
fixture.whenStable().then(() => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
const chips = fixture.debugElement.queryAll(By.css('mat-chip'));
|
||||||
|
expect(chips.length).toBe(2);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should not pre-select any group when preSelectGroups is empty and mode=multiple', async(() => {
|
||||||
|
component.mode = 'multiple';
|
||||||
|
fixture.detectChanges();
|
||||||
|
fixture.whenStable().then(() => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
const chip = fixture.debugElement.query(By.css('mat-chip'));
|
||||||
|
expect(chip).toBeNull();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should pre-select preSelectGroups[0] when mode=single', async(() => {
|
||||||
|
component.mode = 'single';
|
||||||
|
component.preSelectGroups = <any> [{id: mockGroups[1].id}, {id: mockGroups[2].id}];
|
||||||
|
fixture.detectChanges();
|
||||||
|
fixture.whenStable().then(() => {
|
||||||
|
const selectedGroup = component.searchGroupsControl.value;
|
||||||
|
expect(selectedGroup.id).toBe(mockGroups[1].id);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should not pre-select any group when preSelectGroups is empty and mode=single', async(() => {
|
||||||
|
component.mode = 'single';
|
||||||
|
fixture.detectChanges();
|
||||||
|
fixture.whenStable().then(() => {
|
||||||
|
const selectedGroup = component.searchGroupsControl.value;
|
||||||
|
expect(selectedGroup).toBeNull();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should emit removeGroup when a selected group is removed if mode=multiple', async(() => {
|
||||||
|
let removeGroupSpy = spyOn(component.removeGroup, 'emit');
|
||||||
|
|
||||||
|
component.mode = 'multiple';
|
||||||
|
component.preSelectGroups = <any> [{id: mockGroups[1].id}, {id: mockGroups[2].id}];
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
fixture.whenStable().then(() => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
const removeIcon = fixture.debugElement.query(By.css('mat-chip mat-icon'));
|
||||||
|
removeIcon.nativeElement.click();
|
||||||
|
|
||||||
|
expect(removeGroupSpy).toHaveBeenCalledWith({ id: mockGroups[1].id });
|
||||||
|
});
|
||||||
|
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should list groups who have access to the app when appName is specified', async(() => {
|
||||||
|
component.applicationName = 'sample-app';
|
||||||
|
fixture.detectChanges();
|
||||||
|
let inputHTMLElement: HTMLInputElement = <HTMLInputElement> element.querySelector('input');
|
||||||
|
inputHTMLElement.focus();
|
||||||
|
inputHTMLElement.value = 'M';
|
||||||
|
inputHTMLElement.dispatchEvent(new Event('input'));
|
||||||
|
fixture.detectChanges();
|
||||||
|
fixture.whenStable().then(() => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
const groupsList = fixture.debugElement.queryAll(By.css('mat-option'));
|
||||||
|
expect(groupsList.length).toBe(mockGroups.length);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should not list groups who do not have access to the app when appName is specified', async(() => {
|
||||||
|
checkGroupHasClientRoleMappingSpy.and.returnValue(of(false));
|
||||||
|
component.applicationName = 'sample-app';
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
let inputHTMLElement: HTMLInputElement = <HTMLInputElement> element.querySelector('[data-automation-id="adf-cloud-group-search-input"]');
|
||||||
|
inputHTMLElement.focus();
|
||||||
|
inputHTMLElement.value = 'Mock';
|
||||||
|
inputHTMLElement.dispatchEvent(new Event('input'));
|
||||||
|
fixture.detectChanges();
|
||||||
|
fixture.whenStable().then(() => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
const groupsList = fixture.debugElement.queryAll(By.css('mat-option'));
|
||||||
|
expect(groupsList.length).toBe(0);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should validate access to the app when appName is specified', async(() => {
|
||||||
|
findGroupsByNameSpy.and.returnValue(of(mockGroups));
|
||||||
|
checkGroupHasClientRoleMappingSpy.and.returnValue(of(true));
|
||||||
|
fixture.detectChanges();
|
||||||
|
let inputHTMLElement: HTMLInputElement = <HTMLInputElement> element.querySelector('input');
|
||||||
|
inputHTMLElement.focus();
|
||||||
|
inputHTMLElement.value = 'Mock';
|
||||||
|
inputHTMLElement.dispatchEvent(new Event('input'));
|
||||||
|
fixture.detectChanges();
|
||||||
|
fixture.whenStable().then(() => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(checkGroupHasClientRoleMappingSpy).toHaveBeenCalledTimes(mockGroups.length);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should not validate access to the app when appName is not specified', async(() => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
let inputHTMLElement: HTMLInputElement = <HTMLInputElement> element.querySelector('input');
|
||||||
|
inputHTMLElement.focus();
|
||||||
|
inputHTMLElement.value = 'M';
|
||||||
|
inputHTMLElement.dispatchEvent(new Event('input'));
|
||||||
|
fixture.detectChanges();
|
||||||
|
fixture.whenStable().then(() => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(checkGroupHasClientRoleMappingSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
});
|
@ -0,0 +1,274 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2016 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, ElementRef, OnInit, Output, EventEmitter, ViewChild, ViewEncapsulation, Input } from '@angular/core';
|
||||||
|
import { FormControl } from '@angular/forms';
|
||||||
|
import { trigger, state, style, transition, animate } from '@angular/animations';
|
||||||
|
import { Observable, of, BehaviorSubject } from 'rxjs';
|
||||||
|
import { GroupModel, GroupSearchParam } from '../models/group.model';
|
||||||
|
import { GroupCloudService } from '../services/group-cloud.service';
|
||||||
|
import { debounceTime } from 'rxjs/internal/operators/debounceTime';
|
||||||
|
import { distinctUntilChanged, switchMap, flatMap, mergeMap, filter, tap } from 'rxjs/operators';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'adf-cloud-group',
|
||||||
|
templateUrl: './group-cloud.component.html',
|
||||||
|
styleUrls: ['./group-cloud.component.scss'],
|
||||||
|
animations: [
|
||||||
|
trigger('transitionMessages', [
|
||||||
|
state('enter', style({ opacity: 1, transform: 'translateY(0%)' })),
|
||||||
|
transition('void => enter', [
|
||||||
|
style({ opacity: 0, transform: 'translateY(-100%)' }),
|
||||||
|
animate('300ms cubic-bezier(0.55, 0, 0.55, 0.2)')
|
||||||
|
])
|
||||||
|
])
|
||||||
|
],
|
||||||
|
encapsulation: ViewEncapsulation.None
|
||||||
|
})
|
||||||
|
export class GroupCloudComponent implements OnInit {
|
||||||
|
|
||||||
|
static MODE_SINGLE = 'single';
|
||||||
|
static MODE_MULTIPLE = 'multiple';
|
||||||
|
|
||||||
|
@ViewChild('groupInput') groupInput: ElementRef<HTMLInputElement>;
|
||||||
|
|
||||||
|
/** Name of the application. If specified, shows the users who have access to the app. */
|
||||||
|
@Input()
|
||||||
|
applicationName: string;
|
||||||
|
|
||||||
|
/** Mode of the user selection (single/multiple). */
|
||||||
|
@Input()
|
||||||
|
mode: string = GroupCloudComponent.MODE_SINGLE;
|
||||||
|
|
||||||
|
/** Array of users to be pre-selected. Pre-select all users in multi selection mode and only the first user of the array in single selection mode. */
|
||||||
|
@Input()
|
||||||
|
preSelectGroups: GroupModel[] = [];
|
||||||
|
|
||||||
|
/** Emitted when a group is selected. */
|
||||||
|
@Output()
|
||||||
|
selectGroup: EventEmitter<GroupModel> = new EventEmitter<GroupModel>();
|
||||||
|
|
||||||
|
/** Emitted when a group is removed. */
|
||||||
|
@Output()
|
||||||
|
removeGroup: EventEmitter<GroupModel> = new EventEmitter<GroupModel>();
|
||||||
|
|
||||||
|
private selectedGroups: GroupModel[] = [];
|
||||||
|
|
||||||
|
private searchGroups: GroupModel[] = [];
|
||||||
|
|
||||||
|
private searchGroupsSubject: BehaviorSubject<GroupModel[]>;
|
||||||
|
|
||||||
|
private selectedGroupsSubject: BehaviorSubject<GroupModel[]>;
|
||||||
|
|
||||||
|
searchGroups$: Observable<GroupModel[]>;
|
||||||
|
|
||||||
|
selectedGroups$: Observable<GroupModel[]>;
|
||||||
|
|
||||||
|
searchGroupsControl: FormControl = new FormControl('');
|
||||||
|
|
||||||
|
_subscriptAnimationState = 'enter';
|
||||||
|
|
||||||
|
clientId: string;
|
||||||
|
|
||||||
|
searchedValue = '';
|
||||||
|
|
||||||
|
constructor(private groupService: GroupCloudService) {
|
||||||
|
this.selectedGroupsSubject = new BehaviorSubject<GroupModel[]>(this.selectedGroups);
|
||||||
|
this.searchGroupsSubject = new BehaviorSubject<GroupModel[]>(this.searchGroups);
|
||||||
|
this.selectedGroups$ = this.selectedGroupsSubject.asObservable();
|
||||||
|
this.searchGroups$ = this.searchGroupsSubject.asObservable();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.loadPreSelectGroups();
|
||||||
|
this.initSearch();
|
||||||
|
|
||||||
|
if (this.applicationName) {
|
||||||
|
this.disableSearch();
|
||||||
|
this.loadClientId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async loadClientId() {
|
||||||
|
this.clientId = await this.groupService.getClientIdByApplicationName(this.applicationName).toPromise();
|
||||||
|
if (this.clientId) {
|
||||||
|
this.enableSearch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
initSearch() {
|
||||||
|
this.searchGroupsControl.valueChanges.pipe(
|
||||||
|
debounceTime(500),
|
||||||
|
distinctUntilChanged(),
|
||||||
|
tap(() => {
|
||||||
|
this.resetSearchGroups();
|
||||||
|
}),
|
||||||
|
switchMap((inputValue) => {
|
||||||
|
const queryParams = this.createSearchParam(inputValue);
|
||||||
|
return this.findGroupsByName(queryParams);
|
||||||
|
}),
|
||||||
|
filter((group: any) => {
|
||||||
|
return !this.isGroupAlreadySelected(group);
|
||||||
|
}),
|
||||||
|
mergeMap((group: any) => {
|
||||||
|
if (this.clientId) {
|
||||||
|
return this.checkGroupHasClientRoleMapping(group);
|
||||||
|
} else {
|
||||||
|
return of(group);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
).subscribe((searchedGroup) => {
|
||||||
|
this.searchGroups.push(searchedGroup);
|
||||||
|
this.clearError();
|
||||||
|
this.searchGroupsSubject.next(this.searchGroups);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
findGroupsByName(searchParam: GroupSearchParam): Observable<GroupModel> {
|
||||||
|
return this.groupService.findGroupsByName(searchParam).pipe(
|
||||||
|
flatMap((groups: GroupModel[]) => {
|
||||||
|
this.searchedValue = searchParam.name;
|
||||||
|
if (this.searchedValue && !this.hasGroups(groups)) {
|
||||||
|
this.setError();
|
||||||
|
}
|
||||||
|
return groups;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
checkGroupHasClientRoleMapping(group: GroupModel): Observable<GroupModel> {
|
||||||
|
return this.groupService.checkGroupHasClientRoleMapping(group.id, this.clientId).pipe(
|
||||||
|
mergeMap((hasRole: boolean) => {
|
||||||
|
if (hasRole) {
|
||||||
|
return of(group);
|
||||||
|
} else {
|
||||||
|
this.setError();
|
||||||
|
return of();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
isGroupAlreadySelected(group: GroupModel): boolean {
|
||||||
|
if (this.hasGroups(this.selectedGroups)) {
|
||||||
|
const result = this.selectedGroups.filter((selectedGroup: GroupModel) => {
|
||||||
|
return selectedGroup.id === group.id;
|
||||||
|
});
|
||||||
|
if (this.hasGroups(result)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadPreSelectGroups() {
|
||||||
|
if (this.hasGroups(this.preSelectGroups)) {
|
||||||
|
if (this.isMultipleMode()) {
|
||||||
|
this.preSelectGroups.forEach((group: GroupModel) => {
|
||||||
|
this.selectedGroups.push(group);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.searchGroupsControl.setValue(this.preSelectGroups[0]);
|
||||||
|
this.onSelect(this.preSelectGroups[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelect(selectedGroup: GroupModel) {
|
||||||
|
if (this.isMultipleMode()) {
|
||||||
|
if (!this.isGroupAlreadySelected(selectedGroup)) {
|
||||||
|
this.selectedGroups.push(selectedGroup);
|
||||||
|
this.selectedGroupsSubject.next(this.selectedGroups);
|
||||||
|
this.selectGroup.emit(selectedGroup);
|
||||||
|
this.searchGroupsSubject.next([]);
|
||||||
|
}
|
||||||
|
this.groupInput.nativeElement.value = '';
|
||||||
|
this.searchGroupsControl.setValue('');
|
||||||
|
} else {
|
||||||
|
this.selectGroup.emit(selectedGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.clearError();
|
||||||
|
this.resetSearchGroups();
|
||||||
|
}
|
||||||
|
|
||||||
|
onRemove(selectedGroup: GroupModel) {
|
||||||
|
this.removeGroup.emit(selectedGroup);
|
||||||
|
const indexToRemove = this.selectedGroups.findIndex((group: GroupModel) => { return group.id === selectedGroup.id; });
|
||||||
|
this.selectedGroups.splice(indexToRemove, 1);
|
||||||
|
this.selectedGroupsSubject.next(this.selectedGroups);
|
||||||
|
}
|
||||||
|
|
||||||
|
private resetSearchGroups() {
|
||||||
|
this.searchGroups = [];
|
||||||
|
this.searchGroupsSubject.next([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
isMultipleMode(): boolean {
|
||||||
|
return this.mode === GroupCloudComponent.MODE_MULTIPLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDisplayName(group: GroupModel): string {
|
||||||
|
return group ? group.name : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
private hasGroups(groups: GroupModel[]): boolean {
|
||||||
|
return groups && groups.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
createSearchParam(value: any): GroupSearchParam {
|
||||||
|
let queryParams: GroupSearchParam = { name: '' };
|
||||||
|
if (this.isString(value)) {
|
||||||
|
queryParams.name = value.trim();
|
||||||
|
} else {
|
||||||
|
queryParams.name = value.name.trim();
|
||||||
|
}
|
||||||
|
return queryParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
isString(value: any): boolean {
|
||||||
|
return typeof value === 'string';
|
||||||
|
}
|
||||||
|
|
||||||
|
setValidationError() {
|
||||||
|
if (this.hasGroups(this.searchGroups)) {
|
||||||
|
this.clearError();
|
||||||
|
} else {
|
||||||
|
this.setError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private disableSearch() {
|
||||||
|
this.searchGroupsControl.disable();
|
||||||
|
}
|
||||||
|
|
||||||
|
private enableSearch() {
|
||||||
|
this.searchGroupsControl.enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
private setError() {
|
||||||
|
this.searchGroupsControl.setErrors({invalid: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
private clearError() {
|
||||||
|
this.searchGroupsControl.setErrors(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
hasError(): boolean {
|
||||||
|
return this.searchGroupsControl && this.searchGroupsControl.errors && this.searchGroupsControl.errors.invalid;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2016 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 { NgModule } from '@angular/core';
|
||||||
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||||
|
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import { TemplateModule, TranslateLoaderService, FormModule, PipeModule } from '@alfresco/adf-core';
|
||||||
|
import { MaterialModule } from '../material.module';
|
||||||
|
import { GroupCloudComponent } from './components/group-cloud.component';
|
||||||
|
import { InitialGroupNamePipe } from './pipe/group-initial.pipe';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
PipeModule,
|
||||||
|
TranslateModule.forRoot({
|
||||||
|
loader: {
|
||||||
|
provide: TranslateLoader,
|
||||||
|
useClass: TranslateLoaderService
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
TemplateModule,
|
||||||
|
FlexLayoutModule,
|
||||||
|
MaterialModule,
|
||||||
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
FormModule
|
||||||
|
],
|
||||||
|
declarations: [GroupCloudComponent, InitialGroupNamePipe],
|
||||||
|
exports: [GroupCloudComponent, InitialGroupNamePipe]
|
||||||
|
})
|
||||||
|
export class GroupCloudModule { }
|
@ -0,0 +1,104 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2016 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 { GroupModel } from '../models/group.model';
|
||||||
|
|
||||||
|
export let mockGroup1 = new GroupModel({
|
||||||
|
id: 'mock-id-1', name: 'Mock Group 1', path: '/mock', subGroups: []
|
||||||
|
});
|
||||||
|
|
||||||
|
export let mockGroup2 = new GroupModel({
|
||||||
|
id: 'mock-id-2', name: 'Mock Group 2', path: '', subGroups: []
|
||||||
|
});
|
||||||
|
|
||||||
|
export let mockGroup3 = new GroupModel({
|
||||||
|
id: 'mock-id-3', name: 'Fake Group 3', path: '', subGroups: []
|
||||||
|
});
|
||||||
|
|
||||||
|
export let mockGroups = [
|
||||||
|
mockGroup1, mockGroup2, mockGroup3
|
||||||
|
];
|
||||||
|
|
||||||
|
export let mockApplicationDetails = {id: 'mock-app-id', name: 'mock-app-name'};
|
||||||
|
|
||||||
|
export let mockError = {
|
||||||
|
error: {
|
||||||
|
errorKey: 'failed',
|
||||||
|
statusCode: 400,
|
||||||
|
stackTrace: 'For security reasons the stack trace is no longer displayed, but the property is kept for previous versions.'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export let mockApiError = {
|
||||||
|
oauth2Auth: {
|
||||||
|
callCustomApi: () => {
|
||||||
|
return Promise.reject(mockError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export let roleMappingMock = [
|
||||||
|
{ id: 'role-id-1', name: 'role-name-1' }, { id: 'role-id-2', name: 'role-name-2' }
|
||||||
|
];
|
||||||
|
|
||||||
|
export let roleMappingApi = {
|
||||||
|
oauth2Auth: {
|
||||||
|
callCustomApi: () => {
|
||||||
|
return Promise.resolve(roleMappingMock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export let noRoleMappingApi = {
|
||||||
|
oauth2Auth: {
|
||||||
|
callCustomApi: () => {
|
||||||
|
return Promise.resolve([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export let groupsMockApi = {
|
||||||
|
oauth2Auth: {
|
||||||
|
callCustomApi: () => {
|
||||||
|
return Promise.resolve(mockGroups);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export let returnCallQueryParameters = {
|
||||||
|
oauth2Auth: {
|
||||||
|
callCustomApi: (queryUrl, operation, context, queryParams) => {
|
||||||
|
return Promise.resolve(queryParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export let returnCallUrl = {
|
||||||
|
oauth2Auth: {
|
||||||
|
callCustomApi: (queryUrl, operation, context, queryParams) => {
|
||||||
|
return Promise.resolve(queryUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export let applicationDetailsMockApi = {
|
||||||
|
oauth2Auth: {
|
||||||
|
callCustomApi: () => {
|
||||||
|
return Promise.resolve([mockApplicationDetails]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,41 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2016 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class GroupModel {
|
||||||
|
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
path: string;
|
||||||
|
realmRoles: string[];
|
||||||
|
access: any;
|
||||||
|
attributes: any;
|
||||||
|
clientRoles: any;
|
||||||
|
|
||||||
|
constructor(obj?: any) {
|
||||||
|
this.id = obj.id || null;
|
||||||
|
this.name = obj.name || null;
|
||||||
|
this.path = obj.path || null;
|
||||||
|
this.realmRoles = obj.realmRoles || null;
|
||||||
|
this.access = obj.access || null;
|
||||||
|
this.attributes = obj.attributes || null;
|
||||||
|
this.clientRoles = obj.clientRoles || null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GroupSearchParam {
|
||||||
|
name?: string;
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2016 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 { InitialGroupNamePipe } from './group-initial.pipe';
|
||||||
|
import { GroupModel } from '../models/group.model';
|
||||||
|
|
||||||
|
describe('InitialGroupNamePipe', () => {
|
||||||
|
|
||||||
|
let pipe: InitialGroupNamePipe;
|
||||||
|
let fakeGroup: GroupModel;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
pipe = new InitialGroupNamePipe();
|
||||||
|
fakeGroup = new GroupModel({name: 'mock'});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return with the group initial', () => {
|
||||||
|
fakeGroup.name = 'FAKE-GROUP-NAME';
|
||||||
|
let result = pipe.transform(fakeGroup);
|
||||||
|
expect(result).toBe('F');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return an empty string when group is null', () => {
|
||||||
|
let result = pipe.transform(null);
|
||||||
|
expect(result).toBe('');
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,40 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2016 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 { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
import { GroupModel } from '../models/group.model';
|
||||||
|
|
||||||
|
@Pipe({
|
||||||
|
name: 'groupNameInitial'
|
||||||
|
})
|
||||||
|
export class InitialGroupNamePipe implements PipeTransform {
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
transform(group: GroupModel): string {
|
||||||
|
let result = '';
|
||||||
|
if (group) {
|
||||||
|
result = this.getInitialGroupName(group.name).toUpperCase();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
getInitialGroupName(groupName: string) {
|
||||||
|
groupName = (groupName ? groupName[0] : '');
|
||||||
|
return groupName;
|
||||||
|
}
|
||||||
|
}
|
21
lib/process-services-cloud/src/lib/group/public-api.ts
Normal file
21
lib/process-services-cloud/src/lib/group/public-api.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2016 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from './models/group.model';
|
||||||
|
export * from './services/group-cloud.service';
|
||||||
|
export * from './components/group-cloud.component';
|
||||||
|
export * from './group-cloud.module';
|
@ -0,0 +1,135 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2016 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 { async } from '@angular/core/testing';
|
||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
import { GroupCloudService } from './group-cloud.service';
|
||||||
|
import {
|
||||||
|
AlfrescoApiServiceMock,
|
||||||
|
CoreModule,
|
||||||
|
setupTestBed,
|
||||||
|
AlfrescoApiService,
|
||||||
|
LogService
|
||||||
|
} from '@alfresco/adf-core';
|
||||||
|
import {
|
||||||
|
applicationDetailsMockApi,
|
||||||
|
groupsMockApi,
|
||||||
|
returnCallQueryParameters,
|
||||||
|
returnCallUrl,
|
||||||
|
mockApiError,
|
||||||
|
mockError,
|
||||||
|
roleMappingApi,
|
||||||
|
noRoleMappingApi
|
||||||
|
} from '../mock/group-cloud.mock';
|
||||||
|
import { GroupSearchParam } from '../models/group.model';
|
||||||
|
|
||||||
|
describe('GroupCloudService', () => {
|
||||||
|
let service: GroupCloudService;
|
||||||
|
let apiService: AlfrescoApiService;
|
||||||
|
let logService: LogService;
|
||||||
|
|
||||||
|
setupTestBed({
|
||||||
|
imports: [CoreModule.forRoot()],
|
||||||
|
providers: [
|
||||||
|
{ provide: AlfrescoApiService, useClass: AlfrescoApiServiceMock }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
service = TestBed.get(GroupCloudService);
|
||||||
|
apiService = TestBed.get(AlfrescoApiService);
|
||||||
|
logService = TestBed.get(LogService);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should be able to fetch groups', (done) => {
|
||||||
|
spyOn(apiService, 'getInstance').and.returnValue(groupsMockApi);
|
||||||
|
service.findGroupsByName(<GroupSearchParam> {name: 'mock'}).subscribe((res) => {
|
||||||
|
expect(res).toBeDefined();
|
||||||
|
expect(res).not.toBeNull();
|
||||||
|
expect(res.length).toBe(3);
|
||||||
|
expect(res[0].id).toBe('mock-id-1');
|
||||||
|
expect(res[0].name).toBe('Mock Group 1');
|
||||||
|
expect(res[1].id).toBe('mock-id-2');
|
||||||
|
expect(res[1].name).toBe('Mock Group 2');
|
||||||
|
expect(res[2].id).toBe('mock-id-3');
|
||||||
|
expect(res[2].name).toBe('Fake Group 3');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true if group has client role mapping', (done) => {
|
||||||
|
spyOn(apiService, 'getInstance').and.returnValue(roleMappingApi);
|
||||||
|
service.checkGroupHasClientRoleMapping('mock-group-id', 'mock-app-id').subscribe((hasRole) => {
|
||||||
|
expect(hasRole).toBeDefined();
|
||||||
|
expect(hasRole).toBe(true);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false if group does not have client role mapping', (done) => {
|
||||||
|
spyOn(apiService, 'getInstance').and.returnValue(noRoleMappingApi);
|
||||||
|
service.checkGroupHasClientRoleMapping('mock-group-id', 'mock-app-id').subscribe((hasRole) => {
|
||||||
|
expect(hasRole).toBeDefined();
|
||||||
|
expect(hasRole).toBe(false);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should append to the call all the parameters', (done) => {
|
||||||
|
spyOn(apiService, 'getInstance').and.returnValue(returnCallQueryParameters);
|
||||||
|
service.findGroupsByName(<GroupSearchParam> {name: 'mock'}).subscribe((res) => {
|
||||||
|
expect(res).toBeDefined();
|
||||||
|
expect(res).not.toBeNull();
|
||||||
|
expect(res.search).toBe('mock');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should request groups api url', (done) => {
|
||||||
|
spyOn(apiService, 'getInstance').and.returnValue(returnCallUrl);
|
||||||
|
service.findGroupsByName(<GroupSearchParam> {name: 'mock'}).subscribe((requestUrl) => {
|
||||||
|
expect(requestUrl).toBeDefined();
|
||||||
|
expect(requestUrl).not.toBeNull();
|
||||||
|
expect(requestUrl).toContain('/groups');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to fetch the client id', (done) => {
|
||||||
|
spyOn(apiService, 'getInstance').and.returnValue(applicationDetailsMockApi);
|
||||||
|
service.getClientIdByApplicationName('mock-app-name').subscribe((clientId) => {
|
||||||
|
expect(clientId).toBeDefined();
|
||||||
|
expect(clientId).not.toBeNull();
|
||||||
|
expect(clientId).toBe('mock-app-id');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should notify errors returned from the API', (done) => {
|
||||||
|
const logServiceSpy = spyOn(logService, 'error').and.callThrough();
|
||||||
|
spyOn(apiService, 'getInstance').and.returnValue(mockApiError);
|
||||||
|
service.findGroupsByName(<GroupSearchParam> {name: 'mock'}).subscribe(
|
||||||
|
() => {},
|
||||||
|
(res: any) => {
|
||||||
|
expect(res).toBeDefined();
|
||||||
|
expect(res).toEqual(mockError);
|
||||||
|
expect(logServiceSpy).toHaveBeenCalled();
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,110 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright 2016 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 { Injectable } from '@angular/core';
|
||||||
|
import { from, of, Observable, throwError } from 'rxjs';
|
||||||
|
import { map, catchError } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { AlfrescoApiService, AppConfigService, LogService } from '@alfresco/adf-core';
|
||||||
|
import { GroupSearchParam } from '../models/group.model';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class GroupCloudService {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private apiService: AlfrescoApiService,
|
||||||
|
private appConfigService: AppConfigService,
|
||||||
|
private logService: LogService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
findGroupsByName(searchParams: GroupSearchParam): Observable<any> {
|
||||||
|
if (searchParams.name === '') {
|
||||||
|
return of([]);
|
||||||
|
}
|
||||||
|
const url = this.getGroupsApi();
|
||||||
|
const httpMethod = 'GET', pathParams = {}, queryParams = {search: searchParams.name}, bodyParam = {}, headerParams = {},
|
||||||
|
formParams = {}, contentTypes = ['application/json'], accepts = ['application/json'];
|
||||||
|
|
||||||
|
return (from(this.apiService.getInstance().oauth2Auth.callCustomApi(
|
||||||
|
url, httpMethod, pathParams, queryParams,
|
||||||
|
headerParams, formParams, bodyParam,
|
||||||
|
contentTypes, accepts, Object, null, null)
|
||||||
|
)).pipe(
|
||||||
|
catchError((err) => this.handleError(err))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getClientIdByApplicationName(applicationName: string): Observable<string> {
|
||||||
|
const url = this.getApplicationIdApi();
|
||||||
|
const httpMethod = 'GET', pathParams = {}, queryParams = {clientId: applicationName}, bodyParam = {}, headerParams = {}, formParams = {},
|
||||||
|
contentTypes = ['application/json'], accepts = ['application/json'];
|
||||||
|
return from(this.apiService.getInstance()
|
||||||
|
.oauth2Auth.callCustomApi(url, httpMethod, pathParams, queryParams, headerParams,
|
||||||
|
formParams, bodyParam, contentTypes,
|
||||||
|
accepts, Object, null, null)
|
||||||
|
).pipe(
|
||||||
|
map((response: any[]) => {
|
||||||
|
const clientId = response && response.length > 0 ? response[0].id : '';
|
||||||
|
return clientId;
|
||||||
|
}),
|
||||||
|
catchError((err) => this.handleError(err))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
checkGroupHasClientRoleMapping(groupId: string, clientId: string): Observable<any> {
|
||||||
|
const url = this.groupClientRoleMappingApi(groupId, clientId);
|
||||||
|
const httpMethod = 'GET', pathParams = {}, queryParams = {}, bodyParam = {}, headerParams = {},
|
||||||
|
formParams = {}, contentTypes = ['application/json'], accepts = ['application/json'];
|
||||||
|
|
||||||
|
return from(this.apiService.getInstance().oauth2Auth.callCustomApi(
|
||||||
|
url, httpMethod, pathParams, queryParams,
|
||||||
|
headerParams, formParams, bodyParam,
|
||||||
|
contentTypes, accepts, Object, null, null)
|
||||||
|
).pipe(
|
||||||
|
map((response: any[]) => {
|
||||||
|
if (response && response.length > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}),
|
||||||
|
catchError((err) => this.handleError(err))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private groupClientRoleMappingApi(groupId: string, clientId: string): any {
|
||||||
|
return `${this.appConfigService.get('identityHost')}/groups/${groupId}/role-mappings/clients/${clientId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getApplicationIdApi() {
|
||||||
|
return `${this.appConfigService.get('identityHost')}/clients`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getGroupsApi() {
|
||||||
|
return `${this.appConfigService.get('identityHost')}/groups`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throw the error
|
||||||
|
* @param error
|
||||||
|
*/
|
||||||
|
private handleError(error: Response) {
|
||||||
|
this.logService.error(error);
|
||||||
|
return throwError(error || 'Server error');
|
||||||
|
}
|
||||||
|
}
|
@ -145,5 +145,11 @@
|
|||||||
"SAVE": "SAVE",
|
"SAVE": "SAVE",
|
||||||
"CANCEL": "CANCEL"
|
"CANCEL": "CANCEL"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"ADF_CLOUD_GROUPS": {
|
||||||
|
"SEARCH-GROUP": "Groups",
|
||||||
|
"ERROR": {
|
||||||
|
"NOT_FOUND": "No group found with the name {{groupName}}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import { TRANSLATION_PROVIDER } from '@alfresco/adf-core';
|
|||||||
import { AppListCloudModule } from './app/app-list-cloud.module';
|
import { AppListCloudModule } from './app/app-list-cloud.module';
|
||||||
import { TaskCloudModule } from './task/task-cloud.module';
|
import { TaskCloudModule } from './task/task-cloud.module';
|
||||||
import { ProcessCloudModule } from './process/process-cloud.module';
|
import { ProcessCloudModule } from './process/process-cloud.module';
|
||||||
|
import { GroupCloudModule } from './group/group-cloud.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@ -40,7 +41,8 @@ import { ProcessCloudModule } from './process/process-cloud.module';
|
|||||||
exports: [
|
exports: [
|
||||||
AppListCloudModule,
|
AppListCloudModule,
|
||||||
ProcessCloudModule,
|
ProcessCloudModule,
|
||||||
TaskCloudModule
|
TaskCloudModule,
|
||||||
|
GroupCloudModule
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class ProcessServicesCloudModule { }
|
export class ProcessServicesCloudModule { }
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
@import './../process/process-list/components/process-list-cloud.component.scss';
|
@import './../process/process-list/components/process-list-cloud.component.scss';
|
||||||
@import './../task/start-task/components/start-task-cloud.component.scss';
|
@import './../task/start-task/components/start-task-cloud.component.scss';
|
||||||
@import './../task/start-task/components/people-cloud/people-cloud.component.scss';
|
@import './../task/start-task/components/people-cloud/people-cloud.component.scss';
|
||||||
|
@import './../group/components/group-cloud.component';
|
||||||
|
|
||||||
|
|
||||||
@mixin adf-process-services-cloud-theme($theme) {
|
@mixin adf-process-services-cloud-theme($theme) {
|
||||||
@ -15,4 +16,5 @@
|
|||||||
@include adf-process-filters-cloud-theme($theme);
|
@include adf-process-filters-cloud-theme($theme);
|
||||||
@include adf-start-task-cloud-theme($theme);
|
@include adf-start-task-cloud-theme($theme);
|
||||||
@include adf-cloud-people-theme($theme);
|
@include adf-cloud-people-theme($theme);
|
||||||
|
@include adf-cloud-group-theme($theme);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user