[ACA-3619] - add task assignment filter (#5907)

* [ACA-3619] - add task assignment filter

* rework task assignment filter

* fix unit test

Co-authored-by: Silviu Popa <p3701014@L3700101120.ness.com>
This commit is contained in:
Silviu Popa
2020-10-20 19:23:38 +03:00
committed by GitHub
parent 80fc239461
commit dc8117711a
14 changed files with 308 additions and 7 deletions

View File

@@ -162,7 +162,8 @@
"START_DATE": "StartDate",
"COMPLETED_BY": "Completed By",
"COMPLETED_DATE": "CompletedDate",
"CREATED_DATE": "CreatedDate"
"CREATED_DATE": "CreatedDate",
"CANDIDATE_GROUPS": "CandidateGroups"
},
"DIALOG": {
"TITLE": "Save filter as",
@@ -333,5 +334,12 @@
},
"ADF_CLOUD_FORM_COMPONENT": {
"RETRIEVE_METADATA": "Fill form fields with this file's metadata"
},
"ADF_CLOUD_TASK_ASSIGNEMNT_FILTER": {
"ASSIGNED_TO_ME": "Assigned to me",
"UNASSIGNED": "Unassigned",
"LIST_OF_CANDIDATE_USERS": "List of candidate users",
"ASSIGNEE": "Assignee",
"ASSIGNMENT_TYPE": "Assignment type"
}
}

View File

@@ -111,6 +111,12 @@
[mode]="taskFilterProperty.selectionMode"
(changedUsers)="onChangedUser($event, taskFilterProperty)"></adf-cloud-people>
</div>
<adf-cloud-task-assignment-filter
*ngIf="isAssignmentType(taskFilterProperty)"
[taskFilterProperty]="taskFilterProperty"
(assignedChange)="onAssignedChange($event)"
(assignedGroupChange)="onAssignedGroupsChange($event)"></adf-cloud-task-assignment-filter>
</ng-container>
</div>
</form>

View File

@@ -27,7 +27,7 @@ import { AbstractControl, FormBuilder, FormGroup } from '@angular/forms';
import { debounceTime, filter, takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs';
import { DateAdapter } from '@angular/material/core';
import { IdentityUserModel, UserPreferencesService, UserPreferenceValues } from '@alfresco/adf-core';
import { IdentityGroupModel, IdentityUserModel, UserPreferencesService, UserPreferenceValues } from '@alfresco/adf-core';
@Directive()
// tslint:disable-next-line: directive-class-suffix
@@ -202,6 +202,10 @@ export abstract class BaseEditTaskFilterCloudComponent implements OnInit, OnChan
return this.isDisabledForDefaultFilters(action) ? true : this.hasFormChanged(action);
}
isAssignmentType(property: TaskFilterProperties): boolean {
return property.type === 'assignment';
}
/**
* Return filter name
* @param filterName
@@ -300,6 +304,16 @@ export abstract class BaseEditTaskFilterCloudComponent implements OnInit, OnChan
this.getPropertyController(userProperty).setValue(users[0]);
}
onAssignedChange(assignedValue: IdentityUserModel) {
this.editTaskFilterForm.get('candidateGroups').setValue([]);
this.editTaskFilterForm.get('assignee').setValue(assignedValue?.username);
}
onAssignedGroupsChange(groups: IdentityGroupModel[]) {
this.editTaskFilterForm.get('assignee').setValue(null);
this.editTaskFilterForm.get('candidateGroups').setValue(groups);
}
hasError(property: TaskFilterProperties): boolean {
return this.getPropertyController(property).errors && this.getPropertyController(property).errors.invalid;
}

View File

@@ -580,7 +580,6 @@ describe('EditServiceTaskFilterCloudComponent', () => {
expect(deleteButton).toBeFalsy();
});
}));
});
describe('edit filter actions', () => {

View File

@@ -694,6 +694,32 @@ describe('EditTaskFilterCloudComponent', () => {
expect(component.changedTaskFilter.createdTo).toEqual(dateFilter.endDate);
done();
});
component.onFilterChange();
});
it('should show the task assignment filter', () => {
component.appName = 'fake';
component.filterProperties = ['assignment'];
const taskFilterIdChange = new SimpleChange(undefined, 'mock-task-filter-id', true);
component.ngOnChanges({ 'id': taskFilterIdChange });
fixture.detectChanges();
const assignmentComponent = fixture.debugElement.nativeElement.querySelector('adf-cloud-task-assignment-filter');
expect(assignmentComponent).toBeTruthy();
});
it('should filter by user assignment', (done) => {
const identityUserMock = { firstName: 'fake-identity-first-name', username: 'username', lastName: 'fake-identity-last-name', email: 'fakeIdentity@email.com' };
component.appName = 'fake';
component.filterProperties = ['assignment'];
const taskFilterIdChange = new SimpleChange(undefined, 'mock-task-filter-id', true);
component.ngOnChanges({ 'id': taskFilterIdChange });
component.onAssignedChange(identityUserMock);
component.filterChange.subscribe(() => {
expect(component.changedTaskFilter.assignee).toEqual(identityUserMock.username);
done();
});
component.onFilterChange();
});
@@ -730,6 +756,26 @@ describe('EditTaskFilterCloudComponent', () => {
expect(component.changedTaskFilter.createdTo).toEqual(dateFilter.endDate);
done();
});
component.onFilterChange();
});
it('should filter by candidateGroups assignment', (done) => {
const identityGroupsMock = [
{ name: 'group1'},
{ name: 'group2'}
];
component.appName = 'fake';
component.filterProperties = ['assignment'];
const taskFilterIdChange = new SimpleChange(undefined, 'mock-task-filter-id', true);
component.ngOnChanges({ 'id': taskFilterIdChange });
fixture.detectChanges();
component.onAssignedGroupsChange(identityGroupsMock);
component.filterChange.subscribe(() => {
expect(component.changedTaskFilter.candidateGroups).toEqual(identityGroupsMock);
done();
});
component.onFilterChange();
});
});

View File

@@ -358,6 +358,16 @@ export class EditTaskFilterCloudComponent extends BaseEditTaskFilterCloudCompone
key: 'completedBy',
value: this.taskFilter.completedBy ? [this.taskFilter.completedBy] : null,
selectionMode: 'single'
}),
new TaskFilterProperties({
label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.ASSIGNMENT',
type: 'assignment',
key: 'assignment',
attributes: { assignee: 'assignee', candidateGroups: 'candidateGroups'},
value: {
assignee: this.taskFilter.assignee || null,
candidateGroups: this.taskFilter.candidateGroups || []
}
})
];
}

View File

@@ -0,0 +1,20 @@
<div fxLayout="row">
<mat-form-field>
<mat-select class="adf-task-assignment-filter"
placeholder="{{ 'ADF_CLOUD_TASK_ASSIGNEMNT_FILTER.ASSIGNMENT_TYPE' | translate }}"
[(ngModel)]="assignmentType"
(ngModelChange)="onAssignmentTypeChange(assignmentType)">
<mat-option [value]="assignmentTypeList.currentUser">{{ 'ADF_CLOUD_TASK_ASSIGNEMNT_FILTER.ASSIGNED_TO_ME' | translate }} </mat-option>
<mat-option [value]="assignmentTypeList.unassigned">{{ 'ADF_CLOUD_TASK_ASSIGNEMNT_FILTER.UNASSIGNED' | translate }} </mat-option>
<mat-option [value]="assignmentTypeList.candidateGroups">{{ 'ADF_CLOUD_TASK_ASSIGNEMNT_FILTER.LIST_OF_CANDIDATE_USERS' | translate }} </mat-option>
</mat-select>
</mat-form-field>
<adf-cloud-group class="adf-group-cloud-filter" *ngIf="isCandidateGroupsType()"
[mode]="'multiple'"
[appName]="appName"
[preSelectGroups]="candidateGroups"
[searchGroupsControl]="groupForm"
(changedGroups)="onChangedGroups($event)"
[title]="'ADF_CLOUD_TASK_LIST.START_TASK.FORM.LABEL.CANDIDATE_GROUP'"></adf-cloud-group>
</div>

View File

@@ -0,0 +1,8 @@
.adf-task-assignment-filter {
margin-right: 10px;
}
.adf-group-cloud-filter {
margin-left:15px;
flex: 1;
}

View File

@@ -0,0 +1,76 @@
/*!
* @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 { ComponentFixture, TestBed } from '@angular/core/testing';
import { setupTestBed, IdentityUserService, TranslationService, TranslationMock } from '@alfresco/adf-core';
import { TranslateModule } from '@ngx-translate/core';
import { TaskAssignmentFilterCloudComponent } from './task-assignment-filter.component';
import { GroupCloudModule } from 'process-services-cloud/src/lib/group/public-api';
import { TaskFiltersCloudModule } from '../../task-filters-cloud.module';
import { AssignmentType } from '../../models/filter-cloud.model';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
describe('EditTaskFilterCloudComponent', () => {
let component: TaskAssignmentFilterCloudComponent;
let fixture: ComponentFixture<TaskAssignmentFilterCloudComponent>;
let identityUserService: IdentityUserService;
const identityUserMock = { firstName: 'fake-identity-first-name', lastName: 'fake-identity-last-name', email: 'fakeIdentity@email.com' };
setupTestBed({
imports: [
TranslateModule.forRoot(),
GroupCloudModule,
TaskFiltersCloudModule,
NoopAnimationsModule
],
providers: [
{ provide: TranslationService, useClass: TranslationMock }
]
});
beforeEach(() => {
fixture = TestBed.createComponent(TaskAssignmentFilterCloudComponent);
component = fixture.componentInstance;
identityUserService = TestBed.inject(IdentityUserService);
component.taskFilterProperty = {
key: 'assignment',
label: 'mock-filter',
value: null,
type: 'dateRange',
attributes: null,
options: null
};
fixture.detectChanges();
});
afterEach(() => fixture.destroy());
it('should emit the current user info when assignment is the current user', () => {
spyOn(identityUserService, 'getCurrentUserInfo').and.returnValue(identityUserMock);
spyOn(component.assignedChange, 'emit');
component.onAssignmentTypeChange(AssignmentType.CURRENT_USER);
fixture.detectChanges();
expect(component.assignedChange.emit).toHaveBeenCalledWith(identityUserMock);
});
it('should show the candidate groups', () => {
component.assignmentType = AssignmentType.CANDIDATE_GROUPS;
fixture.detectChanges();
const candidateGroups = fixture.debugElement.nativeElement.querySelector('.adf-group-cloud-filter');
expect(candidateGroups).toBeTruthy();
});
});

View File

@@ -0,0 +1,94 @@
/*!
* @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, Output, EventEmitter, OnInit } from '@angular/core';
import { AbstractControl, FormControl } from '@angular/forms';
import { IdentityGroupModel, IdentityUserModel, IdentityUserService } from '@alfresco/adf-core';
import { AssignmentType, TaskFilterProperties } from '../../models/filter-cloud.model';
@Component({
selector: 'adf-cloud-task-assignment-filter',
templateUrl: './task-assignment-filter.component.html',
styleUrls: ['./task-assignment-filter.component.scss']
})
export class TaskAssignmentFilterCloudComponent implements OnInit {
@Input() appName: string;
@Input() taskFilterProperty: TaskFilterProperties;
@Output() assignedChange = new EventEmitter<IdentityUserModel>();
@Output() assignedGroupChange = new EventEmitter<IdentityGroupModel[]>();
assignmentType: AssignmentType;
candidateGroups: IdentityGroupModel[] = [];
groupForm: AbstractControl = new FormControl('');
assignmentTypeList = {
unassigned: AssignmentType.UNASSIGNED,
currentUser: AssignmentType.CURRENT_USER,
candidateGroups: AssignmentType.CANDIDATE_GROUPS
};
constructor(private identityUserService: IdentityUserService) {}
ngOnInit() {
if (this.isFilterPropertyDefined()) {
this.setDefaultAssignedGroups();
this.setDefaultAssignmentType();
}
}
isCandidateGroupsType(): boolean {
return this.assignmentType === AssignmentType.CANDIDATE_GROUPS;
}
onAssignmentTypeChange(type: any) {
this.candidateGroups = [];
if (type === AssignmentType.CURRENT_USER) {
this.assignedChange.emit(this.identityUserService.getCurrentUserInfo());
} else if (type === AssignmentType.UNASSIGNED) {
this.assignedChange.emit(null);
}
}
onChangedGroups(groups: IdentityGroupModel[]) {
this.assignedGroupChange.emit(groups);
}
private setDefaultAssignmentType() {
const assignmentAttr = this.taskFilterProperty.attributes['assignee'];
const assignee = this.taskFilterProperty.value[assignmentAttr];
if (this.candidateGroups.length > 0) {
this.assignmentType = AssignmentType.CANDIDATE_GROUPS;
} else if (assignee) {
this.assignmentType = AssignmentType.CURRENT_USER;
} else {
this.assignmentType = AssignmentType.UNASSIGNED;
}
}
private setDefaultAssignedGroups() {
const assignmentGroupsAttr = this.taskFilterProperty.attributes['candidateGroups'];
this.candidateGroups = this.taskFilterProperty.value[assignmentGroupsAttr];
}
private isFilterPropertyDefined(): boolean {
return !!this.taskFilterProperty.attributes && !!this.taskFilterProperty.value;
}
}

View File

@@ -18,9 +18,9 @@
import { DateCloudFilterType } from '../../../models/date-cloud-filter.model';
import { DateRangeFilterService } from '../../../common/date-range-filter/date-range-filter.service';
import { ComponentSelectionMode } from '../../../types';
import { IdentityUserModel } from '@alfresco/adf-core';
import { IdentityUserModel, IdentityGroupModel } from '@alfresco/adf-core';
export class TaskFilterCloudModel {
export class TaskFilterCloudModel {
id: string;
name: string;
key: string;
@@ -30,6 +30,7 @@ export class TaskFilterCloudModel {
status: string;
sort: string;
assignee: string;
candidateGroups: IdentityGroupModel[];
order: string;
owner: string;
processDefinitionName?: string;
@@ -47,6 +48,7 @@ export class TaskFilterCloudModel {
lastModifiedTo: string;
completedDateType: DateCloudFilterType;
createdDateType: DateCloudFilterType;
assignmentType: AssignmentType;
completedDate: Date;
completedBy: IdentityUserModel;
@@ -94,6 +96,7 @@ export class TaskFilterCloudModel {
this.createdDateType = obj.createdDateType || null;
this.createdFrom = obj._createdFrom || null;
this.createdTo = obj._createdTo || null;
this.candidateGroups = obj.candidateGroups || null;
}
}
@@ -245,6 +248,12 @@ export interface FilterOptions {
value?: string;
}
export enum AssignmentType {
CURRENT_USER = 'CURRENT_USER',
UNASSIGNED = 'UNASSIGNED',
CANDIDATE_GROUPS = 'CANDIDATE_GROUPS'
}
export class TaskFilterProperties {
label: string;
type: string;

View File

@@ -31,6 +31,8 @@ import { EditServiceTaskFilterCloudComponent } from './components/edit-task-filt
import { EditTaskFilterCloudComponent } from './components/edit-task-filters/edit-task-filter-cloud.component';
import { TaskFilterDialogCloudComponent } from './components/task-filter-dialog/task-filter-dialog-cloud.component';
import { ServiceTaskFiltersCloudComponent } from './components/service-task-filters-cloud.component';
import { TaskAssignmentFilterCloudComponent } from './components/task-assignment-filter/task-assignment-filter.component';
import { GroupCloudModule } from '../../group/group-cloud.module';
@NgModule({
imports: [
@@ -42,6 +44,7 @@ import { ServiceTaskFiltersCloudComponent } from './components/service-task-filt
MaterialModule,
AppListCloudModule,
CoreModule,
GroupCloudModule,
ProcessCommonModule,
PeopleCloudModule
],
@@ -50,7 +53,8 @@ import { ServiceTaskFiltersCloudComponent } from './components/service-task-filt
ServiceTaskFiltersCloudComponent,
EditTaskFilterCloudComponent,
EditServiceTaskFilterCloudComponent,
TaskFilterDialogCloudComponent
TaskFilterDialogCloudComponent,
TaskAssignmentFilterCloudComponent
],
exports: [
TaskFiltersCloudComponent,

View File

@@ -127,6 +127,10 @@ export class TaskListCloudComponent extends BaseTaskListCloudComponent {
@Input()
completedTo: string = '';
/** Filter the tasks. Display only tasks with candidateGroups equal to the supplied value. */
@Input()
candidateGroupId: string = '';
constructor(private taskListCloudService: TaskListCloudService,
appConfigService: AppConfigService,
userPreferences: UserPreferencesService) {
@@ -175,7 +179,8 @@ export class TaskListCloudComponent extends BaseTaskListCloudComponent {
completedBy: this.completedBy,
completedFrom: this.completedFrom,
completedTo: this.completedTo,
completedDate: this.completedDate
completedDate: this.completedDate,
candidateGroupId: this.candidateGroupId
};
return new TaskQueryCloudRequestModel(requestNode);
}

View File

@@ -48,6 +48,7 @@ export class TaskQueryCloudRequestModel {
completedDate?: Date;
completedFrom?: string;
completedTo?: string;
candidateGroupId?: string;
constructor(obj?: any) {
if (obj) {
@@ -81,6 +82,7 @@ export class TaskQueryCloudRequestModel {
this.completedFrom = obj.completedFrom;
this.completedTo = obj.completedTo;
this.completedDate = obj.completedDate;
this.candidateGroupId = obj.candidateGroupId;
}
}
}