diff --git a/src/portal/src/lib/components/repository-gridview/repository-gridview.component.html b/src/portal/src/lib/components/repository-gridview/repository-gridview.component.html
index 9d7ab2029..ce0e0b717 100644
--- a/src/portal/src/lib/components/repository-gridview/repository-gridview.component.html
+++ b/src/portal/src/lib/components/repository-gridview/repository-gridview.component.html
@@ -8,7 +8,7 @@
{{'CONFIG.REGISTRY_CERTIFICATE' | translate | uppercase}}
-
+
@@ -23,7 +23,7 @@
-
+
@@ -39,7 +39,7 @@
{{r.pull_count}}
- {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPOSITORY.OF' | translate}}
+ {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPOSITORY.OF' | translate}}
{{pagination.totalItems}} {{'REPOSITORY.ITEMS' | translate}}
diff --git a/src/portal/src/lib/components/repository-gridview/repository-gridview.component.spec.ts b/src/portal/src/lib/components/repository-gridview/repository-gridview.component.spec.ts
index e4bba3b39..83642abfc 100644
--- a/src/portal/src/lib/components/repository-gridview/repository-gridview.component.spec.ts
+++ b/src/portal/src/lib/components/repository-gridview/repository-gridview.component.spec.ts
@@ -1,40 +1,31 @@
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
-
import { RouterTestingModule } from '@angular/router/testing';
-
import { SharedModule } from '../../utils/shared/shared.module';
-import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component';
-import { ImageNameInputComponent } from "../image-name-input/image-name-input.component";
import { RepositoryGridviewComponent } from './repository-gridview.component';
-import { TagComponent } from '../tag/tag.component';
-import { FilterComponent } from '../filter/filter.component';
-
import { ErrorHandler } from '../../utils/error-handler/error-handler';
import { Repository, RepositoryItem, SystemInfo } from '../../services/interface';
import { SERVICE_CONFIG, IServiceConfig } from '../../entities/service.config';
-import { RepositoryService, RepositoryDefaultService } from '../../services/repository.service';
+import { RepositoryService } from '../../services/repository.service';
import { TagService, TagDefaultService } from '../../services/tag.service';
-import { SystemInfoService, SystemInfoDefaultService } from '../../services/system-info.service';
-import { LabelPieceComponent } from "../label-piece/label-piece.component";
+import { SystemInfoService } from '../../services/system-info.service';
import { OperationService } from "../operation/operation.service";
-import { ProjectDefaultService, ProjectService, RetagDefaultService, RetagService } from "../../services";
-import { UserPermissionService, UserPermissionDefaultService } from "../../services/permission.service";
-import { USERSTATICPERMISSION } from "../../services/permission-static";
+import {
+ ProjectDefaultService,
+ ProjectService,
+ RequestQueryParams,
+ RetagDefaultService,
+ RetagService
+} from "../../services";
+import { UserPermissionService } from "../../services/permission.service";
import { of } from "rxjs";
import { HarborLibraryModule } from "../../harbor-library.module";
+import { delay } from 'rxjs/operators';
describe('RepositoryComponentGridview (inline template)', () => {
let compRepo: RepositoryGridviewComponent;
let fixtureRepo: ComponentFixture;
- let repositoryService: RepositoryService;
- let systemInfoService: SystemInfoService;
- let userPermissionService: UserPermissionService;
-
- let spyRepos: jasmine.Spy;
- let spySystemInfo: jasmine.Spy;
-
let mockSystemInfo: SystemInfo = {
"with_notary": true,
"with_admiral": false,
@@ -46,7 +37,6 @@ describe('RepositoryComponentGridview (inline template)', () => {
"has_ca_root": false,
"harbor_version": "v1.1.1-rc1-160-g565110d"
};
-
let mockRepoData: RepositoryItem[] = [
{
"id": 1,
@@ -87,28 +77,34 @@ describe('RepositoryComponentGridview (inline template)', () => {
metadata: { xTotalCount: 2 },
data: mockRepoNginxData
};
- let mockHasCreateRepositoryPermission: boolean = true;
- let mockHasDeleteRepositoryPermission: boolean = true;
- // let mockTagData: Tag[] = [
- // {
- // "digest": "sha256:e5c82328a509aeb7c18c1d7fb36633dc638fcf433f651bdcda59c1cc04d3ee55",
- // "name": "1.11.5",
- // "size": "2049",
- // "architecture": "amd64",
- // "os": "linux",
- // "docker_version": "1.12.3",
- // "author": "NGINX Docker Maintainers \"docker-maint@nginx.com\"",
- // "created": new Date("2016-11-08T22:41:15.912313785Z"),
- // "signature": null,
- // "labels": []
- // }
- // ];
-
let config: IServiceConfig = {
repositoryBaseEndpoint: '/api/repository/testing',
systemInfoEndpoint: '/api/systeminfo/testing',
targetBaseEndpoint: '/api/tag/testing'
};
+ const fakedErrorHandler = {
+ error() {
+ return undefined;
+ }
+ };
+ const fakedSystemInfoService = {
+ getSystemInfo() {
+ return of(mockSystemInfo);
+ }
+ };
+ const fakedRepositoryService = {
+ getRepositories(projectId: number, name: string, param?: RequestQueryParams) {
+ if (name === 'nginx') {
+ return of(mockNginxRepo);
+ }
+ return of(mockRepo).pipe(delay(0));
+ }
+ };
+ const fakedUserPermissionService = {
+ getPermission() {
+ return of(true);
+ }
+ };
beforeEach(async(() => {
TestBed.configureTestingModule({
@@ -118,15 +114,15 @@ describe('RepositoryComponentGridview (inline template)', () => {
HarborLibraryModule
],
providers: [
- ErrorHandler,
+ { provide: ErrorHandler, useValue: fakedErrorHandler },
{ provide: SERVICE_CONFIG, useValue: config },
- { provide: RepositoryService, useClass: RepositoryDefaultService },
+ { provide: RepositoryService, useValue: fakedRepositoryService },
{ provide: TagService, useClass: TagDefaultService },
{ provide: ProjectService, useClass: ProjectDefaultService },
{ provide: RetagService, useClass: RetagDefaultService },
- { provide: SystemInfoService, useClass: SystemInfoDefaultService },
- { provide: UserPermissionService, useClass: UserPermissionDefaultService },
- { provide: OperationService }
+ { provide: SystemInfoService, useValue: fakedSystemInfoService },
+ { provide: UserPermissionService, useValue: fakedUserPermissionService },
+ { provide: OperationService },
]
});
}));
@@ -137,34 +133,14 @@ describe('RepositoryComponentGridview (inline template)', () => {
compRepo.projectId = 1;
compRepo.mode = '';
compRepo.hasProjectAdminRole = true;
-
- repositoryService = fixtureRepo.debugElement.injector.get(RepositoryService);
- systemInfoService = fixtureRepo.debugElement.injector.get(SystemInfoService);
-
- spySystemInfo = spyOn(systemInfoService, 'getSystemInfo').and.returnValues(of(mockSystemInfo));
- spyRepos = spyOn(repositoryService, 'getRepositories')
- .and.callFake(function (projectId: number, name: string) {
- if (name === 'nginx') {
- return of(mockNginxRepo);
- }
- return of(mockRepo);
- });
- userPermissionService = fixtureRepo.debugElement.injector.get(UserPermissionService);
- spyOn(userPermissionService, "getPermission")
- .withArgs(compRepo.projectId,
- USERSTATICPERMISSION.REPOSITORY.KEY, USERSTATICPERMISSION.REPOSITORY.VALUE.CREATE)
- .and.returnValue(of(mockHasCreateRepositoryPermission))
- .withArgs(compRepo.projectId, USERSTATICPERMISSION.REPOSITORY.KEY, USERSTATICPERMISSION.REPOSITORY.VALUE.DELETE)
- .and.returnValue(of(mockHasDeleteRepositoryPermission));
+ compRepo.isCardView = false;
fixtureRepo.detectChanges();
});
let originalTimeout;
-
beforeEach(function () {
originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;
jasmine.DEFAULT_TIMEOUT_INTERVAL = 100000;
});
-
afterEach(function () {
jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout;
});
@@ -183,23 +159,19 @@ describe('RepositoryComponentGridview (inline template)', () => {
expect(elRepo.textContent).toEqual('library/busybox');
});
}));
- // Will fail after upgrade to angular 6. todo: need to fix it.
- it('should filter data by keyword', async(() => {
- fixtureRepo.whenStable().then(() => {
- fixtureRepo.detectChanges();
- compRepo.doSearchRepoNames('nginx');
- fixtureRepo.whenStable().then(() => {
-
- fixtureRepo.detectChanges();
- let de: DebugElement[] = fixtureRepo.debugElement.queryAll(By.css('.datagrid-cell'));
- expect(de).toBeTruthy();
- expect(compRepo.repositories.length).toEqual(1);
- expect(de.length).toEqual(1);
- let el: HTMLElement = de[0].nativeElement;
- expect(el).toBeTruthy();
- expect(el.textContent).toEqual('library/nginx');
- });
- });
- }));
+ it('should filter data by keyword', async () => {
+ fixtureRepo.detectChanges();
+ await fixtureRepo.whenStable();
+ compRepo.doSearchRepoNames('nginx');
+ fixtureRepo.detectChanges();
+ await fixtureRepo.whenStable();
+ let de: DebugElement[] = fixtureRepo.debugElement.queryAll(By.css('.datagrid-cell'));
+ expect(de).toBeTruthy();
+ expect(compRepo.repositories.length).toEqual(1);
+ expect(de.length).toEqual(4);
+ let el: HTMLElement = de[1].nativeElement;
+ expect(el).toBeTruthy();
+ expect(el.textContent).toEqual('library/nginx');
+ });
});
diff --git a/src/portal/src/lib/components/repository-gridview/repository-gridview.component.ts b/src/portal/src/lib/components/repository-gridview/repository-gridview.component.ts
index 612b47876..f861af776 100644
--- a/src/portal/src/lib/components/repository-gridview/repository-gridview.component.ts
+++ b/src/portal/src/lib/components/repository-gridview/repository-gridview.component.ts
@@ -9,14 +9,12 @@ import {
EventEmitter,
OnChanges,
SimpleChanges,
- Inject
+ Inject, OnDestroy
} from "@angular/core";
import { Router } from "@angular/router";
-import { forkJoin } from "rxjs";
-import { finalize } from "rxjs/operators";
+import { forkJoin, Subscription } from "rxjs";
+import { debounceTime, distinctUntilChanged, finalize, switchMap } from "rxjs/operators";
import { TranslateService } from "@ngx-translate/core";
-import { Comparator, State } from "../../services/interface";
-
import {
Repository,
SystemInfo,
@@ -26,7 +24,7 @@ import {
RepositoryItem,
TagService
} from '../../services';
-import { ErrorHandler } from '../../utils/error-handler/error-handler';
+import { ErrorHandler } from '../../utils/error-handler';
import { DEFAULT_PAGE_SIZE, calculatePage, clone } from '../../utils/utils';
import { ConfirmationState, ConfirmationTargets, ConfirmationButtons } from '../../entities/shared.const';
import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component';
@@ -43,13 +41,13 @@ import { map, catchError } from "rxjs/operators";
import { Observable, throwError as observableThrowError } from "rxjs";
import { errorHandler as errorHandFn } from "../../utils/shared/shared.utils";
import { ClrDatagridStateInterface } from "@clr/angular";
+import { FilterComponent } from "../filter/filter.component";
@Component({
selector: "hbr-repository-gridview",
templateUrl: "./repository-gridview.component.html",
styleUrls: ["./repository-gridview.component.scss"],
- changeDetection: ChangeDetectionStrategy.OnPush
})
-export class RepositoryGridviewComponent implements OnChanges, OnInit {
+export class RepositoryGridviewComponent implements OnChanges, OnInit, OnDestroy {
signedCon: { [key: string]: any | string[] } = {};
downloadLink: string;
@Input() projectId: number;
@@ -76,7 +74,7 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit {
pageSize: number = DEFAULT_PAGE_SIZE;
currentPage = 1;
totalCount = 0;
- currentState: State;
+ currentState: ClrDatagridStateInterface;
@ViewChild("confirmationDialog", {static: false})
confirmationDialog: ConfirmationDialogComponent;
@@ -84,6 +82,9 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit {
@ViewChild("gridView", {static: false}) gridView: GridViewComponent;
hasCreateRepositoryPermission: boolean;
hasDeleteRepositoryPermission: boolean;
+ @ViewChild(FilterComponent, {static: true})
+ filterComponent: FilterComponent;
+ searchSub: Subscription;
constructor(@Inject(SERVICE_CONFIG) private configInfo: IServiceConfig,
private errorHandler: ErrorHandler,
private translateService: TranslateService,
@@ -92,8 +93,7 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit {
private tagService: TagService,
private operationService: OperationService,
public userPermissionService: UserPermissionService,
- private ref: ChangeDetectorRef,
- private router: Router) {
+ ) {
if (this.configInfo && this.configInfo.systemInfoEndpoint) {
this.downloadLink = this.configInfo.systemInfoEndpoint + "/getcert";
}
@@ -129,14 +129,40 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit {
.subscribe(systemInfo => (this.systemInfo = systemInfo)
, error => this.errorHandler.error(error));
- if (this.mode === "admiral") {
- this.isCardView = true;
- } else {
- this.isCardView = false;
- }
+ this.isCardView = this.mode === "admiral";
this.lastFilteredRepoName = "";
this.getHelmChartVersionPermission(this.projectId);
+ if (!this.searchSub) {
+ this.searchSub = this.filterComponent.filterTerms.pipe(
+ debounceTime(500),
+ distinctUntilChanged(),
+ switchMap(repoName => {
+ this.lastFilteredRepoName = repoName;
+ this.currentPage = 1;
+ // Pagination
+ let params: RequestQueryParams = new RequestQueryParams()
+ .set("page", "" + this.currentPage).set("page_size", "" + this.pageSize);
+ this.loading = true;
+ return this.repositoryService.getRepositories(this.projectId, this.lastFilteredRepoName, params);
+ })
+ ).subscribe((repo: Repository) => {
+ this.totalCount = repo.metadata.xTotalCount;
+ this.repositories = repo.data;
+ this.signedCon = {};
+ this.loading = false;
+ }, error => {
+ this.loading = false;
+ this.errorHandler.error(error);
+ });
+ }
+ }
+
+ ngOnDestroy() {
+ if (this.searchSub) {
+ this.searchSub.unsubscribe();
+ this.searchSub = null;
+ }
}
confirmDeletion(message: ConfirmationAcknowledgement) {
@@ -160,7 +186,7 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit {
forkJoin(observableLists).subscribe((item) => {
this.selectedRow = [];
this.refresh();
- let st: State = this.getStateAfterDeletion();
+ let st: ClrDatagridStateInterface = this.getStateAfterDeletion();
if (!st) {
this.refresh();
} else {
@@ -214,7 +240,7 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit {
doSearchRepoNames(repoName: string) {
this.lastFilteredRepoName = repoName;
this.currentPage = 1;
- let st: State = this.currentState;
+ let st: ClrDatagridStateInterface = this.currentState;
if (!st || !st.page) {
st = { page: {} };
}
@@ -264,11 +290,7 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit {
{
repoName: repoName,
signedImages: signature,
- }).pipe(finalize(() => {
- let hnd = setInterval(() => this.ref.markForCheck(), 100);
- setTimeout(() => clearInterval(hnd), 5000);
- }))
- .subscribe((res: string) => {
+ }).subscribe((res: string) => {
summaryKey = res;
let message = new ConfirmationMessage(
summaryTitle,
@@ -324,12 +346,6 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit {
evt.stopPropagation();
this.deleteRepos([item]);
}
-
- selectedChange(): void {
- let hnd = setInterval(() => this.ref.markForCheck(), 100);
- setTimeout(() => clearInterval(hnd), 2000);
- }
-
refresh() {
this.doSearchRepoNames("");
}
@@ -356,8 +372,6 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit {
this.loading = false;
this.errorHandler.error(error);
});
- let hnd = setInterval(() => this.ref.markForCheck(), 500);
- setTimeout(() => clearInterval(hnd), 5000);
}
clrLoad(state: ClrDatagridStateInterface): void {
@@ -401,13 +415,9 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit {
this.loading = false;
this.errorHandler.error(error);
});
-
- // Force refresh view
- let hnd = setInterval(() => this.ref.markForCheck(), 100);
- setTimeout(() => clearInterval(hnd), 5000);
}
- getStateAfterDeletion(): State {
+ getStateAfterDeletion(): ClrDatagridStateInterface {
let total: number = this.totalCount - 1;
if (total <= 0) {
return null;
@@ -420,7 +430,7 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit {
targetPageNumber = totalPages; // Should == currentPage -1
}
- let st: State = this.currentState;
+ let st: ClrDatagridStateInterface = this.currentState;
if (!st) {
st = { page: {} };
}