[ACS-5308] Highlight the selected filter in ADW (#8649)

* changes for highlight the selected filter

* implemented the changes as per review comments

* added test cases and implemented review comments

* fix code and linting errors

* e2e fixes

* linting fix

* lint fix

* minor fixes

* fix for unit test case

* e2e fix for process

---------

Co-authored-by: Denys Vuika <denys.vuika@gmail.com>
This commit is contained in:
Yasa-Nataliya
2023-06-16 15:20:08 +05:30
committed by GitHub
parent ac694cd7a2
commit 13f4f62608
6 changed files with 125 additions and 29 deletions

View File

@@ -1,4 +1,4 @@
<div *ngFor="let filter of filters" class="adf-filters__entry" [class.adf-active]="currentFilter === filter"> <div *ngFor="let filter of filters" class="adf-filters__entry" [class.adf-active]="isActiveRoute(filter)">
<button <button
(click)="selectFilter(filter)" (click)="selectFilter(filter)"
[attr.aria-label]="filter.name | translate" [attr.aria-label]="filter.name | translate"

View File

@@ -27,6 +27,8 @@ import { By } from '@angular/platform-browser';
import { fakeProcessFilters } from '../../mock/process/process-filters.mock'; import { fakeProcessFilters } from '../../mock/process/process-filters.mock';
import { ProcessTestingModule } from '../../testing/process.testing.module'; import { ProcessTestingModule } from '../../testing/process.testing.module';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { NavigationStart, Router } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
describe('ProcessFiltersComponent', () => { describe('ProcessFiltersComponent', () => {
@@ -34,11 +36,13 @@ describe('ProcessFiltersComponent', () => {
let fixture: ComponentFixture<ProcessFiltersComponent>; let fixture: ComponentFixture<ProcessFiltersComponent>;
let processFilterService: ProcessFilterService; let processFilterService: ProcessFilterService;
let appsProcessService: AppsProcessService; let appsProcessService: AppsProcessService;
let router: Router;
setupTestBed({ setupTestBed({
imports: [ imports: [
TranslateModule.forRoot(), TranslateModule.forRoot(),
ProcessTestingModule ProcessTestingModule,
RouterTestingModule
], ],
schemas: [CUSTOM_ELEMENTS_SCHEMA] schemas: [CUSTOM_ELEMENTS_SCHEMA]
}); });
@@ -48,6 +52,7 @@ describe('ProcessFiltersComponent', () => {
filterList = fixture.componentInstance; filterList = fixture.componentInstance;
processFilterService = TestBed.inject(ProcessFilterService); processFilterService = TestBed.inject(ProcessFilterService);
appsProcessService = TestBed.inject(AppsProcessService); appsProcessService = TestBed.inject(AppsProcessService);
router = TestBed.inject(Router);
}); });
afterEach(() => { afterEach(() => {
@@ -305,4 +310,18 @@ describe('ProcessFiltersComponent', () => {
const filters: any = fixture.debugElement.queryAll(By.css('.adf-icon')); const filters: any = fixture.debugElement.queryAll(By.css('.adf-icon'));
expect(filters.length).toBe(0); expect(filters.length).toBe(0);
}); });
it('should set isProcessActive to true when activeRoute includes "processes"', () => {
const navigationStartEvent = new NavigationStart(1, 'processes/123');
spyOn(router.events, 'pipe').and.returnValue(of(navigationStartEvent));
fixture.detectChanges();
expect(filterList.isProcessActive).toBe(true);
});
it('should set isProcessActive to false when activeRoute does not include "processes"', () => {
const navigationStartEvent = new NavigationStart(1, 'other-route');
spyOn(router.events, 'pipe').and.returnValue(of(navigationStartEvent));
fixture.detectChanges();
expect(filterList.isProcessActive).toBe(false);
});
}); });

View File

@@ -15,13 +15,16 @@
* limitations under the License. * limitations under the License.
*/ */
import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, ViewEncapsulation } from '@angular/core'; import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges, ViewEncapsulation } from '@angular/core';
import { ProcessInstanceFilterRepresentation, UserProcessInstanceFilterRepresentation } from '@alfresco/js-api'; import { ProcessInstanceFilterRepresentation, UserProcessInstanceFilterRepresentation } from '@alfresco/js-api';
import { Observable } from 'rxjs'; import { Observable, Subject } from 'rxjs';
import { FilterProcessRepresentationModel } from '../models/filter-process.model'; import { FilterProcessRepresentationModel } from '../models/filter-process.model';
import { ProcessFilterService } from './../services/process-filter.service'; import { ProcessFilterService } from './../services/process-filter.service';
import { AppsProcessService } from '../../app-list/services/apps-process.service'; import { AppsProcessService } from '../../app-list/services/apps-process.service';
import { IconModel } from '../../app-list/icon.model'; import { IconModel } from '../../app-list/icon.model';
import { NavigationStart, Router } from '@angular/router';
import { filter, takeUntil } from 'rxjs/operators';
import { Location } from '@angular/common';
@Component({ @Component({
selector: 'adf-process-instance-filters', selector: 'adf-process-instance-filters',
@@ -29,7 +32,7 @@ import { IconModel } from '../../app-list/icon.model';
styleUrls: ['./process-filters.component.scss'], styleUrls: ['./process-filters.component.scss'],
encapsulation: ViewEncapsulation.None encapsulation: ViewEncapsulation.None
}) })
export class ProcessFiltersComponent implements OnInit, OnChanges { export class ProcessFiltersComponent implements OnInit, OnChanges, OnDestroy {
/** The parameters to filter the task filter. If there is no match then the default one /** The parameters to filter the task filter. If there is no match then the default one
* (ie, the first filter in the list) is selected. * (ie, the first filter in the list) is selected.
@@ -71,31 +74,51 @@ export class ProcessFiltersComponent implements OnInit, OnChanges {
filters: UserProcessInstanceFilterRepresentation [] = []; filters: UserProcessInstanceFilterRepresentation [] = [];
active = false; active = false;
isProcessRoute: boolean;
isProcessActive: boolean;
private onDestroy$ = new Subject<boolean>();
private iconsMDL: IconModel; private iconsMDL: IconModel;
constructor(private processFilterService: ProcessFilterService, constructor(private processFilterService: ProcessFilterService,
private appsProcessService: AppsProcessService) { private appsProcessService: AppsProcessService,
private router: Router,
private location: Location) {
} }
ngOnInit() { ngOnInit() {
this.iconsMDL = new IconModel(); this.iconsMDL = new IconModel();
this.router.events
.pipe(
filter((event) => event instanceof NavigationStart),
takeUntil(this.onDestroy$)
)
.subscribe((navigationStart: NavigationStart) => {
const activeRoute = navigationStart.url;
this.isProcessActive = activeRoute.includes('processes');
});
const currentRoute = this.location.path();
this.isProcessRoute = currentRoute.includes('processes');
} }
ngOnChanges(changes: SimpleChanges) { ngOnChanges(changes: SimpleChanges) {
const appId = changes['appId']; const appId = changes['appId'];
const appName = changes['appName']; const appName = changes['appName'];
const filter = changes['filterParam']; const filterParam = changes['filterParam'];
if (appId && (appId.currentValue || appId.currentValue === null)) { if (appId && (appId.currentValue || appId.currentValue === null)) {
this.getFiltersByAppId(appId.currentValue); this.getFiltersByAppId(appId.currentValue);
} else if (appName && appName.currentValue) { } else if (appName && appName.currentValue) {
this.getFiltersByAppName(appName.currentValue); this.getFiltersByAppName(appName.currentValue);
} else if (filter && filter.currentValue !== filter.previousValue) { } else if (filterParam && filterParam.currentValue !== filterParam.previousValue) {
this.selectProcessFilter(filter.currentValue); this.selectProcessFilter(filterParam.currentValue);
} }
} }
isActiveRoute(filterActive: ProcessInstanceFilterRepresentation): boolean {
return (this.isProcessActive || this.isProcessRoute) && this.currentFilter === filterActive;
}
/** /**
* Return the filter list filtered by appId * Return the filter list filtered by appId
* *
@@ -148,12 +171,12 @@ export class ProcessFiltersComponent implements OnInit, OnChanges {
/** /**
* Pass the selected filter as next * Pass the selected filter as next
* *
* @param filter * @param filterModel
*/ */
selectFilter(filter: ProcessInstanceFilterRepresentation) { selectFilter(filterModel: ProcessInstanceFilterRepresentation) {
this.currentFilter = filter; this.currentFilter = filterModel;
this.active = true; this.active = true;
this.filterClicked.emit(filter); this.filterClicked.emit(filterModel);
} }
/** /**
@@ -222,4 +245,9 @@ export class ProcessFiltersComponent implements OnInit, OnChanges {
this.filters = []; this.filters = [];
this.currentFilter = undefined; this.currentFilter = undefined;
} }
ngOnDestroy() {
this.onDestroy$.next(true);
this.onDestroy$.complete();
}
} }

View File

@@ -1,4 +1,4 @@
<div *ngFor="let filter of filters" class="adf-filters__entry" [class.adf-active]="currentFilter === filter"> <div *ngFor="let filter of filters" class="adf-filters__entry" [class.adf-active]="isActiveRoute(filter)">
<button <button
(click)="onFilterClick(filter)" (click)="onFilterClick(filter)"
[attr.aria-label]="filter.name | translate" [attr.aria-label]="filter.name | translate"

View File

@@ -28,6 +28,8 @@ import { ProcessTestingModule } from '../../testing/process.testing.module';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { fakeTaskFilters } from '../../mock/task/task-filters.mock'; import { fakeTaskFilters } from '../../mock/task/task-filters.mock';
import { NavigationStart, Router } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
describe('TaskFiltersComponent', () => { describe('TaskFiltersComponent', () => {
@@ -36,11 +38,13 @@ describe('TaskFiltersComponent', () => {
let appsProcessService: AppsProcessService; let appsProcessService: AppsProcessService;
let component: TaskFiltersComponent; let component: TaskFiltersComponent;
let fixture: ComponentFixture<TaskFiltersComponent>; let fixture: ComponentFixture<TaskFiltersComponent>;
let router: Router;
setupTestBed({ setupTestBed({
imports: [ imports: [
TranslateModule.forRoot(), TranslateModule.forRoot(),
ProcessTestingModule ProcessTestingModule,
RouterTestingModule
] ]
}); });
@@ -54,6 +58,7 @@ describe('TaskFiltersComponent', () => {
taskListService = TestBed.inject(TaskListService); taskListService = TestBed.inject(TaskListService);
taskFilterService = TestBed.inject(TaskFilterService); taskFilterService = TestBed.inject(TaskFilterService);
appsProcessService = TestBed.inject(AppsProcessService); appsProcessService = TestBed.inject(AppsProcessService);
router = TestBed.inject(Router);
}); });
it('should emit an error with a bad response', async () => { it('should emit an error with a bad response', async () => {
@@ -338,5 +343,19 @@ describe('TaskFiltersComponent', () => {
expect(taskFilterTwo.innerText).toBe('default-involved-filter'); expect(taskFilterTwo.innerText).toBe('default-involved-filter');
expect(taskFilterThree.innerText).toBe('default-completed-filter'); expect(taskFilterThree.innerText).toBe('default-completed-filter');
}); });
it('should set isTaskActive to true when activeRoute includes "tasks"', () => {
const navigationStartEvent = new NavigationStart(1, 'tasks/123');
spyOn(router.events, 'pipe').and.returnValue(of(navigationStartEvent));
fixture.detectChanges();
expect(component.isTaskActive).toBe(true);
});
it('should set isTaskActive to false when activeRoute does not include "tasks"', () => {
const navigationStartEvent = new NavigationStart(1, 'other-route');
spyOn(router.events, 'pipe').and.returnValue(of(navigationStartEvent));
fixture.detectChanges();
expect(component.isTaskActive).toBe(false);
});
}); });
}); });

View File

@@ -16,12 +16,14 @@
*/ */
import { AppsProcessService } from '../../app-list/services/apps-process.service'; import { AppsProcessService } from '../../app-list/services/apps-process.service';
import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, ViewEncapsulation } from '@angular/core'; import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges, ViewEncapsulation } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable, Subject } from 'rxjs';
import { FilterParamsModel, FilterRepresentationModel } from '../models/filter.model'; import { FilterParamsModel, FilterRepresentationModel } from '../models/filter.model';
import { TaskFilterService } from './../services/task-filter.service'; import { TaskFilterService } from './../services/task-filter.service';
import { TaskListService } from './../services/tasklist.service'; import { TaskListService } from './../services/tasklist.service';
import { IconModel } from '../../app-list/icon.model'; import { IconModel } from '../../app-list/icon.model';
import { ActivatedRoute, NavigationStart, Router } from '@angular/router';
import { filter, takeUntil } from 'rxjs/operators';
@Component({ @Component({
selector: 'adf-task-filters', selector: 'adf-task-filters',
@@ -29,7 +31,7 @@ import { IconModel } from '../../app-list/icon.model';
styleUrls: ['./task-filters.component.scss'], styleUrls: ['./task-filters.component.scss'],
encapsulation: ViewEncapsulation.None encapsulation: ViewEncapsulation.None
}) })
export class TaskFiltersComponent implements OnInit, OnChanges { export class TaskFiltersComponent implements OnInit, OnChanges, OnDestroy {
/** Parameters to use for the task filter. If there is no match then /** Parameters to use for the task filter. If there is no match then
* the default filter (the first one the list) is selected. * the default filter (the first one the list) is selected.
@@ -71,30 +73,53 @@ export class TaskFiltersComponent implements OnInit, OnChanges {
filters: FilterRepresentationModel [] = []; filters: FilterRepresentationModel [] = [];
private onDestroy$ = new Subject<boolean>();
isTaskRoute: boolean;
isTaskActive: boolean;
private iconsMDL: IconModel; private iconsMDL: IconModel;
constructor(private taskFilterService: TaskFilterService, constructor(private taskFilterService: TaskFilterService,
private taskListService: TaskListService, private taskListService: TaskListService,
private appsProcessService: AppsProcessService) { private appsProcessService: AppsProcessService,
private router: Router,
private activatedRoute: ActivatedRoute) {
} }
ngOnInit() { ngOnInit() {
this.iconsMDL = new IconModel(); this.iconsMDL = new IconModel();
this.router.events
.pipe(
filter((event) => event instanceof NavigationStart),
takeUntil(this.onDestroy$)
)
.subscribe((navigationStart: NavigationStart) => {
const activeRoute = navigationStart.url;
this.isTaskActive = activeRoute.includes('tasks');
});
this.activatedRoute.url.subscribe((segments) => {
const currentRoute = segments.join('/');
this.isTaskRoute = currentRoute.includes('tasks');
});
} }
ngOnChanges(changes: SimpleChanges) { ngOnChanges(changes: SimpleChanges) {
const appName = changes['appName']; const appName = changes['appName'];
const appId = changes['appId']; const appId = changes['appId'];
const filter = changes['filterParam']; const filterParam = changes['filterParam'];
if (appName && appName.currentValue) { if (appName && appName.currentValue) {
this.getFiltersByAppName(appName.currentValue); this.getFiltersByAppName(appName.currentValue);
} else if (appId && appId.currentValue !== appId.previousValue) { } else if (appId && appId.currentValue !== appId.previousValue) {
this.getFiltersByAppId(appId.currentValue); this.getFiltersByAppId(appId.currentValue);
} else if (filter && filter.currentValue !== filter.previousValue) { } else if (filterParam && filterParam.currentValue !== filterParam.previousValue) {
this.selectFilterAndEmit(filter.currentValue); this.selectFilterAndEmit(filterParam.currentValue);
} }
} }
isActiveRoute(filterActive: FilterRepresentationModel): boolean {
return (this.isTaskActive || this.isTaskRoute) && this.currentFilter === filterActive;
}
/** /**
* Return the task list filtered by appId or by appName * Return the task list filtered by appId or by appName
* *
@@ -173,11 +198,11 @@ export class TaskFiltersComponent implements OnInit, OnChanges {
*/ */
public selectFilter(newFilter: FilterParamsModel) { public selectFilter(newFilter: FilterParamsModel) {
if (newFilter) { if (newFilter) {
this.currentFilter = this.filters.find( (filter, index) => this.currentFilter = this.filters.find( (entry, index) =>
newFilter.index === index || newFilter.index === index ||
newFilter.id === filter.id || newFilter.id === entry.id ||
(newFilter.name && (newFilter.name &&
(newFilter.name.toLocaleLowerCase() === filter.name.toLocaleLowerCase()) (newFilter.name.toLocaleLowerCase() === entry.name.toLocaleLowerCase())
)); ));
} }
} }
@@ -190,8 +215,8 @@ export class TaskFiltersComponent implements OnInit, OnChanges {
/** /**
* Selects and emits the clicked filter. * Selects and emits the clicked filter.
*/ */
onFilterClick(filter: FilterParamsModel) { onFilterClick(filterParams: FilterParamsModel) {
this.selectFilter(filter); this.selectFilter(filterParams);
this.filterClicked.emit(this.currentFilter); this.filterClicked.emit(this.currentFilter);
} }
@@ -203,8 +228,8 @@ export class TaskFiltersComponent implements OnInit, OnChanges {
public selectFilterWithTask(taskId: string) { public selectFilterWithTask(taskId: string) {
const filteredFilterList: FilterRepresentationModel[] = []; const filteredFilterList: FilterRepresentationModel[] = [];
this.taskListService.getFilterForTaskById(taskId, this.filters).subscribe( this.taskListService.getFilterForTaskById(taskId, this.filters).subscribe(
(filter: FilterRepresentationModel) => { (filterModel: FilterRepresentationModel) => {
filteredFilterList.push(filter); filteredFilterList.push(filterModel);
}, },
(err) => { (err) => {
this.error.emit(err); this.error.emit(err);
@@ -254,4 +279,9 @@ export class TaskFiltersComponent implements OnInit, OnChanges {
this.filters = []; this.filters = [];
this.currentFilter = undefined; this.currentFilter = undefined;
} }
ngOnDestroy() {
this.onDestroy$.next(true);
this.onDestroy$.complete();
}
} }