mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-26 12:15:20 +01:00
Fix filter bug for replication tasks page
Signed-off-by: AllForNothing <sshijun@vmware.com>
This commit is contained in:
parent
9bc6f3cee4
commit
b749ba4e54
@ -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(() => {
|
||||
|
@ -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<ReplicationJob> {
|
||||
export class ReplicationTasksRoutingResolverService implements Resolve<ReplicationExecution> {
|
||||
|
||||
constructor(
|
||||
private replicationService: ReplicationService,
|
||||
private router: Router) { }
|
||||
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<ReplicationJob> | any {
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<ReplicationExecution> | 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']);
|
||||
}
|
||||
|
@ -14,15 +14,15 @@
|
||||
<h2 class="custom-h2 h2-style">{{executionId}}</h2>
|
||||
</div>
|
||||
<div>
|
||||
<div class="status-progress" *ngIf="executions && executions['status'] === 'InProgress'">
|
||||
<div class="status-progress" *ngIf="execution && execution['status'] === 'InProgress'">
|
||||
<span class="spinner spinner-inline"></span>
|
||||
<span>{{'REPLICATION.IN_PROGRESS'| translate}}</span>
|
||||
</div>
|
||||
<div class="status-success" *ngIf="executions && executions['status'] === 'Succeed'">
|
||||
<div class="status-success" *ngIf="execution && execution['status'] === 'Succeed'">
|
||||
<clr-icon size="18" shape="success-standard" class="color-green"></clr-icon>
|
||||
<span>{{'REPLICATION.SUCCESS'| translate}}</span>
|
||||
</div>
|
||||
<div class="status-failed" *ngIf="executions && executions['status'] === 'Failed'">
|
||||
<div class="status-failed" *ngIf="execution && execution['status'] === 'Failed'">
|
||||
<clr-icon size="18" shape="error-standard" class="color-red"></clr-icon>
|
||||
<span>{{'REPLICATION.FAILURE'| translate}}</span>
|
||||
</div>
|
||||
@ -75,12 +75,12 @@
|
||||
</section>
|
||||
|
||||
<div class="tasks-detail">
|
||||
<h3 class="modal-title">Tasks</h3>
|
||||
<h3 class="modal-title">{{'P2P_PROVIDER.TASKS' | translate}}</h3>
|
||||
<div class="row flex-items-xs-between flex-items-xs-bottom">
|
||||
<div class="action-select">
|
||||
<div class="select filter-tag" [hidden]="!isOpenFilterTag">
|
||||
<select (change)="selectFilter($event)">
|
||||
<option value="resource_type">{{'REPLICATION.RESOURCE_TYPE' |translate}}</option>
|
||||
<option value="resourceType">{{'REPLICATION.RESOURCE_TYPE' |translate}}</option>
|
||||
<option value="status">{{'REPLICATION.STATUS' | translate}}</option>
|
||||
</select>
|
||||
</div>
|
||||
@ -93,15 +93,16 @@
|
||||
</div>
|
||||
</div>
|
||||
<clr-datagrid (clrDgRefresh)="clrLoadTasks($event)" [clrDgLoading]="loading">
|
||||
<clr-dg-column [clrDgSortBy]="'id'">{{'REPLICATION.TASK_ID'| translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'resource_type'" class="resource-width">{{'REPLICATION.RESOURCE_TYPE' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPLICATION.TASK_ID'| translate}}</clr-dg-column>
|
||||
<clr-dg-column class="resource-width">{{'REPLICATION.RESOURCE_TYPE' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'src_resource'">{{'REPLICATION.SOURCE' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'dst_resource'">{{'REPLICATION.DESTINATION' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'operation'">{{'REPLICATION.OPERATION' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'status'">{{'REPLICATION.STATUS' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPLICATION.STATUS' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgSortBy]="startTimeComparator">{{'REPLICATION.CREATION_TIME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgSortBy]="endTimeComparator">{{'REPLICATION.END_TIME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPLICATION.LOGS' | translate}}</clr-dg-column>
|
||||
<clr-dg-placeholder>{{'P2P_PROVIDER.TASKS_PLACEHOLDER' | translate }}</clr-dg-placeholder>
|
||||
<clr-dg-row *ngFor="let t of tasks">
|
||||
<clr-dg-cell>{{t.id}}</clr-dg-cell>
|
||||
<clr-dg-cell class="resource-width">{{t.resource_type}}</clr-dg-cell>
|
||||
|
@ -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<ReplicationTasksComponent>;
|
||||
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);
|
||||
});
|
||||
});
|
@ -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<ReplicationJob> = new CustomComparator<
|
||||
startTimeComparator: ClrDatagridComparatorInterface<ReplicationTask> = new CustomComparator<
|
||||
ReplicationJob
|
||||
>("start_time", "date");
|
||||
endTimeComparator: Comparator<ReplicationJob> = new CustomComparator<
|
||||
endTimeComparator: ClrDatagridComparatorInterface<ReplicationTask> = 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 = <ReplicationJob>(resolverData["replicationTasksRoutingResolver"]);
|
||||
this.executions = replicationJob.data;
|
||||
this.execution = <ReplicationExecution>(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<ReplicationTask>(this.tasks, state);
|
||||
this.tasks = doSorting<ReplicationTask>(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);
|
||||
|
Loading…
Reference in New Issue
Block a user