mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
[MNT-24496] ADW Integration with APS Improvements - Re-assign Tasks (#10350)
* [MNT-24496] ADW Integration with APS Improvements - Re-assign Tasks * [MNT-24496] code improvements * [MNT-24496] remove duplications * [MNT-24496] add unit test * [MNT-24496] cr fixes * [MNT-24496] empty commit [ci:force] * [MNT-24496] fix unit test * [MNT-24496] empty commit [ci:force] * [MNT-24496] cr fix * [MNT-24496] remove redundant import
This commit is contained in:
committed by
GitHub
parent
558ff71878
commit
258f01803c
@@ -54,6 +54,7 @@ describe('TaskDetailsComponent', () => {
|
||||
let getTaskDetailsSpy: jasmine.Spy;
|
||||
let getTasksSpy: jasmine.Spy;
|
||||
let assignTaskSpy: jasmine.Spy;
|
||||
let getWorkflowUsersSpy: jasmine.Spy;
|
||||
let taskCommentsService: CommentsService;
|
||||
let peopleProcessService: PeopleProcessService;
|
||||
|
||||
@@ -63,6 +64,7 @@ describe('TaskDetailsComponent', () => {
|
||||
});
|
||||
peopleProcessService = TestBed.inject(PeopleProcessService);
|
||||
spyOn(peopleProcessService, 'getCurrentUserInfo').and.returnValue(of({ email: 'fake-email' } as any));
|
||||
getWorkflowUsersSpy = spyOn(peopleProcessService, 'getWorkflowUsers').and.returnValue(of([]));
|
||||
|
||||
const taskListService = TestBed.inject(TaskListService);
|
||||
spyOn(taskListService, 'getTaskChecklist').and.returnValue(of(noDataMock));
|
||||
@@ -371,7 +373,7 @@ describe('TaskDetailsComponent', () => {
|
||||
});
|
||||
|
||||
it('should return an observable with user search results', () => {
|
||||
spyOn(peopleProcessService, 'getWorkflowUsers').and.returnValue(
|
||||
getWorkflowUsersSpy.and.returnValue(
|
||||
of([
|
||||
{
|
||||
id: 1,
|
||||
@@ -402,7 +404,7 @@ describe('TaskDetailsComponent', () => {
|
||||
});
|
||||
|
||||
it('should return an empty list for not valid search', () => {
|
||||
spyOn(peopleProcessService, 'getWorkflowUsers').and.returnValue(of([]));
|
||||
getWorkflowUsersSpy.and.returnValue(of([]));
|
||||
|
||||
let lastValue: LightUserRepresentation[];
|
||||
component.peopleSearch.subscribe((users) => (lastValue = users));
|
||||
|
@@ -1,23 +1,23 @@
|
||||
<mat-card appearance="outlined" *ngIf="taskDetails" class="adf-card-container">
|
||||
<mat-card-content>
|
||||
<adf-card-view [properties]="properties" [editable]="!isCompleted()" [displayClearAction]="displayDateClearAction" />
|
||||
<adf-card-view [properties]="properties" [editable]="!readOnly && !isCompleted()" [displayClearAction]="displayDateClearAction"/>
|
||||
</mat-card-content>
|
||||
|
||||
<mat-card-actions class="adf-controls" *ngIf="showClaimRelease">
|
||||
<button *ngIf="isTaskClaimedByCandidateMember()"
|
||||
mat-button
|
||||
data-automation-id="header-unclaim-button"
|
||||
id="unclaim-task"
|
||||
<button *ngIf="isTaskClaimedByCandidateMember()"
|
||||
mat-button
|
||||
data-automation-id="header-unclaim-button"
|
||||
id="unclaim-task"
|
||||
class="adf-claim-controls"
|
||||
adf-unclaim-task
|
||||
[taskId]="taskDetails.id"
|
||||
(success)="onUnclaimTask($event)">
|
||||
{{ 'ADF_TASK_LIST.DETAILS.BUTTON.UNCLAIM' | translate }}
|
||||
</button>
|
||||
<button *ngIf="isTaskClaimable()"
|
||||
mat-button
|
||||
data-automation-id="header-claim-button"
|
||||
id="claim-task"
|
||||
<button *ngIf="isTaskClaimable()"
|
||||
mat-button
|
||||
data-automation-id="header-claim-button"
|
||||
id="claim-task"
|
||||
class="adf-claim-controls"
|
||||
adf-claim-task
|
||||
[taskId]="taskDetails.id"
|
||||
|
@@ -15,10 +15,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { AppConfigService } from '@alfresco/adf-core';
|
||||
import { of } from 'rxjs';
|
||||
import { AppConfigService, CardViewUpdateService } from '@alfresco/adf-core';
|
||||
import { of, Subject } from 'rxjs';
|
||||
import {
|
||||
completedTaskDetailsMock,
|
||||
taskDetailsMock,
|
||||
@@ -32,6 +32,7 @@ import { TaskHeaderComponent } from './task-header.component';
|
||||
import { ProcessTestingModule } from '../../../testing/process.testing.module';
|
||||
import { PeopleProcessService } from '../../../services/people-process.service';
|
||||
import { TaskRepresentation } from '@alfresco/js-api';
|
||||
import { SimpleChanges } from '@angular/core';
|
||||
|
||||
describe('TaskHeaderComponent', () => {
|
||||
let service: TaskListService;
|
||||
@@ -39,6 +40,7 @@ describe('TaskHeaderComponent', () => {
|
||||
let fixture: ComponentFixture<TaskHeaderComponent>;
|
||||
let peopleProcessService: PeopleProcessService;
|
||||
let appConfigService: AppConfigService;
|
||||
let cardViewUpdateService: CardViewUpdateService;
|
||||
|
||||
const fakeBpmAssignedUser: any = {
|
||||
id: 1001,
|
||||
@@ -63,8 +65,10 @@ describe('TaskHeaderComponent', () => {
|
||||
service = TestBed.inject(TaskListService);
|
||||
peopleProcessService = TestBed.inject(PeopleProcessService);
|
||||
spyOn(peopleProcessService, 'getCurrentUserInfo').and.returnValue(of(fakeBpmAssignedUser));
|
||||
spyOn(peopleProcessService, 'getWorkflowUsers').and.returnValue(of([{ id: 1, firstName: 'Test', lastName: 'User' }]));
|
||||
component.taskDetails = new TaskRepresentation(taskDetailsMock);
|
||||
appConfigService = TestBed.inject(AppConfigService);
|
||||
cardViewUpdateService = TestBed.inject(CardViewUpdateService);
|
||||
});
|
||||
|
||||
const getClaimButton = () => fixture.debugElement.query(By.css('[data-automation-id="header-claim-button"]'))?.nativeElement as HTMLButtonElement;
|
||||
@@ -72,6 +76,66 @@ describe('TaskHeaderComponent', () => {
|
||||
const getUnclaimButton = () =>
|
||||
fixture.debugElement.query(By.css('[data-automation-id="header-unclaim-button"]'))?.nativeElement as HTMLButtonElement;
|
||||
|
||||
const triggerNgOnChanges = (currentValue: any, previousValue: any) => {
|
||||
const changes: SimpleChanges = {
|
||||
taskDetails: {
|
||||
currentValue,
|
||||
previousValue,
|
||||
firstChange: false,
|
||||
isFirstChange: () => false
|
||||
}
|
||||
};
|
||||
component.ngOnChanges(changes);
|
||||
};
|
||||
|
||||
it('should set users$ when autocompleteInputValue$ emits new value', fakeAsync(() => {
|
||||
const autocompleteInputValue$ = cardViewUpdateService.autocompleteInputValue$;
|
||||
component.ngOnInit();
|
||||
|
||||
autocompleteInputValue$.next('test');
|
||||
tick(300);
|
||||
|
||||
component.users$.subscribe((users) => {
|
||||
expect(users).toEqual([{ key: 1, label: 'Test User' }]);
|
||||
});
|
||||
}));
|
||||
|
||||
it('should call initData on resetChanges subscription', () => {
|
||||
const resetChanges$ = new Subject<void>();
|
||||
component.resetChanges = resetChanges$;
|
||||
spyOn(component, 'initData');
|
||||
component.ngOnInit();
|
||||
|
||||
expect(component.initData).toHaveBeenCalledTimes(1);
|
||||
resetChanges$.next();
|
||||
expect(component.initData).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should call initData when assignee changes', () => {
|
||||
spyOn(component, 'initData');
|
||||
triggerNgOnChanges({ id: '1', assignee: { id: '2' } }, { id: '1', assignee: { id: '1' } });
|
||||
expect(component.initData).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call initData when task id changes', () => {
|
||||
spyOn(component, 'initData');
|
||||
triggerNgOnChanges({ id: '2', assignee: { id: '1' } }, { id: '1', assignee: { id: '1' } });
|
||||
expect(component.initData).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call refreshData when taskDetails change', () => {
|
||||
spyOn(component, 'refreshData');
|
||||
triggerNgOnChanges(
|
||||
{ id: '1', assignee: { id: '1' }, description: 'one' },
|
||||
{
|
||||
id: '1',
|
||||
assignee: { id: '1' },
|
||||
description: 'two'
|
||||
}
|
||||
);
|
||||
expect(component.refreshData).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should render empty component if no task details provided', async () => {
|
||||
component.taskDetails = undefined;
|
||||
|
||||
@@ -87,7 +151,7 @@ describe('TaskHeaderComponent', () => {
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const formNameEl = fixture.debugElement.query(By.css('[data-automation-id="header-assignee"] .adf-textitem-clickable-value'));
|
||||
const formNameEl = fixture.debugElement.query(By.css('[data-automation-id="header-assignee"] .adf-property-value'));
|
||||
expect(formNameEl.nativeElement.value).toBe('Wilbur Adams');
|
||||
});
|
||||
|
||||
@@ -98,7 +162,7 @@ describe('TaskHeaderComponent', () => {
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-assignee"] .adf-textitem-clickable-value'));
|
||||
const valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-assignee"] .adf-property-value'));
|
||||
expect(valueEl.nativeElement.value).toBe('ADF_TASK_LIST.PROPERTIES.ASSIGNEE_DEFAULT');
|
||||
});
|
||||
|
||||
|
@@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, ViewEncapsulation } from '@angular/core';
|
||||
import { Component, EventEmitter, DestroyRef, Input, OnChanges, OnInit, Output, SimpleChanges, ViewEncapsulation, inject } from '@angular/core';
|
||||
import {
|
||||
CardViewDateItemModel,
|
||||
CardViewMapItemModel,
|
||||
@@ -25,7 +25,10 @@ import {
|
||||
AppConfigService,
|
||||
CardViewIntItemModel,
|
||||
CardViewItemLengthValidator,
|
||||
CardViewComponent
|
||||
CardViewComponent,
|
||||
CardViewUpdateService,
|
||||
CardViewSelectItemModel,
|
||||
CardViewSelectItemOption
|
||||
} from '@alfresco/adf-core';
|
||||
import { PeopleProcessService } from '../../../services/people-process.service';
|
||||
import { TaskDescriptionValidator } from '../../validators/task-description.validator';
|
||||
@@ -36,6 +39,9 @@ import { MatButtonModule } from '@angular/material/button';
|
||||
import { UnclaimTaskDirective } from '../task-form/unclaim-task.directive';
|
||||
import { ClaimTaskDirective } from '../task-form/claim-task.directive';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { debounceTime, filter, map, switchMap } from 'rxjs/operators';
|
||||
import { BehaviorSubject, Observable, Subject } from 'rxjs';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-task-header',
|
||||
@@ -58,6 +64,18 @@ export class TaskHeaderComponent implements OnChanges, OnInit {
|
||||
@Input()
|
||||
showClaimRelease = true;
|
||||
|
||||
/**
|
||||
* (optional) This flag sets read-only mode, preventing changes.
|
||||
*/
|
||||
@Input()
|
||||
readOnly = false;
|
||||
|
||||
/**
|
||||
* Refreshes the card data when an event emitted.
|
||||
*/
|
||||
@Input()
|
||||
resetChanges = new Subject<void>();
|
||||
|
||||
/** Emitted when the task is claimed. */
|
||||
@Output()
|
||||
claim: EventEmitter<any> = new EventEmitter<any>();
|
||||
@@ -72,24 +90,51 @@ export class TaskHeaderComponent implements OnChanges, OnInit {
|
||||
dateLocale: string;
|
||||
|
||||
private currentUserId: number;
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
private readonly usersSubject$ = new BehaviorSubject<CardViewSelectItemOption<number>[]>([]);
|
||||
users$ = this.usersSubject$.asObservable();
|
||||
|
||||
constructor(
|
||||
private peopleProcessService: PeopleProcessService,
|
||||
private translationService: TranslationService,
|
||||
private appConfig: AppConfigService
|
||||
private readonly appConfig: AppConfigService,
|
||||
private readonly cardViewUpdateService: CardViewUpdateService
|
||||
) {
|
||||
this.dateFormat = this.appConfig.get('dateValues.defaultDateFormat');
|
||||
this.dateLocale = this.appConfig.get('dateValues.defaultDateLocale');
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.loadCurrentBpmUserId();
|
||||
this.initData();
|
||||
this.peopleProcessService
|
||||
.getCurrentUserInfo()
|
||||
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe((res) => {
|
||||
this.currentUserId = res ? +res.id : null;
|
||||
this.initData();
|
||||
});
|
||||
|
||||
this.cardViewUpdateService.autocompleteInputValue$
|
||||
.pipe(
|
||||
filter((res) => res.length > 0),
|
||||
debounceTime(300),
|
||||
switchMap((res) => this.getUsers(res)),
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
)
|
||||
.subscribe((users) => {
|
||||
this.usersSubject$.next(users);
|
||||
});
|
||||
|
||||
this.resetChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
|
||||
this.initData();
|
||||
});
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
const taskDetailsChange = changes['taskDetails'];
|
||||
if (taskDetailsChange?.currentValue?.id !== taskDetailsChange?.previousValue?.id) {
|
||||
if (
|
||||
taskDetailsChange?.currentValue?.id !== taskDetailsChange?.previousValue?.id ||
|
||||
taskDetailsChange?.currentValue?.assignee?.id !== taskDetailsChange?.previousValue?.assignee?.id
|
||||
) {
|
||||
this.initData();
|
||||
} else {
|
||||
this.refreshData();
|
||||
@@ -249,15 +294,31 @@ export class TaskHeaderComponent implements OnChanges, OnInit {
|
||||
return this.taskDetails.duration ? `${this.taskDetails.duration} ms` : '';
|
||||
}
|
||||
|
||||
private getUsers(searchQuery: string): Observable<CardViewSelectItemOption<number>[]> {
|
||||
return this.peopleProcessService.getWorkflowUsers(undefined, searchQuery).pipe(
|
||||
map((users) =>
|
||||
users
|
||||
.filter((user) => user.id !== this.currentUserId)
|
||||
.map(({ id, firstName = '', lastName = '' }) => ({
|
||||
key: id,
|
||||
label: `${firstName} ${lastName}`.trim()
|
||||
}))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private initDefaultProperties(parentInfoMap: Map<string, string>): any[] {
|
||||
return [
|
||||
new CardViewTextItemModel({
|
||||
new CardViewSelectItemModel({
|
||||
label: 'ADF_TASK_LIST.PROPERTIES.ASSIGNEE',
|
||||
value: this.taskDetails.getFullName(),
|
||||
value: this.taskDetails.getFullName()
|
||||
? this.taskDetails.getFullName()
|
||||
: this.translationService.instant('ADF_TASK_LIST.PROPERTIES.ASSIGNEE_DEFAULT'),
|
||||
key: 'assignee',
|
||||
default: this.translationService.instant('ADF_TASK_LIST.PROPERTIES.ASSIGNEE_DEFAULT'),
|
||||
clickable: !this.isCompleted(),
|
||||
icon: 'create'
|
||||
editable: this.isAssignedToCurrentUser(),
|
||||
autocompleteBased: true,
|
||||
icon: 'create',
|
||||
options$: this.users$
|
||||
}),
|
||||
new CardViewTextItemModel({
|
||||
label: 'ADF_TASK_LIST.PROPERTIES.STATUS',
|
||||
@@ -345,13 +406,4 @@ export class TaskHeaderComponent implements OnChanges, OnInit {
|
||||
private isValidSelection(filteredProperties: string[], cardItem: CardViewBaseItemModel): boolean {
|
||||
return filteredProperties ? filteredProperties.indexOf(cardItem.key) >= 0 : true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads current bpm userId
|
||||
*/
|
||||
private loadCurrentBpmUserId(): void {
|
||||
this.peopleProcessService.getCurrentUserInfo().subscribe((res) => {
|
||||
this.currentUserId = res ? +res.id : null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user