Support accessory recursion for artifact list (#18366)

1. Update accessory UI to support recursion

Signed-off-by: AllForNothing <sshijun@vmware.com>
This commit is contained in:
Shijun Sun 2023-03-17 13:01:24 +08:00 committed by GitHub
parent 321d8a0885
commit 53d86f872e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 94 additions and 32 deletions

View File

@ -546,7 +546,6 @@
ngProjectAs="clr-dg-row-detail" ngProjectAs="clr-dg-row-detail"
*ngIf="artifact?.accessories?.length"> *ngIf="artifact?.accessories?.length">
<sub-accessories <sub-accessories
(deleteAccessory)="deleteAccessory($event)"
[projectName]="projectName" [projectName]="projectName"
[repositoryName]="repoName" [repositoryName]="repoName"
[artifactDigest]="artifact?.digest" [artifactDigest]="artifact?.digest"

View File

@ -189,6 +189,7 @@ export class ArtifactListTabComponent
); );
copiedHiddenArray: boolean[] = []; copiedHiddenArray: boolean[] = [];
private _hasViewInit: boolean = false; private _hasViewInit: boolean = false;
deleteAccessorySub: Subscription;
constructor( constructor(
private errorHandlerService: ErrorHandler, private errorHandlerService: ErrorHandler,
private artifactService: ArtifactService, private artifactService: ArtifactService,
@ -242,6 +243,14 @@ export class ArtifactListTabComponent
} }
); );
} }
if (!this.deleteAccessorySub) {
this.deleteAccessorySub = this.eventService.subscribe(
HarborEvent.DELETE_ACCESSORY,
(a: Accessory) => {
this.deleteAccessory(a);
}
);
}
} }
ngAfterViewInit() { ngAfterViewInit() {
@ -253,6 +262,10 @@ export class ArtifactListTabComponent
this.updateArtifactSub.unsubscribe(); this.updateArtifactSub.unsubscribe();
this.updateArtifactSub = null; this.updateArtifactSub = null;
} }
if (this.deleteAccessorySub) {
this.deleteAccessorySub.unsubscribe();
this.deleteAccessorySub = null;
}
} }
get withNotary(): boolean { get withNotary(): boolean {
return this.appConfigService.getConfig()?.with_notary; return this.appConfigService.getConfig()?.with_notary;

View File

@ -16,10 +16,14 @@
</button> </button>
</clr-dg-action-overflow> </clr-dg-action-overflow>
<clr-dg-cell class="relative"> <clr-dg-cell class="relative">
<!--it will cause a ExpressionChangedAfterItHasBeenCheckedError when read datagrid['el']?.nativeElement?.offsetHeight so
use OnPush Strategy to avoid ExpressionChangedAfterItHasBeenCheckedError-->
<hr <hr
class="y-dash-line" class="y-dash-line"
*ngIf="i === displayedAccessories?.length - 1 && viewInit" [hidden]="i !== displayedAccessories?.length - 1"
[style.height.px]="dashLineHeight" /> [style.height.px]="
datagrid['el']?.nativeElement?.offsetHeight || 0
" />
<hr class="x-dash-line" /> <hr class="x-dash-line" />
<div class="cell"> <div class="cell">
<div <div
@ -55,6 +59,17 @@
{{ a.creation_time | harborDatetime : 'short' }} {{ a.creation_time | harborDatetime : 'short' }}
</div> </div>
</clr-dg-cell> </clr-dg-cell>
<ng-container
ngProjectAs="clr-dg-row-detail"
*ngIf="a?.accessories?.length">
<sub-accessories
[projectName]="projectName"
[repositoryName]="repositoryName"
[artifactDigest]="a?.digest"
[total]="a?.accessoryNumber"
[accessories]="a?.accessories"
*clrIfExpanded></sub-accessories>
</ng-container>
</clr-dg-row> </clr-dg-row>
<clr-dg-footer *ngIf="total > pageSize"> <clr-dg-footer *ngIf="total > pageSize">
<clr-dg-pagination <clr-dg-pagination

View File

@ -64,6 +64,9 @@ describe('SubAccessoriesComponent', () => {
listAccessories() { listAccessories() {
return of(page2).pipe(delay(0)); return of(page2).pipe(delay(0));
}, },
listAccessoriesResponse() {
return of({}).pipe(delay(0));
},
}; };
let component: SubAccessoriesComponent; let component: SubAccessoriesComponent;

View File

@ -1,10 +1,10 @@
import { import {
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component, Component,
EventEmitter,
Input, Input,
OnInit, OnInit,
Output,
ViewChild,
} from '@angular/core'; } from '@angular/core';
import { import {
clone, clone,
@ -22,8 +22,15 @@ import { ErrorHandler } from '../../../../../../../../shared/units/error-handler
import { finalize } from 'rxjs/operators'; import { finalize } from 'rxjs/operators';
import { SafeUrl } from '@angular/platform-browser'; import { SafeUrl } from '@angular/platform-browser';
import { ArtifactService } from '../../../../artifact.service'; import { ArtifactService } from '../../../../artifact.service';
import { AccessoryQueryParams, artifactDefault } from '../../../../artifact'; import {
import { ClrDatagrid } from '@clr/angular'; AccessoryFront,
AccessoryQueryParams,
artifactDefault,
} from '../../../../artifact';
import {
EventService,
HarborEvent,
} from '../../../../../../../../services/event-service/event.service';
export const ACCESSORY_PAGE_SIZE: number = 5; export const ACCESSORY_PAGE_SIZE: number = 5;
@ -31,8 +38,9 @@ export const ACCESSORY_PAGE_SIZE: number = 5;
selector: 'sub-accessories', selector: 'sub-accessories',
templateUrl: 'sub-accessories.component.html', templateUrl: 'sub-accessories.component.html',
styleUrls: ['./sub-accessories.component.scss'], styleUrls: ['./sub-accessories.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, // use OnPush Strategy to avoid ExpressionChangedAfterItHasBeenCheckedError
}) })
export class SubAccessoriesComponent implements OnInit { export class SubAccessoriesComponent implements OnInit, AfterViewInit {
@Input() @Input()
projectName: string; projectName: string;
@Input() @Input()
@ -41,32 +49,29 @@ export class SubAccessoriesComponent implements OnInit {
artifactDigest: string; artifactDigest: string;
@Input() @Input()
accessories: Accessory[] = []; accessories: Accessory[] = [];
@Output()
deleteAccessory: EventEmitter<Accessory> = new EventEmitter<Accessory>();
currentPage: number = 1; currentPage: number = 1;
@Input() @Input()
total: number = 0; total: number = 0;
pageSize: number = ACCESSORY_PAGE_SIZE; pageSize: number = ACCESSORY_PAGE_SIZE;
page: number = 1; page: number = 1;
displayedAccessories: Accessory[] = []; displayedAccessories: AccessoryFront[] = [];
loading: boolean = false; loading: boolean = false;
@ViewChild('datagrid')
datagrid: ClrDatagrid;
viewInit: boolean = false;
constructor( constructor(
private activatedRoute: ActivatedRoute, private activatedRoute: ActivatedRoute,
private router: Router, private router: Router,
private newArtifactService: NewArtifactService, private newArtifactService: NewArtifactService,
private artifactService: ArtifactService, private artifactService: ArtifactService,
private errorHandlerService: ErrorHandler private errorHandlerService: ErrorHandler,
private cdf: ChangeDetectorRef,
private event: EventService
) {} ) {}
ngAfterViewInit(): void {
this.cdf.detectChanges();
}
ngOnInit(): void { ngOnInit(): void {
this.displayedAccessories = clone(this.accessories); this.displayedAccessories = clone(this.accessories);
// avoid ng checking error
setTimeout(() => {
this.viewInit = true;
});
} }
size(size: number) { size(size: number) {
return formatSize(size.toString()); return formatSize(size.toString());
@ -103,13 +108,14 @@ export class SubAccessoriesComponent implements OnInit {
} }
delete(a: Accessory) { delete(a: Accessory) {
this.deleteAccessory.emit(a); this.event.publish(HarborEvent.DELETE_ACCESSORY, a);
} }
clrLoad() { clrLoad() {
if (this.currentPage === 1) { if (this.currentPage === 1) {
this.displayedAccessories = clone(this.accessories); this.displayedAccessories = clone(this.accessories);
this.getIconFromBackend(); this.getIconFromBackend();
this.getAccessoriesAsync(this.displayedAccessories);
return; return;
} }
this.loading = true; this.loading = true;
@ -126,7 +132,9 @@ export class SubAccessoriesComponent implements OnInit {
.subscribe( .subscribe(
res => { res => {
this.displayedAccessories = res; this.displayedAccessories = res;
this.cdf.detectChanges();
this.getIconFromBackend(); this.getIconFromBackend();
this.getAccessoriesAsync(this.displayedAccessories);
}, },
error => { error => {
this.errorHandlerService.error(error); this.errorHandlerService.error(error);
@ -139,14 +147,37 @@ export class SubAccessoriesComponent implements OnInit {
} }
} }
get dashLineHeight() { // get accessories
if ( getAccessoriesAsync(artifacts: AccessoryFront[]) {
this.datagrid && if (artifacts && artifacts.length) {
this.datagrid['el'] && artifacts.forEach(item => {
this.datagrid['el']?.nativeElement?.offsetHeight const listTagParams: NewArtifactService.ListAccessoriesParams =
) { {
return this.datagrid['el'].nativeElement?.offsetHeight; projectName: this.projectName,
repositoryName: dbEncodeURIComponent(
this.repositoryName
),
reference: item.digest,
page: 1,
pageSize: ACCESSORY_PAGE_SIZE,
};
this.newArtifactService
.listAccessoriesResponse(listTagParams)
.subscribe(res => {
if (res.headers) {
let xHeader: string =
res.headers.get('x-total-count');
if (xHeader) {
item.accessoryNumber = Number.parseInt(
xHeader,
10
);
}
}
item.accessories = res.body;
this.cdf.detectChanges();
});
});
} }
return 0;
} }
} }

View File

@ -14,9 +14,9 @@ export interface ArtifactFront extends Artifact {
} }
export interface AccessoryFront extends Accessory { export interface AccessoryFront extends Accessory {
pullCommand?: string; coSigned?: string;
tagNumber?: number; accessoryNumber?: number;
scan_overview?: any; accessories?: any;
} }
export const multipleFilter: Array<{ export const multipleFilter: Array<{

View File

@ -79,4 +79,5 @@ export enum HarborEvent {
STOP_SCAN_ARTIFACT = 'stopScanArtifact', STOP_SCAN_ARTIFACT = 'stopScanArtifact',
UPDATE_VULNERABILITY_INFO = 'UpdateVulnerabilityInfo', UPDATE_VULNERABILITY_INFO = 'UpdateVulnerabilityInfo',
REFRESH_EXPORT_JOBS = 'refreshExportJobs', REFRESH_EXPORT_JOBS = 'refreshExportJobs',
DELETE_ACCESSORY = 'deleteAccessory',
} }