Refactor gc and gi history page (#14728)

Signed-off-by: AllForNothing <sshijun@vmware.com>
This commit is contained in:
Will Sun 2021-04-26 10:25:36 +08:00 committed by GitHub
parent 2ffa6580fa
commit 705cb5b55d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 323 additions and 489 deletions

View File

@ -3,10 +3,7 @@ import { RouterModule, Routes } from "@angular/router";
import { GcPageComponent } from "./gc-page.component";
import { GcComponent } from "./gc/gc.component";
import { GcHistoryComponent } from "./gc/gc-history/gc-history.component";
import { GcRepoService } from "./gc/gc.service";
import { SharedModule } from "../../../shared/shared.module";
import { GcApiDefaultRepository, GcApiRepository } from "./gc/gc.api.repository";
import { GcViewModelFactory } from "./gc/gc.viewmodel.factory";
const routes: Routes = [
{
@ -24,10 +21,5 @@ const routes: Routes = [
GcComponent,
GcHistoryComponent
],
providers: [
GcRepoService,
{provide: GcApiRepository, useClass: GcApiDefaultRepository },
GcViewModelFactory
]
})
export class GcModule {}

View File

@ -1,30 +1,35 @@
<h5 class="history-header" id="history-header">{{'GC.JOB_HISTORY' | translate}}</h5>
<span class="refresh-btn" (click)="getJobs()">
<span class="refresh-btn" (click)="refresh()">
<clr-icon shape="refresh"></clr-icon>
</span>
<clr-datagrid [clrDgLoading]="loading">
<clr-dg-column>{{'GC.JOB_ID' | translate}}</clr-dg-column>
<clr-dg-column>{{'GC.TRIGGER_TYPE' | translate}}</clr-dg-column>
<clr-dg-column>{{'TAG_RETENTION.DRY_RUN' | translate}}</clr-dg-column>
<clr-dg-column>{{'STATUS' | translate}}</clr-dg-column>
<clr-dg-column>{{'CREATION_TIME' | translate}}</clr-dg-column>
<clr-dg-column>{{'UPDATE_TIME' | translate}}</clr-dg-column>
<clr-dg-column>{{'LOGS' | translate}}</clr-dg-column>
<clr-dg-row *clrDgItems="let job of jobs" [clrDgItem]='job'>
<clr-dg-cell>{{job.id }}</clr-dg-cell>
<clr-dg-cell>{{(job.type ? 'SCHEDULE.'+ job.type.toUpperCase() : '') | translate }}</clr-dg-cell>
<clr-dg-cell>{{isDryRun(job?.parameters) | translate}}</clr-dg-cell>
<clr-dg-cell>{{job.status.toUpperCase() | translate}}</clr-dg-cell>
<clr-dg-cell>{{job.createTime | harborDatetime:'medium'}}</clr-dg-cell>
<clr-dg-cell>{{job.updateTime | harborDatetime:'medium'}}</clr-dg-cell>
<clr-dg-cell>
<a *ngIf="job.status.toLowerCase() === 'success' || job.status.toLowerCase() === 'error'" target="_blank" [href]="getLogLink(job.id)"><clr-icon shape="list"></clr-icon></a>
</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>
<clr-dg-pagination [clrDgPageSize]="15">
<clr-dg-page-size [clrPageSizeOptions]="[15,25,50]">{{"PAGINATION.PAGE_SIZE" | translate}}</clr-dg-page-size>
{{'GC.LATEST_JOBS' | translate :{param: jobs.length} }}
</clr-dg-pagination>
</clr-dg-footer>
<clr-datagrid [clrDgLoading]="loading" (clrDgRefresh)="getJobs($event)">
<clr-dg-column [clrDgField]="'id'">{{'GC.JOB_ID' | translate}}</clr-dg-column>
<clr-dg-column>{{'GC.TRIGGER_TYPE' | translate}}</clr-dg-column>
<clr-dg-column>{{'TAG_RETENTION.DRY_RUN' | translate}}</clr-dg-column>
<clr-dg-column>{{'STATUS' | translate}}</clr-dg-column>
<clr-dg-column>{{'CREATION_TIME' | translate}}</clr-dg-column>
<clr-dg-column [clrDgSortBy]="'update_time'">{{'UPDATE_TIME' | translate}}</clr-dg-column>
<clr-dg-column>{{'LOGS' | translate}}</clr-dg-column>
<clr-dg-row *ngFor="let job of jobs" [clrDgItem]='job'>
<clr-dg-cell>{{job.id }}</clr-dg-cell>
<clr-dg-cell>{{(job.schedule?.type ? 'SCHEDULE.' + job.schedule?.type.toUpperCase() : '') | translate }}</clr-dg-cell>
<clr-dg-cell>{{isDryRun(job?.job_parameters) | translate}}</clr-dg-cell>
<clr-dg-cell>{{job.job_status.toUpperCase() | translate}}</clr-dg-cell>
<clr-dg-cell>{{job.creation_time | harborDatetime:'medium'}}</clr-dg-cell>
<clr-dg-cell>{{job.update_time | harborDatetime:'medium'}}</clr-dg-cell>
<clr-dg-cell>
<a *ngIf="job.job_status.toLowerCase() === 'success' || job.job_status.toLowerCase() === 'error'" target="_blank"
[href]="getLogLink(job.id)">
<clr-icon shape="list"></clr-icon>
</a>
</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>
<clr-dg-pagination #pagination [clrDgPageSize]="pageSize" [(clrDgPage)]="page" [clrDgTotalItems]="total">
<clr-dg-page-size [clrPageSizeOptions]="[15,25,50]">{{"PAGINATION.PAGE_SIZE" | translate}}</clr-dg-page-size>
<span *ngIf="total">{{pagination.firstItem + 1}}
- {{pagination.lastItem + 1}} {{'DESTINATION.OF' | translate}}</span>
{{total}} {{'DESTINATION.ITEMS' | translate}}
</clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>

View File

@ -1,100 +1,99 @@
import {
ComponentFixture,
ComponentFixtureAutoDetect,
TestBed,
waitForAsync
ComponentFixture,
ComponentFixtureAutoDetect, fakeAsync,
TestBed, tick,
} from '@angular/core/testing';
import { GcRepoService } from "../gc.service";
import { of } from 'rxjs';
import { GcViewModelFactory } from "../gc.viewmodel.factory";
import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { ErrorHandler } from '../../../../../shared/units/error-handler';
import { GcHistoryComponent } from './gc-history.component';
import { GcJobData } from "../gcLog";
import { SharedTestingModule } from "../../../../../shared/shared.module";
import { GCHistory } from "../../../../../../../ng-swagger-gen/models/gchistory";
import { HttpHeaders, HttpResponse } from "@angular/common/http";
import { Registry } from "../../../../../../../ng-swagger-gen/models/registry";
import { GcService } from "../../../../../../../ng-swagger-gen/services/gc.service";
import { CURRENT_BASE_HREF } from "../../../../../shared/units/utils";
import { delay } from "rxjs/operators";
describe('GcHistoryComponent', () => {
let component: GcHistoryComponent;
let fixture: ComponentFixture<GcHistoryComponent>;
const mockJobs: GcJobData[] = [
{
id: 1,
job_name: 'test',
job_kind: 'manual',
schedule: null,
job_status: 'pending',
job_parameters: '{"dry_run":true}',
job_uuid: 'abc',
creation_time: null,
update_time: null,
delete: false
},
{
id: 2,
job_name: 'test',
job_kind: 'manual',
schedule: null,
job_status: 'finished',
job_parameters: '{"dry_run":true}',
job_uuid: 'bcd',
creation_time: null,
update_time: null,
delete: false
}
];
let fakeGcRepoService = {
count: 0,
getJobs() {
if (this.count === 0) {
this.count += 1;
return of([mockJobs[0]]);
} else {
this.count += 1;
return of([mockJobs[1]]);
}
},
getLogLink() {
return null;
}
};
const fakeGcViewModelFactory = new GcViewModelFactory();
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [GcHistoryComponent],
imports: [
SharedTestingModule,
TranslateModule.forRoot()
],
providers: [
ErrorHandler,
TranslateService,
GcViewModelFactory,
{ provide: GcRepoService, useValue: fakeGcRepoService },
{ provide: GcViewModelFactory, useValue: fakeGcViewModelFactory },
// open auto detect
{ provide: ComponentFixtureAutoDetect, useValue: true }
]
let component: GcHistoryComponent;
let fixture: ComponentFixture<GcHistoryComponent>;
const mockJobs: GCHistory[] = [
{
id: 1,
job_name: 'test',
job_kind: 'manual',
schedule: null,
job_status: 'pending',
job_parameters: '{"dry_run":true}',
creation_time: null,
update_time: null,
},
{
id: 2,
job_name: 'test',
job_kind: 'manual',
schedule: null,
job_status: 'finished',
job_parameters: '{"dry_run":true}',
creation_time: null,
update_time: null,
}
];
const fakedGcService = {
count: 0,
getGCHistoryResponse() {
if (this.count === 0) {
this.count += 1;
const response: HttpResponse<Array<Registry>> = new HttpResponse<Array<Registry>>({
headers: new HttpHeaders({'x-total-count': [mockJobs[0]].length.toString()}),
body: [mockJobs[0]]
});
return of(response).pipe(delay(0));
} else {
this.count += 1;
const response: HttpResponse<Array<Registry>> = new HttpResponse<Array<Registry>>({
headers: new HttpHeaders({'x-total-count': [mockJobs[1]].length.toString()}),
body: [mockJobs[1]]
});
return of(response).pipe(delay(0));
}
}
};
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [GcHistoryComponent],
imports: [
SharedTestingModule,
],
providers: [
{provide: GcService, useValue: fakedGcService},
// open auto detect
{provide: ComponentFixtureAutoDetect, useValue: true}
]
});
});
beforeEach(() => {
fixture = TestBed.createComponent(GcHistoryComponent);
component = fixture.componentInstance;
fixture.detectChanges();
beforeEach(() => {
fixture = TestBed.createComponent(GcHistoryComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
afterEach(() => {
if (component && component.timerDelay) {
component.timerDelay.unsubscribe();
component.timerDelay = null;
}
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should retry getting jobs', fakeAsync(() => {
tick(10000);
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(component.jobs[0].job_status).toEqual('finished');
});
afterEach(() => {
if (component && component.timerDelay) {
component.timerDelay.unsubscribe();
component.timerDelay = null;
}
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should retry getting jobs', waitForAsync(() => {
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(component.jobs[1].status).toEqual('finished');
});
}));
}));
it('should return right log link', () => {
expect(component.getLogLink('1')).toEqual(`${CURRENT_BASE_HREF}/system/gc/1/log`);
});
});

View File

@ -1,64 +1,103 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { GcRepoService } from "../gc.service";
import { GcJobViewModel } from "../gcLog";
import { GcViewModelFactory } from "../gc.viewmodel.factory";
import { ErrorHandler } from "../../../../../shared/units/error-handler";
import { Subscription, timer } from "rxjs";
import { REFRESH_TIME_DIFFERENCE } from '../../../../../shared/entities/shared.const';
import { GcService } from "../../../../../../../ng-swagger-gen/services/gc.service";
import { CURRENT_BASE_HREF, DEFAULT_PAGE_SIZE, getSortingString } from "../../../../../shared/units/utils";
import { ClrDatagridStateInterface } from "@clr/angular";
import { finalize } from "rxjs/operators";
import { GCHistory } from "../../../../../../../ng-swagger-gen/models/gchistory";
const JOB_STATUS = {
PENDING: "pending",
RUNNING: "running"
};
const YES: string = 'TAG_RETENTION.YES';
const NO: string = 'TAG_RETENTION.NO';
@Component({
selector: 'gc-history',
templateUrl: './gc-history.component.html',
styleUrls: ['./gc-history.component.scss']
})
export class GcHistoryComponent implements OnInit, OnDestroy {
jobs: Array<GcJobViewModel> = [];
loading: boolean;
jobs: Array<GCHistory> = [];
loading: boolean = true;
timerDelay: Subscription;
pageSize: number = DEFAULT_PAGE_SIZE;
page: number = 1;
total: number = 0;
state: ClrDatagridStateInterface;
constructor(
private gcRepoService: GcRepoService,
private gcViewModelFactory: GcViewModelFactory,
private gcService: GcService,
private errorHandler: ErrorHandler
) {}
) {
}
ngOnInit() {
}
refresh() {
this.page = 1;
this.total = 0;
this.getJobs();
}
getJobs() {
getJobs(state?: ClrDatagridStateInterface) {
if (state) {
this.state = state;
}
if (state && state.page) {
this.pageSize = state.page.size;
}
let q: string;
if (state && state.filters && state.filters.length) {
q = encodeURIComponent(`${state.filters[0].property}=~${state.filters[0].value}`);
}
let sort: string;
if (state && state.sort && state.sort.by) {
sort = getSortingString(state);
}
this.loading = true;
this.gcRepoService.getJobs().subscribe(jobs => {
this.jobs = this.gcViewModelFactory.createJobViewModel(jobs);
this.loading = false;
// to avoid some jobs not finished.
if (!this.timerDelay) {
this.timerDelay = timer(REFRESH_TIME_DIFFERENCE, REFRESH_TIME_DIFFERENCE).subscribe(() => {
let count: number = 0;
this.jobs.forEach(job => {
if (
job['status'] === JOB_STATUS.PENDING ||
job['status'] === JOB_STATUS.RUNNING
) {
count++;
this.gcService.getGCHistoryResponse({
page: this.page,
pageSize: this.pageSize,
q: q,
sort: sort
}).pipe(finalize(() => this.loading = false))
.subscribe(res => {
// Get total count
if (res.headers) {
const xHeader: string = res.headers.get("X-Total-Count");
if (xHeader) {
this.total = parseInt(xHeader, 0);
}
this.jobs = res.body;
}
// to avoid some jobs not finished.
if (!this.timerDelay) {
this.timerDelay = timer(REFRESH_TIME_DIFFERENCE, REFRESH_TIME_DIFFERENCE).subscribe(() => {
let count: number = 0;
this.jobs.forEach(job => {
if (
job.job_status === JOB_STATUS.PENDING ||
job.job_status === JOB_STATUS.RUNNING
) {
count++;
}
});
if (count > 0) {
this.getJobs(this.state);
} else {
this.timerDelay.unsubscribe();
this.timerDelay = null;
}
});
if (count > 0) {
this.getJobs();
} else {
this.timerDelay.unsubscribe();
this.timerDelay = null;
}
});
}
}, error => {
}
}, error => {
this.errorHandler.error(error);
this.loading = false;
});
});
}
isDryRun(param: string): string {
@ -78,7 +117,7 @@ export class GcHistoryComponent implements OnInit, OnDestroy {
}
getLogLink(id): string {
return this.gcRepoService.getLogLink(id);
return `${CURRENT_BASE_HREF}/system/gc/${id}/log`;
}
}

View File

@ -1,65 +0,0 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { throwError as observableThrowError, Observable } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { CURRENT_BASE_HREF } from "../../../../shared/units/utils";
export abstract class GcApiRepository {
abstract postSchedule(param): Observable<any>;
abstract putSchedule(param): Observable<any>;
abstract getSchedule(): Observable<any>;
abstract getLog(id): Observable<any>;
abstract getStatus(id): Observable<any>;
abstract getJobs(): Observable<any>;
abstract getLogLink(id): string;
}
@Injectable()
export class GcApiDefaultRepository extends GcApiRepository {
constructor(
private http: HttpClient
) {
super();
}
public postSchedule(param): Observable<any> {
return this.http.post(`${CURRENT_BASE_HREF}/system/gc/schedule`, param)
.pipe(catchError(error => observableThrowError(error)));
}
public putSchedule(param): Observable<any> {
return this.http.put(`${CURRENT_BASE_HREF}/system/gc/schedule`, param)
.pipe(catchError(error => observableThrowError(error)));
}
public getSchedule(): Observable<any> {
return this.http.get(`${CURRENT_BASE_HREF}/system/gc/schedule`)
.pipe(catchError(error => observableThrowError(error)));
}
public getLog(id): Observable<any> {
return this.http.get(`${CURRENT_BASE_HREF}/system/gc/${id}/log`)
.pipe(catchError(error => observableThrowError(error)));
}
public getStatus(id): Observable<any> {
return this.http.get(`${CURRENT_BASE_HREF}/system/gc/${id}`)
.pipe(catchError(error => observableThrowError(error)));
}
public getJobs(): Observable<any> {
return this.http.get(`${CURRENT_BASE_HREF}/system/gc`)
.pipe(catchError(error => observableThrowError(error)));
}
public getLogLink(id) {
return `${CURRENT_BASE_HREF}/system/gc/${id}/log`;
}
}

View File

@ -1,6 +1,6 @@
<div class="cron-selection">
<cron-selection [labelCurrent]="getLabelCurrent" #CronScheduleComponent [labelEdit]='getLabelCurrent'
[originCron]='originCron' (inputvalue)="scheduleGc($event)"></cron-selection>
[originCron]='originCron' (inputvalue)="saveGcSchedule($event)"></cron-selection>
</div>
<div class="clr-row">
<div class="clr-col-2 flex-200"></div>
@ -22,11 +22,11 @@
</div>
<div class="clr-row">
<div class="clr-col-2 flex-200">
<button class="btn btn-primary gc-start-btn" (click)="gcNow()"
<button id="gc-now" class="btn btn-primary gc-start-btn" (click)="gcNow()"
[disabled]="disableGC">{{'GC.GC_NOW' | translate}}</button>
</div>
<div class="clr-col">
<button class="btn btn-outline gc-start-btn" (click)="dryRun()"
<button id="gc-dry-run" class="btn btn-outline gc-start-btn" (click)="dryRun()"
[disabled]="dryRunOnGoing">{{'TAG_RETENTION.WHAT_IF_RUN' | translate}}</button>
</div>
</div>

View File

@ -1,35 +1,18 @@
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
import { GcComponent } from './gc.component';
import { GcApiRepository, GcApiDefaultRepository} from './gc.api.repository';
import { GcRepoService } from './gc.service';
import { ErrorHandler } from '../../../../shared/units/error-handler';
import { GcViewModelFactory } from './gc.viewmodel.factory';
import { CronScheduleComponent } from '../../../../shared/components/cron-schedule';
import { CronTooltipComponent } from "../../../../shared/components/cron-schedule";
import { of } from 'rxjs';
import { GcJobData } from './gcLog';
import { CURRENT_BASE_HREF } from "../../../../shared/units/utils";
import { SharedTestingModule } from "../../../../shared/shared.module";
import { GcService } from "../../../../../../ng-swagger-gen/services/gc.service";
import { ScheduleType } from "../../../../shared/entities/shared.const";
describe('GcComponent', () => {
let component: GcComponent;
let fixture: ComponentFixture<GcComponent>;
let gcRepoService: GcRepoService;
let gcRepoService: GcService;
let mockSchedule = [];
let mockJobs: GcJobData[] = [
{
id: 22222,
schedule: null,
job_status: 'string',
job_parameters: '{"dry_run":true}',
creation_time: new Date().toDateString(),
update_time: new Date().toDateString(),
job_name: 'string',
job_kind: 'string',
job_uuid: 'string',
delete: false
}
];
const fakedErrorHandler = {
error(error) {
return error;
@ -39,7 +22,6 @@ describe('GcComponent', () => {
}
};
let spySchedule: jasmine.Spy;
let spyJobs: jasmine.Spy;
let spyGcNow: jasmine.Spy;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
@ -48,10 +30,7 @@ describe('GcComponent', () => {
],
declarations: [ GcComponent, CronScheduleComponent, CronTooltipComponent],
providers: [
{ provide: GcApiRepository, useClass: GcApiDefaultRepository },
{ provide: ErrorHandler, useValue: fakedErrorHandler },
GcRepoService,
GcViewModelFactory
]
})
.compileComponents();
@ -61,10 +40,9 @@ describe('GcComponent', () => {
fixture = TestBed.createComponent(GcComponent);
component = fixture.componentInstance;
gcRepoService = fixture.debugElement.injector.get(GcRepoService);
spySchedule = spyOn(gcRepoService, "getSchedule").and.returnValues(of(mockSchedule));
spyJobs = spyOn(gcRepoService, "getJobs").and.returnValues(of(mockJobs));
spyGcNow = spyOn(gcRepoService, "manualGc").and.returnValues(of(true));
gcRepoService = fixture.debugElement.injector.get(GcService);
spySchedule = spyOn(gcRepoService, "getGCSchedule").and.returnValues(of(mockSchedule));
spyGcNow = spyOn(gcRepoService, "createGCSchedule").and.returnValues(of(true));
fixture.detectChanges();
});
it('should create', () => {
@ -72,12 +50,25 @@ describe('GcComponent', () => {
});
it('should get schedule and job', () => {
expect(spySchedule.calls.count()).toEqual(1);
expect(spyJobs.calls.count()).toEqual(1);
});
it('should trigger gcNow', () => {
const ele: HTMLButtonElement = fixture.nativeElement.querySelector('.gc-start-btn');
const ele: HTMLButtonElement = fixture.nativeElement.querySelector('#gc-now');
ele.click();
fixture.detectChanges();
expect(spyGcNow.calls.count()).toEqual(1);
});
it('should trigger dry run', () => {
const ele: HTMLButtonElement = fixture.nativeElement.querySelector('#gc-dry-run');
ele.click();
fixture.detectChanges();
expect(spyGcNow.calls.count()).toEqual(1);
});
it('getScheduleType function should work', () => {
expect(GcComponent.getScheduleType).toBeTruthy();
expect(GcComponent.getScheduleType(null)).toEqual(ScheduleType.NONE);
expect(GcComponent.getScheduleType('0 0 0 0 0 0')).toEqual(ScheduleType.CUSTOM);
expect(GcComponent.getScheduleType('0 0 * * * *')).toEqual(ScheduleType.HOURLY);
expect(GcComponent.getScheduleType('0 0 0 * * *')).toEqual(ScheduleType.DAILY);
expect(GcComponent.getScheduleType('0 0 0 * * 0')).toEqual(ScheduleType.WEEKLY);
});
});

View File

@ -1,32 +1,26 @@
import {
Component,
Input,
Output,
EventEmitter,
ViewChild,
OnInit
} from "@angular/core";
import { TranslateService } from "@ngx-translate/core";
import { GcJobViewModel } from "./gcLog";
import { GcViewModelFactory } from "./gc.viewmodel.factory";
import { GcRepoService } from "./gc.service";
import {
SCHEDULE_TYPE_NONE,
ONE_MINITUE,
THREE_SECONDS, GCSchedule
} from "./gc.const";
import { ErrorHandler } from "../../../../shared/units/error-handler";
import { CronScheduleComponent } from "../../../../shared/components/cron-schedule/cron-schedule.component";
import { OriginCron } from '../../../../shared/services/interface';
import { CronScheduleComponent } from "../../../../shared/components/cron-schedule";
import { OriginCron } from '../../../../shared/services';
import { finalize } from "rxjs/operators";
import { GcService } from "../../../../../../ng-swagger-gen/services/gc.service";
import { GCHistory } from "../../../../../../ng-swagger-gen/models/gchistory";
import { ScheduleType } from "../../../../shared/entities/shared.const";
const ONE_MINUTE = 60000;
@Component({
selector: "gc-config",
templateUrl: "./gc.component.html",
styleUrls: ["./gc.component.scss"]
})
export class GcComponent implements OnInit {
jobs: Array<GcJobViewModel> = [];
schedule: GCSchedule = {};
originCron: OriginCron;
disableGC: boolean = false;
getLabelCurrent = 'GC.CURRENT_SCHEDULE';
@ -35,67 +29,66 @@ export class GcComponent implements OnInit {
CronScheduleComponent: CronScheduleComponent;
shouldDeleteUntagged: boolean;
dryRunOnGoing: boolean = false;
constructor(
private gcRepoService: GcRepoService,
private gcViewModelFactory: GcViewModelFactory,
private gcService: GcService,
private errorHandler: ErrorHandler,
private translate: TranslateService
) {
translate.setDefaultLang("en-us");
}
ngOnInit() {
this.getCurrentSchedule();
this.getJobs();
}
getCurrentSchedule() {
this.loadingGcStatus.emit(true);
this.gcRepoService.getSchedule()
.pipe(finalize(() => {
this.loadingGcStatus.emit(false);
}))
.subscribe(schedule => {
this.initSchedule(schedule);
}, error => {
this.errorHandler.error(error);
});
this.gcService.getGCSchedule()
.pipe(finalize(() => {
this.loadingGcStatus.emit(false);
}))
.subscribe(schedule => {
this.initSchedule(schedule);
}, error => {
this.errorHandler.error(error);
});
}
public initSchedule(schedule: GCSchedule) {
if (schedule && schedule.schedule !== null) {
this.schedule = schedule;
this.originCron = this.schedule.schedule;
private initSchedule(gcHistory: GCHistory) {
if (gcHistory && gcHistory.schedule) {
this.originCron = {
type: gcHistory.schedule.type,
cron: gcHistory.schedule.cron
};
} else {
this.originCron = {
type: SCHEDULE_TYPE_NONE,
type: ScheduleType.NONE,
cron: ''
};
}
if (schedule && schedule.job_parameters) {
this.shouldDeleteUntagged = JSON.parse(schedule.job_parameters).delete_untagged;
if (gcHistory && gcHistory.job_parameters) {
this.shouldDeleteUntagged = JSON.parse(gcHistory.job_parameters).delete_untagged;
} else {
this.shouldDeleteUntagged = false;
}
}
getJobs() {
this.gcRepoService.getJobs().subscribe(jobs => {
this.jobs = this.gcViewModelFactory.createJobViewModel(jobs);
});
}
gcNow(): void {
this.disableGC = true;
setTimeout(() => {
this.enableGc();
}, ONE_MINITUE);
}, ONE_MINUTE);
this.gcRepoService.manualGc(this.shouldDeleteUntagged, false).subscribe(
this.gcService.createGCSchedule({
parameters: {
delete_untagged: this.shouldDeleteUntagged,
dry_run: false
},
schedule: {
type: ScheduleType.MANUAL
}
}).subscribe(
response => {
this.translate.get("GC.MSG_SUCCESS").subscribe((res: string) => {
this.errorHandler.info(res);
});
this.errorHandler.info("GC.MSG_SUCCESS");
},
error => {
this.errorHandler.error(error);
@ -105,62 +98,66 @@ export class GcComponent implements OnInit {
dryRun() {
this.dryRunOnGoing = true;
this.gcRepoService.manualGc(this.shouldDeleteUntagged, true)
this.gcService.createGCSchedule({
parameters: {
delete_untagged: this.shouldDeleteUntagged,
dry_run: true
},
schedule: {
type: ScheduleType.MANUAL
}
})
.pipe(finalize(() => this.dryRunOnGoing = false))
.subscribe(
response => {
this.translate.get("GC.DRY_RUN_SUCCESS").subscribe((res: string) => {
this.errorHandler.info(res);
});
},
error => {
this.errorHandler.error(error);
}
);
response => {
this.errorHandler.info("GC.DRY_RUN_SUCCESS");
},
error => {
this.errorHandler.error(error);
}
);
}
private enableGc() {
this.disableGC = false;
}
private resetSchedule(cron) {
this.schedule = {
schedule: {
type: this.CronScheduleComponent.scheduleType,
cron: cron
}
};
if (!cron) {
this.shouldDeleteUntagged = false;
}
this.getJobs();
}
scheduleGc(cron: string) {
let schedule = this.schedule;
if (schedule && schedule.schedule && schedule.schedule.type !== SCHEDULE_TYPE_NONE) {
this.gcRepoService.putScheduleGc(this.shouldDeleteUntagged, this.CronScheduleComponent.scheduleType, cron).subscribe(
saveGcSchedule(cron: string) {
if (this.originCron && this.originCron.type !== ScheduleType.NONE) {// no schedule, then create
this.gcService.createGCSchedule({
parameters: {
delete_untagged: this.shouldDeleteUntagged,
dry_run: false
},
schedule: {
type: GcComponent.getScheduleType(cron),
cron: cron
}
}).subscribe(
response => {
this.translate
.get("GC.MSG_SCHEDULE_RESET")
.subscribe((res) => {
this.errorHandler.info(res);
this.CronScheduleComponent.resetSchedule();
});
this.resetSchedule(cron);
this.errorHandler.info("GC.MSG_SCHEDULE_RESET");
this.CronScheduleComponent.resetSchedule();
this.getCurrentSchedule(); // refresh schedule
},
error => {
this.errorHandler.error(error);
}
);
} else {
this.gcRepoService.postScheduleGc(this.shouldDeleteUntagged, this.CronScheduleComponent.scheduleType, cron).subscribe(
this.gcService.updateGCSchedule({
parameters: {
delete_untagged: this.shouldDeleteUntagged,
dry_run: false
},
schedule: {
type: GcComponent.getScheduleType(cron),
cron: cron
}
}).subscribe(
response => {
this.translate.get("GC.MSG_SCHEDULE_SET").subscribe((res) => {
this.errorHandler.info(res);
this.CronScheduleComponent.resetSchedule();
});
this.resetSchedule(cron);
this.errorHandler.info("GC.MSG_SCHEDULE_RESET");
this.CronScheduleComponent.resetSchedule();
this.getCurrentSchedule(); // refresh schedule
},
error => {
this.errorHandler.error(error);
@ -168,4 +165,20 @@ export class GcComponent implements OnInit {
);
}
}
static getScheduleType(cron: string): 'Hourly' | 'Daily' | 'Weekly' | 'Custom' | 'Manual' | 'None' {
if (cron) {
if (cron === '0 0 * * * *') {
return ScheduleType.HOURLY;
}
if (cron === '0 0 0 * * *') {
return ScheduleType.DAILY;
}
if (cron === '0 0 0 * * 0') {
return ScheduleType.WEEKLY;
}
return ScheduleType.CUSTOM;
}
return ScheduleType.NONE;
}
}

View File

@ -1,25 +0,0 @@
import { OriginCron } from "../../../../shared/services";
export const SCHEDULE_TYPE_NONE = "None";
export const ONE_MINITUE = 60000;
export const THREE_SECONDS = 3000;
export interface GCSchedule {
schedule?: OriginCron;
parameters?: {[key: string]: any};
id?: number;
job_name?: string;
job_kind?: string;
job_parameters?: string;
job_status?: string;
deleted?: boolean;
creation_time?: Date;
update_time?: Date;
}

View File

@ -1,72 +0,0 @@
import { Injectable } from '@angular/core';
import { Observable, Subscription, Subject, of } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { GcApiRepository } from './gc.api.repository';
import { ErrorHandler } from '../../../../shared/units/error-handler';
import { GcJobData } from './gcLog';
@Injectable()
export class GcRepoService {
constructor(
private gcApiRepository: GcApiRepository,
) {}
public manualGc(shouldDeleteUntagged: boolean, isDryRun: boolean): Observable<any> {
const param = {
"schedule": {
"type": "Manual"
},
parameters: {
delete_untagged: shouldDeleteUntagged,
dry_run: isDryRun
}
};
return this.gcApiRepository.postSchedule(param);
}
public getJobs(): Observable <GcJobData []> {
return this.gcApiRepository.getJobs();
}
public getLog(id): Observable <any> {
return this.gcApiRepository.getLog(id);
}
public getSchedule(): Observable <any> {
return this.gcApiRepository.getSchedule();
}
public postScheduleGc(shouldDeleteUntagged: boolean, type, cron): Observable <any> {
let param = {
"schedule": {
"type": type,
"cron": cron,
},
parameters: {
delete_untagged: shouldDeleteUntagged
}
};
return this.gcApiRepository.postSchedule(param);
}
public putScheduleGc(shouldDeleteUntagged, type, cron): Observable <any> {
let param = {
"schedule": {
"type": type,
"cron": cron,
},
parameters: {
delete_untagged: shouldDeleteUntagged
}
};
return this.gcApiRepository.putSchedule(param);
}
public getLogLink(id): string {
return this.gcApiRepository.getLogLink(id);
}
}

View File

@ -1,24 +0,0 @@
import { Injectable } from '@angular/core';
import { GcJobData, GcJobViewModel } from './gcLog';
@Injectable()
export class GcViewModelFactory {
public createJobViewModel(jobs: GcJobData[]): GcJobViewModel[] {
let gcViewModels: GcJobViewModel[] = [];
for (let job of jobs) {
let createTime = new Date(job.creation_time);
let updateTime = new Date(job.update_time);
gcViewModels.push({
id: job.id,
type: job.schedule ? job.schedule.type : null,
status: job.job_status,
parameters: job.job_parameters,
createTime: createTime,
updateTime: updateTime,
details: null
});
}
return gcViewModels;
}
}

View File

@ -1,28 +0,0 @@
export class GcJobData {
id: number;
job_name: string;
job_kind: string;
schedule: Schedule;
job_status: string;
job_parameters: string;
job_uuid: string;
creation_time: string;
update_time: string;
delete: boolean;
}
export class Schedule {
type: string;
cron: string;
}
export class GcJobViewModel {
id: number;
type: string;
status: string;
parameters: string;
createTime: Date;
updateTime: Date;
details: string;
}

View File

@ -242,3 +242,12 @@ export enum ResourceType {
}
export const CARD_VIEW_LOCALSTORAGE_KEY = 'card-view';
export enum ScheduleType {
NONE = "None",
DAILY = "Daily",
WEEKLY = "Weekly",
HOURLY = "Hourly",
CUSTOM = "Custom",
MANUAL = 'Manual'
}

View File

@ -47,4 +47,4 @@ export abstract class ErrorHandler {
abstract log(log: any): void;
abstract handleErrorPopupUnauthorized(error: any): void;
}
}