From b749ba4e5461f3ec214823080c7677f6e6cb13cb Mon Sep 17 00:00:00 2001 From: AllForNothing Date: Mon, 21 Dec 2020 14:02:20 +0800 Subject: [PATCH] Fix filter bug for replication tasks page Signed-off-by: AllForNothing --- ...ion-tasks-routing-resolver.service.spec.ts | 2 +- ...lication-tasks-routing-resolver.service.ts | 11 +- .../replication-tasks.component.html | 17 +-- .../replication-tasks.component.spec.ts | 120 ++++++++++++++++++ .../replication-tasks.component.ts | 87 +++++++------ 5 files changed, 185 insertions(+), 52 deletions(-) create mode 100644 src/portal/src/lib/components/replication/replication-tasks/replication-tasks.component.spec.ts diff --git a/src/portal/src/app/services/routing-resolvers/replication-tasks-routing-resolver.service.spec.ts b/src/portal/src/app/services/routing-resolvers/replication-tasks-routing-resolver.service.spec.ts index c296b45d4..9f93e7385 100644 --- a/src/portal/src/app/services/routing-resolvers/replication-tasks-routing-resolver.service.spec.ts +++ b/src/portal/src/app/services/routing-resolvers/replication-tasks-routing-resolver.service.spec.ts @@ -1,7 +1,7 @@ import { TestBed, inject } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; -import { ReplicationService } from "../../../lib/services"; import { ReplicationTasksRoutingResolverService } from "./replication-tasks-routing-resolver.service"; +import { ReplicationService } from "../../../../ng-swagger-gen/services"; describe('ReplicationTasksRoutingResolverService', () => { beforeEach(() => { diff --git a/src/portal/src/app/services/routing-resolvers/replication-tasks-routing-resolver.service.ts b/src/portal/src/app/services/routing-resolvers/replication-tasks-routing-resolver.service.ts index 0354faef7..35373ff4f 100644 --- a/src/portal/src/app/services/routing-resolvers/replication-tasks-routing-resolver.service.ts +++ b/src/portal/src/app/services/routing-resolvers/replication-tasks-routing-resolver.service.ts @@ -15,25 +15,26 @@ import { Injectable } from '@angular/core'; import { Router, Resolve, RouterStateSnapshot, ActivatedRouteSnapshot } from '@angular/router'; import { Observable } from 'rxjs'; import { map, catchError } from "rxjs/operators"; -import { ReplicationJob, ReplicationService } from "../../../lib/services"; +import { ReplicationService } from "../../../../ng-swagger-gen/services"; +import { ReplicationExecution } from "../../../../ng-swagger-gen/models/replication-execution"; @Injectable({ providedIn: 'root' }) -export class ReplicationTasksRoutingResolverService implements Resolve { +export class ReplicationTasksRoutingResolverService implements Resolve { constructor( private replicationService: ReplicationService, private router: Router) { } - resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable | any { + resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable | any { // Support both parameters and query parameters let executionId = route.params['id']; if (!executionId) { executionId = route.queryParams['project_id']; } - return this.replicationService.getExecutionById(executionId) - .pipe(map((res: ReplicationJob) => { + return this.replicationService.getReplicationExecution(+executionId) + .pipe(map((res: ReplicationExecution) => { if (!res) { this.router.navigate(['/harbor', 'projects']); } diff --git a/src/portal/src/lib/components/replication/replication-tasks/replication-tasks.component.html b/src/portal/src/lib/components/replication/replication-tasks/replication-tasks.component.html index ca09baa48..cbd4ff2d7 100644 --- a/src/portal/src/lib/components/replication/replication-tasks/replication-tasks.component.html +++ b/src/portal/src/lib/components/replication/replication-tasks/replication-tasks.component.html @@ -14,15 +14,15 @@

{{executionId}}

-
+
{{'REPLICATION.IN_PROGRESS'| translate}}
-
+
{{'REPLICATION.SUCCESS'| translate}}
-
+
{{'REPLICATION.FAILURE'| translate}}
@@ -75,12 +75,12 @@
- +
@@ -93,15 +93,16 @@
- {{'REPLICATION.TASK_ID'| translate}} - {{'REPLICATION.RESOURCE_TYPE' | translate}} + {{'REPLICATION.TASK_ID'| translate}} + {{'REPLICATION.RESOURCE_TYPE' | translate}} {{'REPLICATION.SOURCE' | translate}} {{'REPLICATION.DESTINATION' | translate}} {{'REPLICATION.OPERATION' | translate}} - {{'REPLICATION.STATUS' | translate}} + {{'REPLICATION.STATUS' | translate}} {{'REPLICATION.CREATION_TIME' | translate}} {{'REPLICATION.END_TIME' | translate}} {{'REPLICATION.LOGS' | translate}} + {{'P2P_PROVIDER.TASKS_PLACEHOLDER' | translate }} {{t.id}} {{t.resource_type}} diff --git a/src/portal/src/lib/components/replication/replication-tasks/replication-tasks.component.spec.ts b/src/portal/src/lib/components/replication/replication-tasks/replication-tasks.component.spec.ts new file mode 100644 index 000000000..df63e3671 --- /dev/null +++ b/src/portal/src/lib/components/replication/replication-tasks/replication-tasks.component.spec.ts @@ -0,0 +1,120 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core'; +import { NoopAnimationsModule } from "@angular/platform-browser/animations"; +import { ReplicationExecution } from "../../../../../ng-swagger-gen/models/replication-execution"; +import { ReplicationTasksComponent } from "./replication-tasks.component"; +import { ActivatedRoute } from "@angular/router"; +import { ReplicationService } from "../../../../../ng-swagger-gen/services"; +import { ErrorHandler } from "../../../utils/error-handler"; +import { RouterTestingModule } from "@angular/router/testing"; +import { TranslateModule } from "@ngx-translate/core"; +import { of, Subscription } from "rxjs"; +import { delay } from "rxjs/operators"; +import { HttpHeaders, HttpResponse } from "@angular/common/http"; +import { ClarityModule, ClrDatagridStateInterface } from "@clr/angular"; +import { ReplicationTask } from "../../../../../ng-swagger-gen/models/replication-task"; + + +describe('ReplicationTasksComponent', () => { + const mockJob: ReplicationExecution = { + id: 1, + status: "Failed", + policy_id: 1, + trigger: "Manual", + total: 0, + failed: 1, + succeed: 0, + in_progress: 0, + stopped: 0 + }; + const mockTask: ReplicationTask = { + "dst_resource": "library/lightstreamer [1 item(s) in total]", + "end_time": "2020-12-21T05:56:04.000Z", + "execution_id": 15, + "id": 30, + "job_id": "8f45cd0c512ba3d8f23ee3fa", + "operation": "copy", + "resource_type": "image", + "src_resource": "library/lightstreamer [1 item(s) in total]", + "start_time": "2020-12-21T05:56:03.000Z", + "status": "Failed" + }; + let fixture: ComponentFixture; + let comp: ReplicationTasksComponent; + const fakedErrorHandler = { + error() { + } + }; + const fakedActivatedRoute = { + snapshot: { + params: { + id: 1 + }, + data: { + replicationTasksRoutingResolver: mockJob + } + } + }; + const fakedReplicationService = { + listReplicationTasksResponse() { + return of(new HttpResponse({ + body: [mockTask], + headers: new HttpHeaders({ + "x-total-count": "1" + }) + })).pipe(delay(0)); + }, + getReplicationExecution() { + return of(mockJob).pipe(delay(0)); + } + }; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + schemas: [CUSTOM_ELEMENTS_SCHEMA, + NO_ERRORS_SCHEMA], + imports: [ + NoopAnimationsModule, + RouterTestingModule, + ClarityModule, + TranslateModule.forRoot() + ], + declarations: [ + ReplicationTasksComponent, + ], + providers: [ + {provide: ErrorHandler, useValue: fakedErrorHandler}, + {provide: ReplicationService, useValue: fakedReplicationService}, + {provide: ActivatedRoute, useValue: fakedActivatedRoute} + ] + }); + })); + beforeEach(() => { + fixture = TestBed.createComponent(ReplicationTasksComponent); + comp = fixture.componentInstance; + comp.timerDelay = new Subscription(); + comp.getExecutionDetail(); + fixture.detectChanges(); + }); + afterEach(() => { + if (comp.timerDelay) { + comp.timerDelay.unsubscribe(); + comp.timerDelay = null; + } + }); + it('should be created', () => { + expect(comp).toBeTruthy(); + }); + it('job status should be failed', async () => { + fixture.detectChanges(); + await fixture.whenStable(); + const span: HTMLSpanElement = fixture.nativeElement.querySelector('.status-failed>span'); + expect(span.innerText).toEqual('REPLICATION.FAILURE'); + }); + it('should render task list', async () => { + fixture.autoDetectChanges(); + await fixture.whenStable(); + const row = fixture.nativeElement.querySelectorAll('clr-dg-row'); + expect(row.length).toEqual(1); + }); +}); diff --git a/src/portal/src/lib/components/replication/replication-tasks/replication-tasks.component.ts b/src/portal/src/lib/components/replication/replication-tasks/replication-tasks.component.ts index 56cc66cce..c6263cfaf 100644 --- a/src/portal/src/lib/components/replication/replication-tasks/replication-tasks.component.ts +++ b/src/portal/src/lib/components/replication/replication-tasks/replication-tasks.component.ts @@ -1,20 +1,23 @@ import { Component, OnInit, OnDestroy } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { ReplicationService } from "../../../services/replication.service"; import { TranslateService } from '@ngx-translate/core'; import { finalize } from "rxjs/operators"; import { Subscription, timer } from "rxjs"; -import { ErrorHandler } from "../../../utils/error-handler/error-handler"; -import { ReplicationJob, ReplicationTasks, Comparator, ReplicationJobItem, State } from "../../../services/interface"; -import { CustomComparator, DEFAULT_PAGE_SIZE } from "../../../utils/utils"; -import { RequestQueryParams } from "../../../services/RequestQueryParams"; +import { ErrorHandler } from "../../../utils/error-handler"; +import { ClrDatagridComparatorInterface, ReplicationJob, ReplicationTasks } from "../../../services"; +import { CURRENT_BASE_HREF, CustomComparator, DEFAULT_PAGE_SIZE, doFiltering, doSorting } from "../../../utils/utils"; import { REFRESH_TIME_DIFFERENCE } from '../../../entities/shared.const'; import { ClrDatagridStateInterface } from '@clr/angular'; +import { ReplicationExecution } from "../../../../../ng-swagger-gen/models/replication-execution"; +import { ReplicationService } from "../../../../../ng-swagger-gen/services"; +import ListReplicationTasksParams = ReplicationService.ListReplicationTasksParams; +import { ReplicationTask } from "../../../../../ng-swagger-gen/models/replication-task"; const executionStatus = 'InProgress'; const STATUS_MAP = { "Succeed": "Succeeded" }; +const SUCCEED: string = 'Succeed'; @Component({ selector: 'replication-tasks', templateUrl: './replication-tasks.component.html', @@ -28,18 +31,18 @@ export class ReplicationTasksComponent implements OnInit, OnDestroy { totalCount: number; loading = true; searchTask: string; - defaultFilter = "resource_type"; - tasks: ReplicationTasks[]; + defaultFilter = "resourceType"; + tasks: ReplicationTask[]; taskItem: ReplicationTasks[] = []; tasksCopy: ReplicationTasks[] = []; stopOnGoing: boolean; - executions: ReplicationJobItem[]; + execution: ReplicationExecution; timerDelay: Subscription; executionId: string; - startTimeComparator: Comparator = new CustomComparator< + startTimeComparator: ClrDatagridComparatorInterface = new CustomComparator< ReplicationJob >("start_time", "date"); - endTimeComparator: Comparator = new CustomComparator< + endTimeComparator: ClrDatagridComparatorInterface = new CustomComparator< ReplicationJob >("end_time", "date"); @@ -56,18 +59,17 @@ export class ReplicationTasksComponent implements OnInit, OnDestroy { this.executionId = this.route.snapshot.params['id']; const resolverData = this.route.snapshot.data; if (resolverData) { - const replicationJob = (resolverData["replicationTasksRoutingResolver"]); - this.executions = replicationJob.data; + this.execution = (resolverData["replicationTasksRoutingResolver"]); this.clrLoadPage(); } } getExecutionDetail(): void { this.inProgress = true; if (this.executionId) { - this.replicationService.getExecutionById(this.executionId) + this.replicationService.getReplicationExecution(+this.executionId) .pipe(finalize(() => (this.inProgress = false))) .subscribe(res => { - this.executions = res.data; + this.execution = res; this.clrLoadPage(); }, error => { @@ -80,12 +82,12 @@ export class ReplicationTasksComponent implements OnInit, OnDestroy { if (!this.timerDelay) { this.timerDelay = timer(REFRESH_TIME_DIFFERENCE, REFRESH_TIME_DIFFERENCE).subscribe(() => { let count: number = 0; - if (this.executions['status'] === executionStatus) { + if (this.execution['status'] === executionStatus) { count++; } if (count > 0) { this.getExecutionDetail(); - let state: State = { + let state: ClrDatagridStateInterface = { page: {} }; this.clrLoadTasks(state); @@ -98,36 +100,36 @@ export class ReplicationTasksComponent implements OnInit, OnDestroy { } public get trigger(): string { - return this.executions && this.executions['trigger'] - ? this.executions['trigger'] + return this.execution && this.execution['trigger'] + ? this.execution['trigger'] : ""; } - public get startTime(): Date { - return this.executions && this.executions['start_time'] - ? this.executions['start_time'] + public get startTime(): string { + return this.execution && this.execution['start_time'] + ? this.execution['start_time'] : null; } - public get successNum(): string { - return this.executions && this.executions['succeed']; + public get successNum(): number { + return this.execution && this.execution['succeed']; } - public get failedNum(): string { - return this.executions && this.executions['failed']; + public get failedNum(): number { + return this.execution && this.execution['failed']; } - public get progressNum(): string { - return this.executions && this.executions['in_progress']; + public get progressNum(): number { + return this.execution && this.execution['in_progress']; } - public get stoppedNum(): string { - return this.executions && this.executions['stopped']; + public get stoppedNum(): number { + return this.execution && this.execution['stopped']; } stopJob() { this.stopOnGoing = true; - this.replicationService.stopJobs(this.executionId) + this.replicationService.stopReplication(+this.executionId) .subscribe(response => { this.stopOnGoing = false; this.getExecutionDetail(); @@ -141,7 +143,7 @@ export class ReplicationTasksComponent implements OnInit, OnDestroy { } viewLog(taskId: number | string): string { - return this.replicationService.getJobBaseUrl() + "/executions/" + this.executionId + "/tasks/" + taskId + "/log"; + return CURRENT_BASE_HREF + "/replication" + "/executions/" + this.executionId + "/tasks/" + taskId + "/log"; } ngOnDestroy() { @@ -157,14 +159,20 @@ export class ReplicationTasksComponent implements OnInit, OnDestroy { if (state && state.page && state.page.size) { this.pageSize = state.page.size; } - let params: RequestQueryParams = new RequestQueryParams(); - params = params.set('page_size', this.pageSize + '').set('page', this.currentPage + ''); + const param: ListReplicationTasksParams = { + id: +this.executionId, + page: this.currentPage, + pageSize: this.pageSize, + }; if (this.searchTask && this.searchTask !== "") { - params = params.set(this.defaultFilter, this.searchTask); + if (this.searchTask === STATUS_MAP.Succeed && this.defaultFilter === 'status') {// convert 'Succeeded' to 'Succeed' + param[this.defaultFilter] = SUCCEED; + } else { + param[this.defaultFilter] = this.searchTask; + } } - this.loading = true; - this.replicationService.getReplicationTasks(this.executionId, params) + this.replicationService.listReplicationTasksResponse(param) .pipe(finalize(() => { this.loading = false; })) @@ -176,6 +184,9 @@ export class ReplicationTasksComponent implements OnInit, OnDestroy { } } this.tasks = res.body; // Keep the data + // Do customising filtering and sorting + this.tasks = doFiltering(this.tasks, state); + this.tasks = doSorting(this.tasks, state); }, error => { this.errorHandler.error(error); @@ -193,7 +204,7 @@ export class ReplicationTasksComponent implements OnInit, OnDestroy { // refresh icon refreshTasks(): void { this.currentPage = 1; - let state: State = { + let state: ClrDatagridStateInterface = { page: {} }; this.clrLoadTasks(state); @@ -202,7 +213,7 @@ export class ReplicationTasksComponent implements OnInit, OnDestroy { public doSearch(value: string): void { this.currentPage = 1; this.searchTask = value.trim(); - let state: State = { + let state: ClrDatagridStateInterface = { page: {} }; this.clrLoadTasks(state);