Refactor artifact-list-tab component (#17542)

Signed-off-by: AllForNothing <sshijun@vmware.com>
This commit is contained in:
Shijun Sun 2022-09-15 11:37:36 +08:00 committed by GitHub
parent acf68d3533
commit 13b3233faf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 266 additions and 123 deletions

View File

@ -3,58 +3,8 @@
#confirmationDialog
(confirmAction)="confirmDeletion($event)">
</confirmation-dialog>
<clr-modal
class="hidden-tag"
[(clrModalOpen)]="showTagManifestOpened"
[clrModalStaticBackdrop]="staticBackdrop"
[clrModalClosable]="closable">
<h3 class="modal-title">{{ manifestInfoTitle | translate }}</h3>
<div class="modal-body">
<div class="row col-md-12">
<textarea class="clr-textarea w-100" rows="2" #digestTarget>{{
digestId
}}</textarea>
</div>
</div>
<div class="modal-footer">
<span class="copy-failed" [hidden]="!copyFailed">{{
'TAG.COPY_ERROR' | translate
}}</span>
<button
type="button"
class="btn btn-primary"
[ngxClipboard]="digestTarget"
(cbOnSuccess)="onSuccess($event)"
(cbOnError)="onError($event)">
{{ 'BUTTON.COPY' | translate }}
</button>
</div>
</clr-modal>
<clr-modal
class="hidden-tag"
[(clrModalOpen)]="retagDialogOpened"
[clrModalStaticBackdrop]="staticBackdrop">
<h3 class="modal-title">{{ 'REPOSITORY.RETAG' | translate }}</h3>
<div class="modal-body retag-modal-body">
<div class="row col-md-12">
<hbr-image-name-input #imageNameInput></hbr-image-name-input>
</div>
</div>
<div class="modal-footer">
<button
type="button"
[disabled]="
imageNameInput.projectName.invalid ||
imageNameInput.repoName.invalid ||
imageNameInput.notExist ||
imageNameInput.checkingName
"
class="btn btn-primary"
(click)="onRetag()">
{{ 'BUTTON.CONFIRM' | translate }}
</button>
</div>
</clr-modal>
<app-copy-artifact></app-copy-artifact>
<app-copy-digest></app-copy-digest>
<div class="row tag-row">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<clr-datagrid

View File

@ -1,5 +1,5 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ArtifactListTabComponent } from './artifact-list-tab.component';
import { of } from 'rxjs';
import { delay } from 'rxjs/operators';
@ -343,7 +343,7 @@ describe('ArtifactListTabComponent (inline template)', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [SharedTestingModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
schemas: [NO_ERRORS_SCHEMA],
declarations: [ArtifactListTabComponent],
providers: [
{

View File

@ -95,6 +95,8 @@ import { ArtifactListPageService } from '../../artifact-list-page.service';
import { ACCESSORY_PAGE_SIZE } from './sub-accessories/sub-accessories.component';
import { Accessory } from 'ng-swagger-gen/models/accessory';
import { Tag } from '../../../../../../../../../ng-swagger-gen/models/tag';
import { CopyArtifactComponent } from './copy-artifact/copy-artifact.component';
import { CopyDigestComponent } from './copy-digest/copy-digest.component';
export interface LabelState {
iconsShow: boolean;
@ -120,17 +122,10 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
registryUrl: string;
artifactList: ArtifactFront[] = [];
availableTime = AVAILABLE_TIME;
showTagManifestOpened: boolean;
retagDialogOpened: boolean;
manifestInfoTitle: string;
digestId: string;
staticBackdrop = true;
closable = false;
lastFilteredTagName: string;
inprogress: boolean;
openLabelFilterPanel: boolean;
openLabelFilterPiece: boolean;
retagSrcImage: string;
showlabel: boolean;
pullComparator: Comparator<Artifact> = new CustomComparator<Artifact>(
@ -143,7 +138,6 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
);
loading = true;
copyFailed = false;
selectedRow: Artifact[] = [];
labelListOpen = false;
selectedTag: Artifact[];
@ -164,10 +158,10 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
@ViewChild('confirmationDialog')
confirmationDialog: ConfirmationDialogComponent;
@ViewChild('imageNameInput')
imageNameInput: ImageNameInputComponent;
@ViewChild('digestTarget') textInput: ElementRef;
@ViewChild(CopyArtifactComponent)
copyArtifactComponent: CopyArtifactComponent;
@ViewChild(CopyDigestComponent)
copyDigestComponent: CopyDigestComponent;
pageSize: number = getPageSizeFromLocalStorage(
PageSizeMapKeys.ARTIFACT_LIST_TAB_COMPONENT
);
@ -846,47 +840,10 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
retag() {
if (this.selectedRow && this.selectedRow.length && !this.depth) {
this.retagDialogOpened = true;
this.imageNameInput.imageNameForm.reset({
repoName: this.repoName,
projectName: null,
});
this.imageNameInput.notExist = false;
this.retagSrcImage =
this.repoName + ':' + this.selectedRow[0].digest;
this.copyArtifactComponent.retag(this.selectedRow[0].digest);
}
}
onRetag() {
let params: NewArtifactService.CopyArtifactParams = {
projectName: this.imageNameInput.projectName.value,
repositoryName: dbEncodeURIComponent(
this.imageNameInput.repoName.value
),
from: `${this.projectName}/${this.repoName}@${this.selectedRow[0].digest}`,
};
this.newArtifactService
.CopyArtifact(params)
.pipe(
finalize(() => {
this.imageNameInput.form.reset();
this.retagDialogOpened = false;
})
)
.subscribe(
response => {
this.translateService
.get('RETAG.MSG_SUCCESS')
.subscribe((res: string) => {
this.errorHandlerService.info(res);
});
},
error => {
this.errorHandlerService.error(error);
}
);
}
deleteArtifact() {
if (this.selectedRow && this.selectedRow.length && !this.depth) {
let artifactNames: string[] = [];
@ -1059,10 +1016,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
showDigestId() {
if (this.selectedRow && this.selectedRow.length === 1 && !this.depth) {
this.manifestInfoTitle = 'REPOSITORY.COPY_DIGEST_ID';
this.digestId = this.selectedRow[0].digest;
this.showTagManifestOpened = true;
this.copyFailed = false;
this.copyDigestComponent.showDigestId(this.selectedRow[0].digest);
}
}
@ -1080,21 +1034,6 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
}
}
onSuccess($event: any): void {
this.copyFailed = false;
// Directly close dialog
this.showTagManifestOpened = false;
}
onError($event: any): void {
// Show error
this.copyFailed = true;
// Select all text
if (this.textInput) {
this.textInput.nativeElement.select();
}
}
// Get vulnerability scanning status
scanStatus(artifact: Artifact): string {
if (artifact) {

View File

@ -0,0 +1,25 @@
<clr-modal
class="hidden-tag"
[(clrModalOpen)]="retagDialogOpened"
[clrModalStaticBackdrop]="true">
<h3 class="modal-title">{{ 'REPOSITORY.RETAG' | translate }}</h3>
<div class="modal-body retag-modal-body">
<div class="row col-md-12">
<hbr-image-name-input #imageNameInput></hbr-image-name-input>
</div>
</div>
<div class="modal-footer">
<button
type="button"
[disabled]="
imageNameInput.projectName.invalid ||
imageNameInput.repoName.invalid ||
imageNameInput.notExist ||
imageNameInput.checkingName
"
class="btn btn-primary"
(click)="onRetag()">
{{ 'BUTTON.CONFIRM' | translate }}
</button>
</div>
</clr-modal>

View File

@ -0,0 +1,57 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CopyArtifactComponent } from './copy-artifact.component';
import { SharedTestingModule } from '../../../../../../../../shared/shared.module';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { RouterTestingModule } from '@angular/router/testing';
import { ArtifactService } from 'ng-swagger-gen/services/artifact.service';
import { of } from 'rxjs';
describe('CopyArtifactComponent', () => {
let component: CopyArtifactComponent;
let fixture: ComponentFixture<CopyArtifactComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
schemas: [NO_ERRORS_SCHEMA],
declarations: [CopyArtifactComponent],
imports: [SharedTestingModule, RouterTestingModule],
}).compileComponents();
fixture = TestBed.createComponent(CopyArtifactComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should open modal', async () => {
component.retag(`sha256@test`);
fixture.detectChanges();
await fixture.whenStable();
const modal = fixture.nativeElement.querySelector(`clr-modal`);
expect(modal).toBeTruthy();
});
it('should call retag API', async () => {
const artifactService: ArtifactService =
TestBed.inject(ArtifactService);
const spy: jasmine.Spy = spyOn(
artifactService,
`CopyArtifact`
).and.returnValue(of(null));
component.retagDialogOpened = true;
component.imageNameInput.imageNameForm.reset({
repoName: `test`,
projectName: `test`,
});
fixture.detectChanges();
await fixture.whenStable();
const btn: HTMLButtonElement =
fixture.nativeElement.querySelector(`.btn-primary`);
btn.click();
fixture.detectChanges();
expect(spy.calls.count()).toEqual(1);
});
});

View File

@ -0,0 +1,76 @@
import { Component, OnInit, ViewChild } from '@angular/core';
import { dbEncodeURIComponent } from '../../../../../../../../shared/units/utils';
import { finalize } from 'rxjs/operators';
import { ArtifactService } from 'ng-swagger-gen/services/artifact.service';
import { ImageNameInputComponent } from '../../../../../../../../shared/components/image-name-input/image-name-input.component';
import { Project } from '../../../../../../project';
import { ActivatedRoute } from '@angular/router';
import { ErrorHandler } from '../../../../../../../../shared/units/error-handler';
import { TranslateService } from '@ngx-translate/core';
@Component({
selector: 'app-copy-artifact',
templateUrl: './copy-artifact.component.html',
styleUrls: ['./copy-artifact.component.scss'],
})
export class CopyArtifactComponent implements OnInit {
retagDialogOpened: boolean = false;
projectName: string;
repoName: string;
@ViewChild('imageNameInput')
imageNameInput: ImageNameInputComponent;
digest: string;
constructor(
private activatedRoute: ActivatedRoute,
private artifactService: ArtifactService,
private errorHandlerService: ErrorHandler,
private translateService: TranslateService
) {}
ngOnInit(): void {
const resolverData = this.activatedRoute.snapshot?.parent?.parent?.data;
if (resolverData) {
this.projectName = (<Project>resolverData['projectResolver']).name;
}
this.repoName = this.activatedRoute.snapshot?.parent?.params['repo'];
}
onRetag() {
let params: ArtifactService.CopyArtifactParams = {
projectName: this.imageNameInput.projectName.value,
repositoryName: dbEncodeURIComponent(
this.imageNameInput.repoName.value
),
from: `${this.projectName}/${this.repoName}@${this.digest}`,
};
this.artifactService
.CopyArtifact(params)
.pipe(
finalize(() => {
this.imageNameInput.form.reset();
this.retagDialogOpened = false;
})
)
.subscribe({
next: response => {
this.translateService
.get('RETAG.MSG_SUCCESS')
.subscribe((res: string) => {
this.errorHandlerService.info(res);
});
},
error: error => {
this.errorHandlerService.error(error);
},
});
}
retag(digest: string) {
this.retagDialogOpened = true;
this.imageNameInput.imageNameForm.reset({
repoName: this.repoName,
projectName: null,
});
this.digest = digest;
this.imageNameInput.notExist = false;
}
}

View File

@ -0,0 +1,27 @@
<clr-modal
class="hidden-tag"
[(clrModalOpen)]="showTagManifestOpened"
[clrModalStaticBackdrop]="true"
[clrModalClosable]="false">
<h3 class="modal-title">{{ 'REPOSITORY.COPY_DIGEST_ID' | translate }}</h3>
<div class="modal-body">
<div class="row col-md-12">
<textarea class="clr-textarea w-100" rows="2" #digestTarget>{{
digestId
}}</textarea>
</div>
</div>
<div class="modal-footer">
<span class="copy-failed" [hidden]="!copyFailed">{{
'TAG.COPY_ERROR' | translate
}}</span>
<button
type="button"
class="btn btn-primary"
[ngxClipboard]="digestTarget"
(cbOnSuccess)="onSuccess($event)"
(cbOnError)="onError($event)">
{{ 'BUTTON.COPY' | translate }}
</button>
</div>
</clr-modal>

View File

@ -0,0 +1,32 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CopyDigestComponent } from './copy-digest.component';
import { SharedTestingModule } from '../../../../../../../../shared/shared.module';
describe('CopyDigestComponent', () => {
let component: CopyDigestComponent;
let fixture: ComponentFixture<CopyDigestComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [CopyDigestComponent],
imports: [SharedTestingModule],
}).compileComponents();
fixture = TestBed.createComponent(CopyDigestComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should show right digest', async () => {
const digest: string = 'sha256@test';
component.showDigestId(digest);
fixture.detectChanges();
await fixture.whenStable();
const textArea: HTMLTextAreaElement =
fixture.nativeElement.querySelector(`textarea`);
expect(textArea.textContent).toEqual(digest);
});
});

View File

@ -0,0 +1,33 @@
import { Component, ElementRef, ViewChild } from '@angular/core';
@Component({
selector: 'app-copy-digest',
templateUrl: './copy-digest.component.html',
styleUrls: ['./copy-digest.component.scss'],
})
export class CopyDigestComponent {
showTagManifestOpened: boolean = false;
digestId: string;
@ViewChild('digestTarget') textInput: ElementRef;
copyFailed: boolean = false;
constructor() {}
onSuccess($event: any): void {
this.copyFailed = false;
// Directly close dialog
this.showTagManifestOpened = false;
}
onError($event: any): void {
// Show error
this.copyFailed = true;
// Select all text
if (this.textInput) {
this.textInput.nativeElement.select();
}
}
showDigestId(digest: string) {
this.digestId = digest;
this.showTagManifestOpened = true;
this.copyFailed = false;
}
}

View File

@ -21,6 +21,8 @@ import { HistogramChartComponent } from './vulnerability-scanning/histogram-char
import { ArtifactInfoComponent } from './artifact-list-page/artifact-list/artifact-info/artifact-info.component';
import { SubAccessoriesComponent } from './artifact-list-page/artifact-list/artifact-list-tab/sub-accessories/sub-accessories.component';
import { ArtifactListPageService } from './artifact-list-page/artifact-list-page.service';
import { CopyArtifactComponent } from './artifact-list-page/artifact-list/artifact-list-tab/copy-artifact/copy-artifact.component';
import { CopyDigestComponent } from './artifact-list-page/artifact-list/artifact-list-tab/copy-digest/copy-digest.component';
const routes: Routes = [
{
@ -82,6 +84,8 @@ const routes: Routes = [
HistogramChartComponent,
ArtifactInfoComponent,
SubAccessoriesComponent,
CopyArtifactComponent,
CopyDigestComponent,
],
imports: [RouterModule.forChild(routes), SharedModule],
providers: [