Merge pull request #2504 from steven-zou/master

Improve repo-tag-stack view
This commit is contained in:
Steven Zou 2017-06-13 17:04:12 +08:00 committed by GitHub
commit b695ec78db
5 changed files with 129 additions and 134 deletions

View File

@ -21,7 +21,7 @@ export const REPOSITORY_STACKVIEW_TEMPLATE: string = `
<clr-dg-cell>{{r.name}}</clr-dg-cell> <clr-dg-cell>{{r.name}}</clr-dg-cell>
<clr-dg-cell>{{r.tags_count}}</clr-dg-cell> <clr-dg-cell>{{r.tags_count}}</clr-dg-cell>
<clr-dg-cell>{{r.pull_count}}</clr-dg-cell> <clr-dg-cell>{{r.pull_count}}</clr-dg-cell>
<hbr-tag *clrIfExpanded ngProjectAs="clr-dg-row-detail" class="sub-grid-custom" [repoName]="r.name" [hasSignedIn]="hasSignedIn" [hasProjectAdminRole]="hasProjectAdminRole" [projectId]="projectId" [isEmbedded]="true" (refreshRepo)="refresh($event)"></hbr-tag> <hbr-tag *clrIfExpanded ngProjectAs="clr-dg-row-detail" class="sub-grid-custom" [repoName]="r.name" [registryUrl]="registryUrl" [withNotary]="withNotary" [hasSignedIn]="hasSignedIn" [hasProjectAdminRole]="hasProjectAdminRole" [projectId]="projectId" [isEmbedded]="true" (refreshRepo)="refresh($event)"></hbr-tag>
</clr-dg-row> </clr-dg-row>
<clr-dg-footer> <clr-dg-footer>
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPOSITORY.OF' | translate}} {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPOSITORY.OF' | translate}}

View File

@ -17,18 +17,15 @@ import { SystemInfoService, SystemInfoDefaultService } from '../service/system-i
import { click } from '../utils'; import { click } from '../utils';
describe('RepositoryComponentStackview (inline template)', ()=> { describe('RepositoryComponentStackview (inline template)', () => {
let compRepo: RepositoryStackviewComponent; let compRepo: RepositoryStackviewComponent;
let fixtureRepo: ComponentFixture<RepositoryStackviewComponent>; let fixtureRepo: ComponentFixture<RepositoryStackviewComponent>;
let repositoryService: RepositoryService; let repositoryService: RepositoryService;
let spyRepos: jasmine.Spy;
let compTag: TagComponent;
let fixtureTag: ComponentFixture<TagComponent>;
let tagService: TagService; let tagService: TagService;
let systemInfoService: SystemInfoService; let systemInfoService: SystemInfoService;
let spyRepos: jasmine.Spy;
let spyTags: jasmine.Spy; let spyTags: jasmine.Spy;
let spySystemInfo: jasmine.Spy; let spySystemInfo: jasmine.Spy;
@ -44,7 +41,6 @@ describe('RepositoryComponentStackview (inline template)', ()=> {
"harbor_version": "v1.1.1-rc1-160-g565110d" "harbor_version": "v1.1.1-rc1-160-g565110d"
}; };
let mockRepoData: Repository[] = [ let mockRepoData: Repository[] = [
{ {
"id": 1, "id": 1,
@ -80,10 +76,12 @@ describe('RepositoryComponentStackview (inline template)', ()=> {
]; ];
let config: IServiceConfig = { let config: IServiceConfig = {
repositoryBaseEndpoint: '/api/repository/testing' repositoryBaseEndpoint: '/api/repository/testing',
systemInfoEndpoint: '/api/systeminfo/testing',
targetBaseEndpoint: '/api/tag/testing'
}; };
beforeEach(async(()=>{ beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ imports: [
SharedModule SharedModule
@ -96,7 +94,7 @@ describe('RepositoryComponentStackview (inline template)', ()=> {
], ],
providers: [ providers: [
ErrorHandler, ErrorHandler,
{ provide: SERVICE_CONFIG, useValue : config }, { provide: SERVICE_CONFIG, useValue: config },
{ provide: RepositoryService, useClass: RepositoryDefaultService }, { provide: RepositoryService, useClass: RepositoryDefaultService },
{ provide: TagService, useClass: TagDefaultService }, { provide: TagService, useClass: TagDefaultService },
{ provide: SystemInfoService, useClass: SystemInfoDefaultService } { provide: SystemInfoService, useClass: SystemInfoDefaultService }
@ -104,69 +102,74 @@ describe('RepositoryComponentStackview (inline template)', ()=> {
}); });
})); }));
beforeEach(()=>{ beforeEach(() => {
fixtureRepo = TestBed.createComponent(RepositoryStackviewComponent); fixtureRepo = TestBed.createComponent(RepositoryStackviewComponent);
compRepo = fixtureRepo.componentInstance; compRepo = fixtureRepo.componentInstance;
compRepo.projectId = 1; compRepo.projectId = 1;
compRepo.hasProjectAdminRole = true; compRepo.hasProjectAdminRole = true;
repositoryService = fixtureRepo.debugElement.injector.get(RepositoryService); repositoryService = fixtureRepo.debugElement.injector.get(RepositoryService);
systemInfoService = fixtureRepo.debugElement.injector.get(SystemInfoService);
spyRepos = spyOn(repositoryService, 'getRepositories').and.returnValues(Promise.resolve(mockRepoData)); spyRepos = spyOn(repositoryService, 'getRepositories').and.returnValues(Promise.resolve(mockRepoData));
fixtureRepo.detectChanges();
});
beforeEach(()=>{
fixtureTag = TestBed.createComponent(TagComponent);
compTag = fixtureTag.componentInstance;
compTag.projectId = compRepo.projectId;
compTag.repoName = 'library/busybox';
compTag.hasProjectAdminRole = true;
compTag.hasSignedIn = true;
tagService = fixtureTag.debugElement.injector.get(TagService);
systemInfoService = fixtureTag.debugElement.injector.get(SystemInfoService);
spyTags = spyOn(tagService, 'getTags').and.returnValues(Promise.resolve(mockTagData));
spySystemInfo = spyOn(systemInfoService, 'getSystemInfo').and.returnValues(Promise.resolve(mockSystemInfo)); spySystemInfo = spyOn(systemInfoService, 'getSystemInfo').and.returnValues(Promise.resolve(mockSystemInfo));
fixtureTag.detectChanges(); fixtureRepo.detectChanges();
}); });
it('should load and render data', async(()=>{ it('should create', () => {
expect(compRepo).toBeTruthy();
});
it('should load and render data', async(() => {
fixtureRepo.detectChanges(); fixtureRepo.detectChanges();
fixtureRepo.whenStable().then(()=>{
fixtureRepo.whenStable().then(() => {
fixtureRepo.detectChanges(); fixtureRepo.detectChanges();
let deRepo: DebugElement = fixtureRepo.debugElement.query(By.css('datagrid-cell')); let deRepo: DebugElement = fixtureRepo.debugElement.query(By.css('datagrid-cell'));
fixtureRepo.detectChanges();
expect(deRepo).toBeTruthy(); expect(deRepo).toBeTruthy();
let elRepo: HTMLElement = deRepo.nativeElement; let elRepo: HTMLElement = deRepo.nativeElement;
fixtureRepo.detectChanges();
expect(elRepo).toBeTruthy(); expect(elRepo).toBeTruthy();
fixtureRepo.detectChanges();
expect(elRepo.textContent).toEqual('library/busybox'); expect(elRepo.textContent).toEqual('library/busybox');
click(deRepo);
fixtureTag.detectChanges();
let deTag: DebugElement = fixtureTag.debugElement.query(By.css('datagrid-cell'));
expect(deTag).toBeTruthy();
let elTag: HTMLElement = deTag.nativeElement;
expect(elTag).toBeTruthy();
expect(elTag.textContent).toEqual('1.12.5');
}); });
})); }));
it('should filter data by keyword', async(()=>{ it('should filter data by keyword', async(() => {
fixtureRepo.detectChanges(); fixtureRepo.detectChanges();
fixtureRepo.whenStable().then(()=>{
fixtureRepo.whenStable().then(() => {
fixtureRepo.detectChanges(); fixtureRepo.detectChanges();
compRepo.doSearchRepoNames('nginx'); compRepo.doSearchRepoNames('nginx');
fixtureRepo.detectChanges(); fixtureRepo.detectChanges();
let de: DebugElement[] = fixtureRepo.debugElement.queryAll(By.css('datagrid-cell')); let de: DebugElement[] = fixtureRepo.debugElement.queryAll(By.css('datagrid-cell'));
fixtureRepo.detectChanges();
expect(de).toBeTruthy(); expect(de).toBeTruthy();
expect(de.length).toEqual(1); expect(de.length).toEqual(1);
let el: HTMLElement = de[0].nativeElement; let el: HTMLElement = de[0].nativeElement;
fixtureRepo.detectChanges();
expect(el).toBeTruthy(); expect(el).toBeTruthy();
expect(el.textContent).toEqual('library/nginx'); expect(el.textContent).toEqual('library/nginx');
}); });
})); }));
it('should display embedded tag view when click >', async(() => {
fixtureRepo.detectChanges();
fixtureRepo.whenStable().then(() => {
fixtureRepo.detectChanges();
let el: HTMLElement = fixtureRepo.nativeElement.querySelector('.datagrid-expandable-caret');
expect(el).toBeTruthy();
let button: HTMLButtonElement = el.querySelector('button');
expect(button).toBeTruthy();
click(button);
fixtureRepo.detectChanges();
let el2: HTMLElement = fixtureRepo.nativeElement.querySelector('.datagrid-row-detail');
expect(el2).toBeTruthy();
let el3: Element = el2.querySelector(".datagrid-cell");
expect(el3).toBeTruthy();
expect(el3.textContent).toEqual('1.11.5');
});
}));
}); });

View File

@ -5,9 +5,13 @@ import { Comparator } from 'clarity-angular';
import { REPOSITORY_STACKVIEW_TEMPLATE } from './repository-stackview.component.html'; import { REPOSITORY_STACKVIEW_TEMPLATE } from './repository-stackview.component.html';
import { REPOSITORY_STACKVIEW_STYLES } from './repository-stackview.component.css'; import { REPOSITORY_STACKVIEW_STYLES } from './repository-stackview.component.css';
import { Repository } from '../service/interface'; import {
Repository,
SystemInfo,
SystemInfoService,
RepositoryService
} from '../service/index';
import { ErrorHandler } from '../error-handler/error-handler'; import { ErrorHandler } from '../error-handler/error-handler';
import { RepositoryService } from '../service/repository.service';
import { toPromise, CustomComparator } from '../utils'; import { toPromise, CustomComparator } from '../utils';
@ -21,7 +25,7 @@ import { Subscription } from 'rxjs/Subscription';
@Component({ @Component({
selector: 'hbr-repository-stackview', selector: 'hbr-repository-stackview',
template: REPOSITORY_STACKVIEW_TEMPLATE, template: REPOSITORY_STACKVIEW_TEMPLATE,
styles: [ REPOSITORY_STACKVIEW_STYLES ], styles: [REPOSITORY_STACKVIEW_STYLES],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class RepositoryStackviewComponent implements OnInit { export class RepositoryStackviewComponent implements OnInit {
@ -33,6 +37,7 @@ export class RepositoryStackviewComponent implements OnInit {
lastFilteredRepoName: string; lastFilteredRepoName: string;
repositories: Repository[]; repositories: Repository[];
systemInfo: SystemInfo;
@ViewChild('confirmationDialog') @ViewChild('confirmationDialog')
confirmationDialog: ConfirmationDialogComponent; confirmationDialog: ConfirmationDialogComponent;
@ -45,7 +50,16 @@ export class RepositoryStackviewComponent implements OnInit {
private errorHandler: ErrorHandler, private errorHandler: ErrorHandler,
private translateService: TranslateService, private translateService: TranslateService,
private repositoryService: RepositoryService, private repositoryService: RepositoryService,
private ref: ChangeDetectorRef){} private systemInfoService: SystemInfoService,
private ref: ChangeDetectorRef) { }
public get registryUrl(): string {
return this.systemInfo ? this.systemInfo.registry_url : "";
}
public get withNotary(): boolean {
return this.systemInfo ? this.systemInfo.with_notary : false;
}
confirmDeletion(message: ConfirmationAcknowledgement) { confirmDeletion(message: ConfirmationAcknowledgement) {
if (message && if (message &&
@ -58,16 +72,21 @@ export class RepositoryStackviewComponent implements OnInit {
response => { response => {
this.refresh(); this.refresh();
this.translateService.get('REPOSITORY.DELETED_REPO_SUCCESS') this.translateService.get('REPOSITORY.DELETED_REPO_SUCCESS')
.subscribe(res=>this.errorHandler.info(res)); .subscribe(res => this.errorHandler.info(res));
}).catch(error => this.errorHandler.error(error)); }).catch(error => this.errorHandler.error(error));
} }
} }
ngOnInit(): void { ngOnInit(): void {
if(!this.projectId) { if (!this.projectId) {
this.errorHandler.error('Project ID cannot be unset.'); this.errorHandler.error('Project ID cannot be unset.');
return; return;
} }
//Get system info for tag views
toPromise<SystemInfo>(this.systemInfoService.getSystemInfo())
.then(systemInfo => this.systemInfo = systemInfo)
.catch(error => this.errorHandler.error(error));
this.lastFilteredRepoName = ''; this.lastFilteredRepoName = '';
this.retrieve(); this.retrieve();
} }
@ -78,8 +97,8 @@ export class RepositoryStackviewComponent implements OnInit {
.then( .then(
repos => this.repositories = repos, repos => this.repositories = repos,
error => this.errorHandler.error(error)); error => this.errorHandler.error(error));
let hnd = setInterval(()=>this.ref.markForCheck(), 100); let hnd = setInterval(() => this.ref.markForCheck(), 100);
setTimeout(()=>clearInterval(hnd), 1000); setTimeout(() => clearInterval(hnd), 1000);
} }
doSearchRepoNames(repoName: string) { doSearchRepoNames(repoName: string) {

View File

@ -8,33 +8,16 @@ import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation
import { TagComponent } from './tag.component'; import { TagComponent } from './tag.component';
import { ErrorHandler } from '../error-handler/error-handler'; import { ErrorHandler } from '../error-handler/error-handler';
import { SystemInfo, Tag } from '../service/interface'; import { Tag } from '../service/interface';
import { SERVICE_CONFIG, IServiceConfig } from '../service.config'; import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
import { TagService, TagDefaultService } from '../service/tag.service'; import { TagService, TagDefaultService } from '../service/tag.service';
import { SystemInfoService, SystemInfoDefaultService } from '../service/system-info.service';
describe('TagComponent (inline template)', ()=> { describe('TagComponent (inline template)', ()=> {
let comp: TagComponent; let comp: TagComponent;
let fixture: ComponentFixture<TagComponent>; let fixture: ComponentFixture<TagComponent>;
let tagService: TagService; let tagService: TagService;
let systemInfoService: SystemInfoService;
let spy: jasmine.Spy; let spy: jasmine.Spy;
let spySystemInfo: jasmine.Spy;
let mockSystemInfo: SystemInfo = {
"with_notary": true,
"with_admiral": false,
"admiral_endpoint": "NA",
"auth_mode": "db_auth",
"registry_url": "10.112.122.56",
"project_creation_restriction": "everyone",
"self_registration": true,
"has_ca_root": false,
"harbor_version": "v1.1.1-rc1-160-g565110d"
};
let mockTags: Tag[] = [ let mockTags: Tag[] = [
{ {
"digest": "sha256:e5c82328a509aeb7c18c1d7fb36633dc638fcf433f651bdcda59c1cc04d3ee55", "digest": "sha256:e5c82328a509aeb7c18c1d7fb36633dc638fcf433f651bdcda59c1cc04d3ee55",
@ -64,8 +47,7 @@ describe('TagComponent (inline template)', ()=> {
providers: [ providers: [
ErrorHandler, ErrorHandler,
{ provide: SERVICE_CONFIG, useValue: config }, { provide: SERVICE_CONFIG, useValue: config },
{ provide: TagService, useClass: TagDefaultService }, { provide: TagService, useClass: TagDefaultService }
{ provide: SystemInfoService, useClass: SystemInfoDefaultService }
] ]
}); });
})); }));
@ -78,11 +60,11 @@ describe('TagComponent (inline template)', ()=> {
comp.repoName = 'library/nginx'; comp.repoName = 'library/nginx';
comp.hasProjectAdminRole = true; comp.hasProjectAdminRole = true;
comp.hasSignedIn = true; comp.hasSignedIn = true;
comp.registryUrl = 'http://registry.testing.com';
comp.withNotary = false;
tagService = fixture.debugElement.injector.get(TagService); tagService = fixture.debugElement.injector.get(TagService);
systemInfoService = fixture.debugElement.injector.get(SystemInfoService);
spy = spyOn(tagService, 'getTags').and.returnValues(Promise.resolve(mockTags)); spy = spyOn(tagService, 'getTags').and.returnValues(Promise.resolve(mockTags));
spySystemInfo = spyOn(systemInfoService, 'getSystemInfo').and.returnValues(Promise.resolve(mockSystemInfo));
fixture.detectChanges(); fixture.detectChanges();
}); });

View File

@ -14,7 +14,6 @@
import { Component, OnInit, ViewChild, Input, Output, EventEmitter, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; import { Component, OnInit, ViewChild, Input, Output, EventEmitter, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
import { TagService } from '../service/tag.service'; import { TagService } from '../service/tag.service';
import { SystemInfoService } from '../service/system-info.service';
import { ErrorHandler } from '../error-handler/error-handler'; import { ErrorHandler } from '../error-handler/error-handler';
import { ConfirmationTargets, ConfirmationState, ConfirmationButtons } from '../shared/shared.const'; import { ConfirmationTargets, ConfirmationState, ConfirmationButtons } from '../shared/shared.const';
@ -23,7 +22,7 @@ import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation
import { ConfirmationMessage } from '../confirmation-dialog/confirmation-message'; import { ConfirmationMessage } from '../confirmation-dialog/confirmation-message';
import { ConfirmationAcknowledgement } from '../confirmation-dialog/confirmation-state-message'; import { ConfirmationAcknowledgement } from '../confirmation-dialog/confirmation-state-message';
import { SystemInfo, Tag } from '../service/interface'; import { Tag } from '../service/interface';
import { TAG_TEMPLATE } from './tag.component.html'; import { TAG_TEMPLATE } from './tag.component.html';
import { TAG_STYLE } from './tag.component.css'; import { TAG_STYLE } from './tag.component.css';
@ -37,7 +36,7 @@ import { State, Comparator } from 'clarity-angular';
@Component({ @Component({
selector: 'hbr-tag', selector: 'hbr-tag',
template: TAG_TEMPLATE, template: TAG_TEMPLATE,
styles: [ TAG_STYLE ], styles: [TAG_STYLE],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class TagComponent implements OnInit { export class TagComponent implements OnInit {
@ -48,13 +47,13 @@ export class TagComponent implements OnInit {
@Input() hasSignedIn: boolean; @Input() hasSignedIn: boolean;
@Input() hasProjectAdminRole: boolean; @Input() hasProjectAdminRole: boolean;
@Input() registryUrl: string;
@Input() withNotary: boolean;
@Output() refreshRepo = new EventEmitter<boolean>(); @Output() refreshRepo = new EventEmitter<boolean>();
tags: Tag[]; tags: Tag[];
registryUrl: string;
withNotary: boolean;
showTagManifestOpened: boolean; showTagManifestOpened: boolean;
manifestInfoTitle: string; manifestInfoTitle: string;
@ -71,10 +70,9 @@ export class TagComponent implements OnInit {
constructor( constructor(
private errorHandler: ErrorHandler, private errorHandler: ErrorHandler,
private systemInfoService: SystemInfoService,
private tagService: TagService, private tagService: TagService,
private translateService: TranslateService, private translateService: TranslateService,
private ref: ChangeDetectorRef){} private ref: ChangeDetectorRef) { }
confirmDeletion(message: ConfirmationAcknowledgement) { confirmDeletion(message: ConfirmationAcknowledgement) {
if (message && if (message &&
@ -91,7 +89,7 @@ export class TagComponent implements OnInit {
response => { response => {
this.retrieve(); this.retrieve();
this.translateService.get('REPOSITORY.DELETED_TAG_SUCCESS') this.translateService.get('REPOSITORY.DELETED_TAG_SUCCESS')
.subscribe(res=>this.errorHandler.info(res)); .subscribe(res => this.errorHandler.info(res));
}).catch(error => this.errorHandler.error(error)); }).catch(error => this.errorHandler.error(error));
} }
} }
@ -99,22 +97,15 @@ export class TagComponent implements OnInit {
} }
ngOnInit() { ngOnInit() {
if(!this.projectId) { if (!this.projectId) {
this.errorHandler.error('Project ID cannot be unset.'); this.errorHandler.error('Project ID cannot be unset.');
return; return;
} }
if(!this.repoName) { if (!this.repoName) {
this.errorHandler.error('Repo name cannot be unset.'); this.errorHandler.error('Repo name cannot be unset.');
return; return;
} }
toPromise<SystemInfo>(this.systemInfoService.getSystemInfo())
.then(systemInfo=>{
if(systemInfo) {
this.registryUrl = systemInfo.registry_url || '';
this.withNotary = systemInfo.with_notary || false;
}
},
error=> this.errorHandler.error(error));
this.retrieve(); this.retrieve();
} }
@ -126,7 +117,7 @@ export class TagComponent implements OnInit {
.then(items => { .then(items => {
this.tags = items; this.tags = items;
this.loading = false; this.loading = false;
if(this.tags && this.tags.length === 0) { if (this.tags && this.tags.length === 0) {
this.refreshRepo.emit(true); this.refreshRepo.emit(true);
} }
}) })
@ -134,8 +125,8 @@ export class TagComponent implements OnInit {
this.errorHandler.error(error); this.errorHandler.error(error);
this.loading = false; this.loading = false;
}); });
let hnd = setInterval(()=>this.ref.markForCheck(), 100); let hnd = setInterval(() => this.ref.markForCheck(), 100);
setTimeout(()=>clearInterval(hnd), 1000); setTimeout(() => clearInterval(hnd), 1000);
} }
deleteTag(tag: Tag) { deleteTag(tag: Tag) {
@ -164,7 +155,7 @@ export class TagComponent implements OnInit {
} }
showDigestId(tag: Tag) { showDigestId(tag: Tag) {
if(tag) { if (tag) {
this.manifestInfoTitle = 'REPOSITORY.COPY_DIGEST_ID'; this.manifestInfoTitle = 'REPOSITORY.COPY_DIGEST_ID';
this.digestId = tag.digest; this.digestId = tag.digest;
this.showTagManifestOpened = true; this.showTagManifestOpened = true;