diff --git a/src/portal/src/app/base/harbor-shell/harbor-shell.component.spec.ts b/src/portal/src/app/base/harbor-shell/harbor-shell.component.spec.ts index d47e2881e..3828294af 100644 --- a/src/portal/src/app/base/harbor-shell/harbor-shell.component.spec.ts +++ b/src/portal/src/app/base/harbor-shell/harbor-shell.component.spec.ts @@ -8,7 +8,6 @@ import { SearchTriggerService } from '../../shared/components/global-search/sear import { HarborShellComponent } from './harbor-shell.component'; import { ClarityModule } from "@clr/angular"; import { of } from 'rxjs'; -import { ConfigScannerService } from "../left-side-nav/interrogation-services/scanner/config-scanner.service"; import { modalEvents } from '../modal-events.const'; import { PasswordSettingComponent } from '../password-setting/password-setting.component'; import { AboutDialogComponent } from '../../shared/components/about-dialog/about-dialog.component'; @@ -21,6 +20,10 @@ import { ErrorHandler } from '../../shared/units/error-handler'; import { AccountSettingsModalComponent } from "../account-settings/account-settings-modal.component"; import { InlineAlertComponent } from "../../shared/components/inline-alert/inline-alert.component"; import { AccountSettingsModalService } from "../account-settings/account-settings-modal-service.service"; +import { ScannerService } from "../../../../ng-swagger-gen/services/scanner.service"; +import { HttpHeaders, HttpResponse } from "@angular/common/http"; +import { Registry } from "../../../../ng-swagger-gen/models/registry"; +import { delay } from "rxjs/operators"; describe('HarborShellComponent', () => { let component: HarborShellComponent; @@ -71,9 +74,16 @@ describe('HarborShellComponent', () => { }; } }; - let fakeConfigScannerService = { - getScanners() { - return of(true); + let fakeScannerService = { + listScannersResponse() { + const response: HttpResponse> = new HttpResponse>({ + headers: new HttpHeaders({'x-total-count': [].length.toString()}), + body: [] + }); + return of(response).pipe(delay(0)); + }, + listScanners() { + return of([]).pipe(delay(0)); } }; beforeEach(waitForAsync(() => { @@ -92,7 +102,7 @@ describe('HarborShellComponent', () => { { provide: SessionService, useValue: fakeSessionService }, { provide: SearchTriggerService, useValue: fakeSearchTriggerService }, { provide: AppConfigService, useValue: fakeAppConfigService }, - { provide: ConfigScannerService, useValue: fakeConfigScannerService }, + { provide: ScannerService, useValue: fakeScannerService }, { provide: MessageHandlerService, useValue: mockMessageHandlerService }, { provide: AccountSettingsModalService, useValue: mockAccountSettingsModalService }, { provide: PasswordSettingService, useValue: mockPasswordSettingService }, diff --git a/src/portal/src/app/base/harbor-shell/harbor-shell.component.ts b/src/portal/src/app/base/harbor-shell/harbor-shell.component.ts index 736a0a02c..150aafb6d 100644 --- a/src/portal/src/app/base/harbor-shell/harbor-shell.component.ts +++ b/src/portal/src/app/base/harbor-shell/harbor-shell.component.ts @@ -13,7 +13,7 @@ // limitations under the License. import { Component, OnInit, ViewChild, OnDestroy, ElementRef, ChangeDetectorRef } from '@angular/core'; import { Router, ActivatedRoute } from '@angular/router'; -import { Subscription } from "rxjs"; +import { forkJoin, Observable, Subscription } from "rxjs"; import { AppConfigService } from '../../services/app-config.service'; import { ModalEvent } from '../modal-event'; import { modalEvents } from '../modal-events.const'; @@ -23,12 +23,14 @@ import { SessionService } from '../../shared/services/session.service'; import { AboutDialogComponent } from '../../shared/components/about-dialog/about-dialog.component'; import { SearchTriggerService } from '../../shared/components/global-search/search-trigger.service'; import { CommonRoutes } from "../../shared/entities/shared.const"; -import { ConfigScannerService, SCANNERS_DOC } from "../left-side-nav/interrogation-services/scanner/config-scanner.service"; import { THEME_ARRAY, ThemeInterface } from "../../services/theme"; -import { clone } from "../../shared/units/utils"; +import { clone, DEFAULT_PAGE_SIZE } from "../../shared/units/utils"; import { ThemeService } from "../../services/theme.service"; import { AccountSettingsModalComponent } from "../account-settings/account-settings-modal.component"; import { EventService, HarborEvent } from "../../services/event-service/event.service"; +import { SCANNERS_DOC } from "../left-side-nav/interrogation-services/scanner/scanner"; +import { ScannerService } from "../../../../ng-swagger-gen/services/scanner.service"; +import { Project } from "../../../../ng-swagger-gen/models/project"; const HAS_SHOWED_SCANNER_INFO: string = 'hasShowScannerInfo'; const YES: string = 'yes'; @@ -76,7 +78,7 @@ export class HarborShellComponent implements OnInit, OnDestroy { private session: SessionService, private searchTrigger: SearchTriggerService, private appConfigService: AppConfigService, - private scannerService: ConfigScannerService, + private scannerService: ScannerService, public theme: ThemeService, private event: EventService, private cd: ChangeDetectorRef @@ -108,7 +110,9 @@ export class HarborShellComponent implements OnInit, OnDestroy { this.isSearchResultsOpened = false; }); if (!(localStorage && localStorage.getItem(HAS_SHOWED_SCANNER_INFO) === YES)) { - this.getDefaultScanner(); + if (this.isSystemAdmin) { + this.getDefaultScanner(); + } } // set local in app if (localStorage) { @@ -131,11 +135,37 @@ export class HarborShellComponent implements OnInit, OnDestroy { } getDefaultScanner() { - this.scannerService.getScanners() - .subscribe(scanners => { - if (scanners && scanners.length) { - this.showScannerInfo = scanners.some(scanner => scanner.is_default); - } + this.scannerService.listScannersResponse({ + pageSize: DEFAULT_PAGE_SIZE, + page: 1 + }).subscribe(res => { + if (res.headers) { + const xHeader: string = res.headers.get("X-Total-Count"); + const totalCount = parseInt(xHeader, 0); + let arr = res.body || []; + if (totalCount <= DEFAULT_PAGE_SIZE) { // already gotten all scanners + if (arr && arr.length) { + this.showScannerInfo = arr.some(scanner => scanner.is_default); + } + } else { // get all the scanners in specified times + const times: number = Math.ceil(totalCount / DEFAULT_PAGE_SIZE); + const observableList: Observable[] = []; + for (let i = 2; i <= times; i++) { + observableList.push(this.scannerService.listScanners({ + page: i, + pageSize: DEFAULT_PAGE_SIZE + })); + } + forkJoin(observableList).subscribe(response => { + if (response && response.length) { + response.forEach(item => { + arr = arr.concat(item); + }); + this.showScannerInfo = arr.some(scanner => scanner.is_default); + } + }); + } + } }); } ngOnDestroy(): void { diff --git a/src/portal/src/app/base/left-side-nav/interrogation-services/interrogation-services.module.ts b/src/portal/src/app/base/left-side-nav/interrogation-services/interrogation-services.module.ts index 254e6d335..c335259a5 100644 --- a/src/portal/src/app/base/left-side-nav/interrogation-services/interrogation-services.module.ts +++ b/src/portal/src/app/base/left-side-nav/interrogation-services/interrogation-services.module.ts @@ -17,7 +17,6 @@ import { SharedModule } from "../../../shared/shared.module"; import { NewScannerModalComponent } from "./scanner/new-scanner-modal/new-scanner-modal.component"; import { ScannerMetadataComponent } from "./scanner/scanner-metadata/scanner-metadata.component"; import { NewScannerFormComponent } from "./scanner/new-scanner-form/new-scanner-form.component"; -import { ConfigScannerService } from "./scanner/config-scanner.service"; import { RouterModule, Routes } from "@angular/router"; import { ConfigurationScannerComponent } from "./scanner/config-scanner.component"; import { VulnerabilityConfigComponent } from "./vulnerability/vulnerability-config.component"; @@ -61,7 +60,6 @@ const routes: Routes = [ VulnerabilityConfigComponent ], providers: [ - ConfigScannerService, ScanAllRepoService, {provide: ScanApiRepository, useClass: ScanApiDefaultRepository }, ] diff --git a/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/config-scanner.component.html b/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/config-scanner.component.html index a69845672..2d8c35ce1 100644 --- a/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/config-scanner.component.html +++ b/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/config-scanner.component.html @@ -8,7 +8,7 @@ - +
@@ -52,7 +52,7 @@
- +
@@ -67,7 +67,7 @@ {{'SCANNER.NO_SCANNER' | translate}} - + {{scanner.name}} {{'SCANNER.DEFAULT' | translate}} @@ -96,9 +96,10 @@ - + {{"PAGINATION.PAGE_SIZE" | translate}} - 1 - {{scanners?.length}} {{'WEBHOOK.OF' | translate}} {{scanners?.length}} {{'WEBHOOK.ITEMS' | translate}} + {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'DESTINATION.OF' | translate}} + {{total}} {{'DESTINATION.ITEMS' | translate}} diff --git a/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/config-scanner.component.spec.ts b/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/config-scanner.component.spec.ts index b28e225dc..689a6813f 100644 --- a/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/config-scanner.component.spec.ts +++ b/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/config-scanner.component.spec.ts @@ -1,17 +1,15 @@ import { ComponentFixture, ComponentFixtureAutoDetect, TestBed } from '@angular/core/testing'; -import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; -import { ClarityModule } from "@clr/angular"; import { of } from "rxjs"; +import { delay } from "rxjs/operators"; import { ConfigurationScannerComponent } from "./config-scanner.component"; -import { ConfigScannerService } from "./config-scanner.service"; -import { MessageHandlerService } from "../../../../shared/services/message-handler.service"; import { SharedTestingModule } from "../../../../shared/shared.module"; import { ScannerMetadataComponent } from "./scanner-metadata/scanner-metadata.component"; import { NewScannerModalComponent } from "./new-scanner-modal/new-scanner-modal.component"; import { NewScannerFormComponent } from "./new-scanner-form/new-scanner-form.component"; -import { TranslateService } from "@ngx-translate/core"; -import { ErrorHandler } from "../../../../shared/units/error-handler"; -import { ConfirmationDialogService } from "../../../global-confirmation-dialog/confirmation-dialog.service"; +import { ScannerService } from "../../../../../../ng-swagger-gen/services/scanner.service"; +import { HttpHeaders, HttpResponse } from "@angular/common/http"; +import { Registry } from "../../../../../../ng-swagger-gen/models/registry"; +import { ClrLoadingState } from "@clr/angular"; describe('ConfigurationScannerComponent', () => { let mockScannerMetadata = { @@ -35,10 +33,14 @@ describe('ConfigurationScannerComponent', () => { let fixture: ComponentFixture; let fakedConfigScannerService = { getScannerMetadata() { - return of(mockScannerMetadata); + return of(mockScannerMetadata).pipe(delay(10)); }, - getScanners() { - return of([mockScanner1]); + listScannersResponse() { + const response: HttpResponse> = new HttpResponse>({ + headers: new HttpHeaders({'x-total-count': [mockScanner1].length.toString()}), + body: [mockScanner1] + }); + return of(response).pipe(delay(10)); }, updateScanner() { return of(true); @@ -48,8 +50,6 @@ describe('ConfigurationScannerComponent', () => { TestBed.configureTestingModule({ imports: [ SharedTestingModule, - BrowserAnimationsModule, - ClarityModule, ], declarations: [ ConfigurationScannerComponent, @@ -58,11 +58,7 @@ describe('ConfigurationScannerComponent', () => { NewScannerFormComponent ], providers: [ - ErrorHandler, - MessageHandlerService, - ConfirmationDialogService, - TranslateService, - { provide: ConfigScannerService, useValue: fakedConfigScannerService }, + { provide: ScannerService, useValue: fakedConfigScannerService }, // open auto detect { provide: ComponentFixtureAutoDetect, useValue: true } ] @@ -71,9 +67,11 @@ describe('ConfigurationScannerComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(ConfigurationScannerComponent); component = fixture.componentInstance; + component.newScannerDialog.saveBtnState = ClrLoadingState.LOADING; fixture.detectChanges(); }); - it('should create', () => { + it('should create', async () => { + await fixture.whenStable(); expect(component).toBeTruthy(); expect(component.scanners.length).toBe(1); }); diff --git a/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/config-scanner.component.ts b/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/config-scanner.component.ts index 1311a61e7..004a142ee 100644 --- a/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/config-scanner.component.ts +++ b/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/config-scanner.component.ts @@ -1,14 +1,16 @@ import { Component, ViewChild, OnInit, OnDestroy } from "@angular/core"; -import { Scanner } from "./scanner"; +import { Scanner, SCANNERS_DOC } from "./scanner"; import { NewScannerModalComponent } from "./new-scanner-modal/new-scanner-modal.component"; -import { ConfigScannerService, SCANNERS_DOC } from "./config-scanner.service"; import { finalize } from "rxjs/operators"; import { MessageHandlerService } from "../../../../shared/services/message-handler.service"; import { ErrorHandler } from "../../../../shared/units/error-handler"; -import { clone } from "../../../../shared/units/utils"; +import { clone, DEFAULT_PAGE_SIZE, getSortingString } from "../../../../shared/units/utils"; import { ConfirmationDialogService } from "../../../global-confirmation-dialog/confirmation-dialog.service"; import { ConfirmationButtons, ConfirmationState, ConfirmationTargets } from "../../../../shared/entities/shared.const"; import { ConfirmationMessage } from "../../../global-confirmation-dialog/confirmation-message"; +import { ScannerService } from "../../../../../../ng-swagger-gen/services/scanner.service"; +import { ClrDatagridStateInterface } from "@clr/angular"; +import { ScannerRegistrationReq } from "../../../../../../ng-swagger-gen/models/scanner-registration-req"; @Component({ selector: 'config-scanner', @@ -18,13 +20,17 @@ import { ConfirmationMessage } from "../../../global-confirmation-dialog/confirm export class ConfigurationScannerComponent implements OnInit, OnDestroy { scanners: Scanner[] = []; selectedRow: Scanner; - onGoing: boolean = false; + onGoing: boolean = true; @ViewChild(NewScannerModalComponent) newScannerDialog: NewScannerModalComponent; deletionSubscription: any; scannerDocUrl: string = SCANNERS_DOC; + page: number = 1; + pageSize: number = DEFAULT_PAGE_SIZE; + total: number = 0; + state: ClrDatagridStateInterface; constructor( - private configScannerService: ConfigScannerService, + private configScannerService: ScannerService, private errorHandler: ErrorHandler, private msgHandler: MessageHandlerService, private deletionDialogService: ConfirmationDialogService, @@ -35,17 +41,18 @@ export class ConfigurationScannerComponent implements OnInit, OnDestroy { if (confirmed && confirmed.source === ConfirmationTargets.SCANNER && confirmed.state === ConfirmationState.CONFIRMED) { - this.configScannerService.deleteScanners(confirmed.data) + this.configScannerService.deleteScanner({ + registrationId: confirmed.data[0].uuid + }) .subscribe(response => { this.msgHandler.showSuccess("SCANNER.DELETE_SUCCESS"); - this.getScanners(); + this.refresh(); }, error => { this.errorHandler.error(error); }); } }); } - this.getScanners(); } ngOnDestroy(): void { if (this.deletionSubscription) { @@ -53,13 +60,45 @@ export class ConfigurationScannerComponent implements OnInit, OnDestroy { this.deletionSubscription = null; } } - getScanners() { + refresh() { + this.page = 1; + this.selectedRow = null; + this.total = 0; + this.getScanners(this.state); + } + getScanners(state?: ClrDatagridStateInterface) { + 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); + } else { // sort by creation_time desc by default + sort = `-creation_time`; + } this.onGoing = true; - this.configScannerService.getScanners() + this.configScannerService.listScannersResponse({ + page: this.page, + pageSize: this.pageSize, + q: q, + sort: sort + }) .pipe(finalize(() => this.onGoing = false)) .subscribe(response => { - this.scanners = response; - this.getMetadataForAll(); + // Get total count + if (response.headers) { + let xHeader: string = response.headers.get("X-Total-Count"); + if (xHeader) { + this.total = parseInt(xHeader, 0); + } + } + this.scanners = response.body || []; + this.getMetadataForAll(); }, error => { this.errorHandler.error(error); }); @@ -69,7 +108,9 @@ export class ConfigurationScannerComponent implements OnInit, OnDestroy { this.scanners.forEach((scanner, index) => { if (scanner.uuid ) { this.scanners[index].loadingMetadata = true; - this.configScannerService.getScannerMetadata(scanner.uuid) + this.configScannerService.getScannerMetadata({ + registrationId: scanner.uuid + }) .pipe(finalize(() => this.scanners[index].loadingMetadata = false)) .subscribe(response => { this.scanners[index].metadata = response; @@ -91,12 +132,15 @@ export class ConfigurationScannerComponent implements OnInit, OnDestroy { } changeStat() { if (this.selectedRow) { - let scanner: Scanner = clone(this.selectedRow); + let scanner: ScannerRegistrationReq = clone(this.selectedRow); scanner.disabled = !scanner.disabled; - this.configScannerService.updateScanner(scanner) + this.configScannerService.updateScanner({ + registrationId: this.selectedRow.uuid, + registration: scanner + }) .subscribe(response => { this.msgHandler.showSuccess("SCANNER.UPDATE_SUCCESS"); - this.getScanners(); + this.refresh(); }, error => { this.errorHandler.error(error); }); @@ -104,10 +148,15 @@ export class ConfigurationScannerComponent implements OnInit, OnDestroy { } setAsDefault() { if (this.selectedRow) { - this.configScannerService.setAsDefault(this.selectedRow.uuid) + this.configScannerService.setScannerAsDefault({ + registrationId: this.selectedRow.uuid, + payload: { + is_default: true + } + }) .subscribe(response => { this.msgHandler.showSuccess("SCANNER.UPDATE_SUCCESS"); - this.getScanners(); + this.refresh(); }, error => { this.errorHandler.error(error); }); diff --git a/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/config-scanner.service.spec.ts b/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/config-scanner.service.spec.ts deleted file mode 100644 index 05af33fde..000000000 --- a/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/config-scanner.service.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { TestBed, inject } from '@angular/core/testing'; -import { SharedTestingModule } from "../../../../shared/shared.module"; -import { ConfigScannerService } from "./config-scanner.service"; - -describe('TagService', () => { - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ - SharedTestingModule - ], - providers: [ - ConfigScannerService - ] - }); - }); - - it('should be initialized', inject([ConfigScannerService], (service: ConfigScannerService) => { - expect(service).toBeTruthy(); - })); -}); diff --git a/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/config-scanner.service.ts b/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/config-scanner.service.ts deleted file mode 100644 index bac505dbf..000000000 --- a/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/config-scanner.service.ts +++ /dev/null @@ -1,82 +0,0 @@ -import {Injectable} from "@angular/core"; -import {Scanner} from "./scanner"; -import { forkJoin, Observable, throwError as observableThrowError } from "rxjs"; -import { catchError, map } from "rxjs/operators"; -import { HttpClient } from "@angular/common/http"; -import { ScannerMetadata } from "./scanner-metadata"; -import { CURRENT_BASE_HREF } from "../../../../shared/units/utils"; - -export const SCANNERS_DOC: string = "https://goharbor.io/blog/harbor-1.10-release/#vulnerability-scanning-with-pluggable-scanners"; - -@Injectable({ - providedIn: 'root', -}) -export class ConfigScannerService { - - constructor( private http: HttpClient) {} - getScannersByName(name: string): Observable { - name = encodeURIComponent(name); - return this.http.get(`${ CURRENT_BASE_HREF }/scanners?ex_name=${name}`) - .pipe(catchError(error => observableThrowError(error))) - .pipe(map(response => response as Scanner[])); - } - getScannersByEndpointUrl(endpointUrl: string): Observable { - endpointUrl = encodeURIComponent(endpointUrl); - return this.http.get(`${ CURRENT_BASE_HREF }/scanners?ex_url=${endpointUrl}`) - .pipe(catchError(error => observableThrowError(error))) - .pipe(map(response => response as Scanner[])); - } - testEndpointUrl(testValue: any): Observable { - return this.http.post(`${ CURRENT_BASE_HREF }/scanners/ping`, testValue) - .pipe(catchError(error => observableThrowError(error))); - } - addScanner(scanner: Scanner): Observable { - return this.http.post(CURRENT_BASE_HREF + '/scanners', scanner ) - .pipe(catchError(error => observableThrowError(error))); - } - getScanners(): Observable { - return this.http.get(CURRENT_BASE_HREF + '/scanners') - .pipe(map(response => response as Scanner[])) - .pipe(catchError(error => observableThrowError(error))); - } - updateScanner(scanner: Scanner): Observable { - return this.http.put(`${ CURRENT_BASE_HREF }/scanners/${scanner.uuid}`, scanner ) - .pipe(catchError(error => observableThrowError(error))); - } - deleteScanner(scanner: Scanner): Observable { - return this.http.delete(`${ CURRENT_BASE_HREF }/scanners/${scanner.uuid}`) - .pipe(catchError(error => observableThrowError(error))); - } - deleteScanners(scanners: Scanner[]): Observable { - let observableLists: any[] = []; - if (scanners && scanners.length > 0) { - scanners.forEach(scanner => { - observableLists.push(this.deleteScanner(scanner)); - }); - return forkJoin(...observableLists); - } - } - getProjectScanner(projectId: number): Observable { - return this.http.get(`${ CURRENT_BASE_HREF }/projects/${projectId}/scanner`) - .pipe(map(response => response as Scanner)) - .pipe(catchError(error => observableThrowError(error))); - } - updateProjectScanner(projectId: number , uid: string): Observable { - return this.http.put(`${ CURRENT_BASE_HREF }/projects/${projectId}/scanner` , {uuid: uid}) - .pipe(catchError(error => observableThrowError(error))); - } - getScannerMetadata(uid: string): Observable { - return this.http.get(`${ CURRENT_BASE_HREF }/scanners/${uid}/metadata`) - .pipe(map(response => response as ScannerMetadata)) - .pipe(catchError(error => observableThrowError(error))); - } - setAsDefault(uid: string): Observable { - return this.http.patch(`${ CURRENT_BASE_HREF }/scanners/${uid}`, {is_default: true} ) - .pipe(catchError(error => observableThrowError(error))); - } - getProjectScanners(projectId: number) { - return this.http.get(`${ CURRENT_BASE_HREF }/projects/${projectId}/scanner/candidates`) - .pipe(map(response => response as Scanner[])) - .pipe(catchError(error => observableThrowError(error))); - } -} diff --git a/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/new-scanner-form/new-scanner-form.component.spec.ts b/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/new-scanner-form/new-scanner-form.component.spec.ts index 963785150..db89e7a0b 100644 --- a/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/new-scanner-form/new-scanner-form.component.spec.ts +++ b/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/new-scanner-form/new-scanner-form.component.spec.ts @@ -4,10 +4,10 @@ import { FormBuilder } from "@angular/forms"; import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { ClarityModule } from "@clr/angular"; import { SharedTestingModule } from "../../../../../shared/shared.module"; -import { ConfigScannerService } from "../config-scanner.service"; import { of } from "rxjs"; import { TranslateService } from "@ngx-translate/core"; import { delay } from "rxjs/operators"; +import { ScannerService } from "../../../../../../../ng-swagger-gen/services/scanner.service"; describe('NewScannerFormComponent', () => { let mockScanner1 = { @@ -19,7 +19,7 @@ describe('NewScannerFormComponent', () => { let component: NewScannerFormComponent; let fixture: ComponentFixture; let fakedConfigScannerService = { - getScannersByName() { + listScanners() { return of([mockScanner1]).pipe(delay(500)); }, getScannersByEndpointUrl() { @@ -37,7 +37,7 @@ describe('NewScannerFormComponent', () => { providers: [ FormBuilder, TranslateService, - { provide: ConfigScannerService, useValue: fakedConfigScannerService }, + { provide: ScannerService, useValue: fakedConfigScannerService }, // open auto detect { provide: ComponentFixtureAutoDetect, useValue: true } ] diff --git a/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/new-scanner-form/new-scanner-form.component.ts b/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/new-scanner-form/new-scanner-form.component.ts index e29cd16ba..f75b3d5ed 100644 --- a/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/new-scanner-form/new-scanner-form.component.ts +++ b/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/new-scanner-form/new-scanner-form.component.ts @@ -9,7 +9,7 @@ import { import { FormBuilder, FormGroup, Validators } from "@angular/forms"; import { fromEvent } from "rxjs"; import { debounceTime, distinctUntilChanged, filter, finalize, map, switchMap } from "rxjs/operators"; -import { ConfigScannerService } from "../config-scanner.service"; +import { ScannerService } from "../../../../../../../ng-swagger-gen/services/scanner.service"; @Component({ @@ -48,7 +48,7 @@ export class NewScannerFormComponent implements OnInit, AfterViewInit, OnDestro isEdit: boolean; @ViewChild('name') scannerName: ElementRef; @ViewChild('endpointUrl') scannerEndpointUrl: ElementRef; - constructor(private fb: FormBuilder, private scannerService: ConfigScannerService) { + constructor(private fb: FormBuilder, private scannerService: ScannerService) { } ngAfterViewInit(): void { if (!this.checkNameSubscribe) { @@ -65,7 +65,9 @@ export class NewScannerFormComponent implements OnInit, AfterViewInit, OnDestro switchMap((name) => { this.isNameExisting = false; this.checkOnGoing = true; - return this.scannerService.getScannersByName(name) + return this.scannerService.listScanners({ + q: encodeURIComponent(`name=${name}`) + }) .pipe(finalize(() => this.checkOnGoing = false)); })).subscribe(response => { if (response && response.length > 0) { @@ -94,7 +96,9 @@ export class NewScannerFormComponent implements OnInit, AfterViewInit, OnDestro switchMap((endpointUrl) => { this.isEndpointUrlExisting = false; this.checkEndpointOnGoing = true; - return this.scannerService.getScannersByEndpointUrl(endpointUrl) + return this.scannerService.listScanners({ + q: encodeURIComponent(`url=${endpointUrl}`) + }) .pipe(finalize(() => this.checkEndpointOnGoing = false)); })).subscribe(response => { if (response && response.length > 0) { diff --git a/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/new-scanner-modal/new-scanner-modal.component.spec.ts b/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/new-scanner-modal/new-scanner-modal.component.spec.ts index bf5352e1f..b93e450a2 100644 --- a/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/new-scanner-modal/new-scanner-modal.component.spec.ts +++ b/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/new-scanner-modal/new-scanner-modal.component.spec.ts @@ -1,6 +1,5 @@ import { ComponentFixture, ComponentFixtureAutoDetect, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { ClrLoadingState } from "@clr/angular"; -import { ConfigScannerService } from "../config-scanner.service"; import { NewScannerModalComponent } from "./new-scanner-modal.component"; import { MessageHandlerService } from "../../../../../shared/services/message-handler.service"; import { NewScannerFormComponent } from "../new-scanner-form/new-scanner-form.component"; @@ -9,6 +8,7 @@ import { of, Subscription } from "rxjs"; import { delay } from "rxjs/operators"; import { SharedTestingModule } from "../../../../../shared/shared.module"; import { Scanner } from "../scanner"; +import { ScannerService } from "../../../../../../../ng-swagger-gen/services/scanner.service"; describe('NewScannerModalComponent', () => { let component: NewScannerModalComponent; @@ -21,13 +21,13 @@ describe('NewScannerModalComponent', () => { auth: "", }; let fakedConfigScannerService = { - getScannersByName() { + listScanners() { return of([mockScanner1]); }, - testEndpointUrl() { + pingScanner() { return of(true).pipe(delay(200)); }, - addScanner() { + createScanner() { return of(true).pipe(delay(200)); }, updateScanner() { @@ -45,7 +45,7 @@ describe('NewScannerModalComponent', () => { ], providers: [ MessageHandlerService, - { provide: ConfigScannerService, useValue: fakedConfigScannerService }, + { provide: ScannerService, useValue: fakedConfigScannerService }, FormBuilder, // open auto detect { provide: ComponentFixtureAutoDetect, useValue: true } diff --git a/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/new-scanner-modal/new-scanner-modal.component.ts b/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/new-scanner-modal/new-scanner-modal.component.ts index 287932b7c..81c5478d3 100644 --- a/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/new-scanner-modal/new-scanner-modal.component.ts +++ b/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/new-scanner-modal/new-scanner-modal.component.ts @@ -1,12 +1,14 @@ import { Component, EventEmitter, Output, ViewChild } from '@angular/core'; import { Scanner } from "../scanner"; import { NewScannerFormComponent } from "../new-scanner-form/new-scanner-form.component"; -import { ConfigScannerService } from "../config-scanner.service"; import { ClrLoadingState } from "@clr/angular"; import { finalize } from "rxjs/operators"; import { MessageHandlerService } from "../../../../../shared/services/message-handler.service"; import { TranslateService } from "@ngx-translate/core"; import { InlineAlertComponent } from "../../../../../shared/components/inline-alert/inline-alert.component"; +import { ScannerService } from "../../../../../../../ng-swagger-gen/services/scanner.service"; +import { ScannerRegistrationReq } from "../../../../../../../ng-swagger-gen/models/scanner-registration-req"; +import { clone } from "../../../../../shared/units/utils"; @Component({ selector: "new-scanner-modal", @@ -29,7 +31,7 @@ export class NewScannerModalComponent { editScanner: Scanner; @ViewChild(InlineAlertComponent) inlineAlert: InlineAlertComponent; constructor( - private configScannerService: ConfigScannerService, + private configScannerService: ScannerService, private msgHandler: MessageHandlerService, private translate: TranslateService, ) {} @@ -47,8 +49,8 @@ export class NewScannerModalComponent { create(): void { this.onSaving = true; this.saveBtnState = ClrLoadingState.LOADING; - let scanner: Scanner = new Scanner(); - let value = this.newScannerFormComponent.newScannerForm.value; + const scanner: ScannerRegistrationReq = {name: "", url: ""}; + const value = this.newScannerFormComponent.newScannerForm.value; scanner.name = value.name; scanner.description = value.description; scanner.url = value.url; @@ -66,7 +68,9 @@ export class NewScannerModalComponent { } scanner.skip_certVerify = !!value.skipCertVerify; scanner.use_internal_addr = !!value.useInner; - this.configScannerService.addScanner(scanner) + this.configScannerService.createScanner({ + registration: scanner + }) .pipe(finalize(() => this.onSaving = false)) .subscribe(response => { this.close(); @@ -176,8 +180,8 @@ export class NewScannerModalComponent { onTestEndpoint() { this.onTesting = true; this.checkBtnState = ClrLoadingState.LOADING; - let scanner: Scanner = new Scanner(); - let value = this.newScannerFormComponent.newScannerForm.value; + const scanner: ScannerRegistrationReq = {name: "", url: ""}; + const value = this.newScannerFormComponent.newScannerForm.value; scanner.name = value.name; scanner.description = value.description; scanner.url = value.url; @@ -195,7 +199,9 @@ export class NewScannerModalComponent { } scanner.skip_certVerify = !!value.skipCertVerify; scanner.use_internal_addr = !!value.useInner; - this.configScannerService.testEndpointUrl(scanner) + this.configScannerService.pingScanner({ + settings: scanner + }) .pipe(finalize(() => this.onTesting = false)) .subscribe(response => { this.inlineAlert.showInlineSuccess({ @@ -236,7 +242,11 @@ export class NewScannerModalComponent { this.editScanner.skip_certVerify = !!value.skipCertVerify; this.editScanner.use_internal_addr = !!value.useInner; this.editScanner.uuid = this.uid; - this.configScannerService.updateScanner(this.editScanner) + const scanner: ScannerRegistrationReq = clone(this.editScanner); + this.configScannerService.updateScanner({ + registrationId: this.editScanner.uuid, + registration: scanner + }) .pipe(finalize(() => this.onSaving = false)) .subscribe(response => { this.close(); diff --git a/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/scanner-metadata.ts b/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/scanner-metadata.ts deleted file mode 100644 index 8610b51fb..000000000 --- a/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/scanner-metadata.ts +++ /dev/null @@ -1,16 +0,0 @@ -export class ScannerMetadata { - scanner?: { - name?: string; - vendor?: string; - version?: string; - }; - capabilities?: [{ - consumes_mime_types?: Array; - produces_mime_types?: Array; - }]; - properties?: { - [key: string]: string; - }; - constructor() { - } -} diff --git a/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/scanner-metadata/scanner-metadata.component.spec.ts b/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/scanner-metadata/scanner-metadata.component.spec.ts index cf956bfc3..9bd74a227 100644 --- a/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/scanner-metadata/scanner-metadata.component.spec.ts +++ b/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/scanner-metadata/scanner-metadata.component.spec.ts @@ -2,10 +2,10 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { ClarityModule } from "@clr/angular"; import { SharedTestingModule } from "../../../../../shared/shared.module"; -import { ConfigScannerService } from "../config-scanner.service"; import { of } from "rxjs"; import { ScannerMetadataComponent } from "./scanner-metadata.component"; import { ErrorHandler } from "../../../../../shared/units/error-handler"; +import { ScannerService } from "../../../../../../../ng-swagger-gen/services/scanner.service"; describe('ScannerMetadataComponent', () => { let mockScannerMetadata = { @@ -38,7 +38,7 @@ describe('ScannerMetadataComponent', () => { ], providers: [ ErrorHandler, - { provide: ConfigScannerService, useValue: fakedConfigScannerService }, + { provide: ScannerService, useValue: fakedConfigScannerService }, ] }); }); diff --git a/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/scanner-metadata/scanner-metadata.component.ts b/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/scanner-metadata/scanner-metadata.component.ts index c24385088..dbe45fe60 100644 --- a/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/scanner-metadata/scanner-metadata.component.ts +++ b/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/scanner-metadata/scanner-metadata.component.ts @@ -2,12 +2,12 @@ import { Component, Input, OnInit } from "@angular/core"; -import { ConfigScannerService } from "../config-scanner.service"; import { finalize } from "rxjs/operators"; -import { ScannerMetadata } from "../scanner-metadata"; import { DatePipe } from "@angular/common"; import { ErrorHandler } from "../../../../../shared/units/error-handler"; -import {DATABASE_NEXT_UPDATE_PROPERTY, DATABASE_UPDATED_PROPERTY} from "../../../../../shared/units/utils"; +import { DATABASE_NEXT_UPDATE_PROPERTY, DATABASE_UPDATED_PROPERTY } from "../../../../../shared/units/utils"; +import { ScannerService } from "../../../../../../../ng-swagger-gen/services/scanner.service"; +import { ScannerAdapterMetadata } from "../../../../../../../ng-swagger-gen/models/scanner-adapter-metadata"; @Component({ selector: 'scanner-metadata', @@ -17,13 +17,15 @@ import {DATABASE_NEXT_UPDATE_PROPERTY, DATABASE_UPDATED_PROPERTY} from "../../.. export class ScannerMetadataComponent implements OnInit { @Input() uid: string; loading: boolean = false; - scannerMetadata: ScannerMetadata; - constructor(private configScannerService: ConfigScannerService, + scannerMetadata: ScannerAdapterMetadata; + constructor(private configScannerService: ScannerService, private errorHandler: ErrorHandler) { } ngOnInit(): void { this.loading = true; - this.configScannerService.getScannerMetadata(this.uid) + this.configScannerService.getScannerMetadata({ + registrationId: this.uid + }) .pipe(finalize(() => this.loading = false)) .subscribe(response => { this.scannerMetadata = response; diff --git a/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/scanner.ts b/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/scanner.ts index adbc51f67..86dcc5c6a 100644 --- a/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/scanner.ts +++ b/src/portal/src/app/base/left-side-nav/interrogation-services/scanner/scanner.ts @@ -1,24 +1,9 @@ -import { ScannerMetadata } from "./scanner-metadata"; +import { ScannerRegistration } from "../../../../../../ng-swagger-gen/models/scanner-registration"; +import { ScannerAdapterMetadata } from "../../../../../../ng-swagger-gen/models/scanner-adapter-metadata"; -export class Scanner { - name?: string; - description?: string; - uuid?: string; - url?: string; - auth?: string; - access_credential?: string; - adapter?: string; - disabled?: boolean; - is_default?: boolean; - skip_certVerify?: boolean; - use_internal_addr?: boolean; - create_time?: any; - update_time?: any; - vendor?: string; - version?: string; - metadata?: ScannerMetadata; +export interface Scanner extends ScannerRegistration { + metadata?: ScannerAdapterMetadata; loadingMetadata?: boolean; - health?: string; - constructor() { - } } + +export const SCANNERS_DOC: string = "https://goharbor.io/blog/harbor-1.10-release/#vulnerability-scanning-with-pluggable-scanners"; diff --git a/src/portal/src/app/base/project/scanner/scanner.component.spec.ts b/src/portal/src/app/base/project/scanner/scanner.component.spec.ts index cfda67908..8cccf3f34 100644 --- a/src/portal/src/app/base/project/scanner/scanner.component.spec.ts +++ b/src/portal/src/app/base/project/scanner/scanner.component.spec.ts @@ -3,11 +3,13 @@ import { of } from "rxjs"; import { TranslateService } from "@ngx-translate/core"; import { MessageHandlerService } from "../../../shared/services/message-handler.service"; import { ScannerComponent } from "./scanner.component"; -import { ConfigScannerService } from "../../left-side-nav/interrogation-services/scanner/config-scanner.service"; import { SharedTestingModule } from "../../../shared/shared.module"; import { ActivatedRoute } from "@angular/router"; import { Scanner } from "../../left-side-nav/interrogation-services/scanner/scanner"; import { ErrorHandler } from "../../../shared/units/error-handler"; +import { ProjectService } from "../../../../../ng-swagger-gen/services/project.service"; +import { HttpHeaders, HttpResponse } from "@angular/common/http"; +import { Registry } from "../../../../../ng-swagger-gen/models/registry"; describe('ScannerComponent', () => { const mockScanner1: Scanner = { @@ -28,17 +30,21 @@ describe('ScannerComponent', () => { }; let component: ScannerComponent; let fixture: ComponentFixture; - let fakedConfigScannerService = { - getProjectScanner() { + let fakedProjectService = { + getScannerOfProject() { return of(mockScanner1); }, - getScanners() { + listScannerCandidatesOfProject() { return of([mockScanner1, mockScanner2]); }, - getProjectScanners() { - return of([mockScanner1, mockScanner2]); + listScannerCandidatesOfProjectResponse() { + const response: HttpResponse> = new HttpResponse>({ + headers: new HttpHeaders({'x-total-count': [mockScanner1, mockScanner2].length.toString()}), + body: [mockScanner1, mockScanner2] + }); + return of(response); }, - updateProjectScanner() { + setScannerOfProject() { return of(true); } }; @@ -63,8 +69,8 @@ describe('ScannerComponent', () => { TranslateService, MessageHandlerService, ErrorHandler, - {provide: ActivatedRoute, useValue: fakedRoute}, - { provide: ConfigScannerService, useValue: fakedConfigScannerService }, + { provide: ActivatedRoute, useValue: fakedRoute }, + { provide: ProjectService, useValue: fakedProjectService }, ] }) .compileComponents(); diff --git a/src/portal/src/app/base/project/scanner/scanner.component.ts b/src/portal/src/app/base/project/scanner/scanner.component.ts index b7be97ae9..89bec8d54 100644 --- a/src/portal/src/app/base/project/scanner/scanner.component.ts +++ b/src/portal/src/app/base/project/scanner/scanner.component.ts @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. import { Component, OnInit, ViewChild } from "@angular/core"; -import { ConfigScannerService } from "../../left-side-nav/interrogation-services/scanner/config-scanner.service"; import { Scanner } from "../../left-side-nav/interrogation-services/scanner/scanner"; import { MessageHandlerService } from "../../../shared/services/message-handler.service"; import { ActivatedRoute } from "@angular/router"; @@ -22,6 +21,10 @@ import { TranslateService } from "@ngx-translate/core"; import { ErrorHandler } from "../../../shared/units/error-handler"; import { UserPermissionService, USERSTATICPERMISSION } from "../../../shared/services"; import { InlineAlertComponent } from "../../../shared/components/inline-alert/inline-alert.component"; +import { ProjectService } from "../../../../../ng-swagger-gen/services/project.service"; +import { DEFAULT_PAGE_SIZE } from "../../../shared/units/utils"; +import { forkJoin, Observable } from "rxjs"; +import { Project } from "../../../../../ng-swagger-gen/models/project"; @Component({ @@ -40,12 +43,12 @@ export class ScannerComponent implements OnInit { onSaving: boolean = false; hasCreatePermission: boolean = false; @ViewChild(InlineAlertComponent) inlineAlert: InlineAlertComponent; - constructor( private configScannerService: ConfigScannerService, - private msgHandler: MessageHandlerService, + constructor( private msgHandler: MessageHandlerService, private errorHandler: ErrorHandler, private route: ActivatedRoute, private userPermissionService: UserPermissionService, - private translate: TranslateService + private translate: TranslateService, + private projectService: ProjectService ) { } ngOnInit() { @@ -70,7 +73,9 @@ export class ScannerComponent implements OnInit { } getScanner(isCheckHealth?: boolean) { this.loading = true; - this.configScannerService.getProjectScanner(this.projectId) + this.projectService.getScannerOfProject({ + projectNameOrId: this.projectId.toString() + }) .pipe(finalize(() => this.loading = false)) .subscribe(response => { if (response && "{}" !== JSON.stringify(response)) { @@ -89,14 +94,44 @@ export class ScannerComponent implements OnInit { } getScanners() { if (this.projectId) { - this.configScannerService.getProjectScanners(this.projectId) - .subscribe(response => { - if (response && response.length > 0) { - this.scanners = response.filter(scanner => { - return !scanner.disabled; + this.projectService.listScannerCandidatesOfProjectResponse({ + projectNameOrId: this.projectId.toString(), + page: 1, + pageSize: DEFAULT_PAGE_SIZE + }).subscribe(response => { + if (response.headers) { + const xHeader: string = response.headers.get("X-Total-Count"); + const totalCount = parseInt(xHeader, 0); + let arr = response.body || []; + if (totalCount <= DEFAULT_PAGE_SIZE) { // already gotten all scanners + if (arr && arr.length > 0) { + this.scanners = arr.filter(scanner => { + return !scanner.disabled; + }); + } + } else { // get all the scanners in specified times + const times: number = Math.ceil(totalCount / DEFAULT_PAGE_SIZE); + const observableList: Observable[] = []; + for (let i = 2; i <= times; i++) { + observableList.push(this.projectService.listScannerCandidatesOfProject({ + page: i, + pageSize: DEFAULT_PAGE_SIZE, + projectNameOrId: this.projectId.toString() + })); + } + forkJoin(observableList).subscribe(res => { + if (res && res.length) { + res.forEach(item => { + arr = arr.concat(item); + }); + this.scanners = arr.filter(scanner => { + return !scanner.disabled; + }); + } }); } - }); + } + }); } } close() { @@ -118,8 +153,12 @@ export class ScannerComponent implements OnInit { } save() { this.saveBtnState = ClrLoadingState.LOADING; - this.configScannerService.updateProjectScanner(this.projectId, this.selectedScanner.uuid) - .subscribe(response => { + this.projectService.setScannerOfProject({ + projectNameOrId: this.projectId.toString(), + payload: { + uuid: this.selectedScanner.uuid + } + }).subscribe(response => { this.close(); this.msgHandler.showSuccess('Update Success'); this.getScanner(true); diff --git a/src/portal/src/app/shared/units/utils.spec.ts b/src/portal/src/app/shared/units/utils.spec.ts index 89d2f8252..2309cfb23 100644 --- a/src/portal/src/app/shared/units/utils.spec.ts +++ b/src/portal/src/app/shared/units/utils.spec.ts @@ -1,4 +1,5 @@ -import { delUrlParam, isSameArrayValue, isSameObject } from "./utils"; +import { delUrlParam, getQueryString, getSortingString, isSameArrayValue, isSameObject } from "./utils"; +import { ClrDatagridStateInterface } from "@clr/angular"; describe('functions in utils.ts should work', () => { it('function isSameArrayValue() should work', () => { @@ -30,4 +31,26 @@ describe('functions in utils.ts should work', () => { expect(delUrlParam('http://test.com', 'param2')).toEqual('http://test.com'); expect(delUrlParam('http://test.com?param2', 'param2')).toEqual('http://test.com'); }); + + it('function getSortingString() should work', () => { + expect(getSortingString).toBeTruthy(); + const state: ClrDatagridStateInterface = { + sort: { + by: 'name', + reverse: true + } + }; + expect(getSortingString(state)).toEqual('-name'); + }); + + it('function getQueryString() should work', () => { + expect(getQueryString).toBeTruthy(); + const state: ClrDatagridStateInterface = { + filters: [ + {property: 'name', value: 'test'}, + {property: 'url', value: 'http://test.com'}, + ] + }; + expect(getQueryString(state)).toEqual(encodeURIComponent('name=~test,url=~http://test.com')); + }); }); diff --git a/src/portal/src/app/shared/units/utils.ts b/src/portal/src/app/shared/units/utils.ts index 4e6d5dfb8..6a4015546 100644 --- a/src/portal/src/app/shared/units/utils.ts +++ b/src/portal/src/app/shared/units/utils.ts @@ -631,6 +631,10 @@ export function deleteEmptyKey(obj: Object): void { } } +/** + * Get sorting string from current state + * @param state + */ export function getSortingString(state: ClrDatagridStateInterface): string { if (state && state.sort && state.sort.by) { let sortString: string; @@ -647,6 +651,32 @@ export function getSortingString(state: ClrDatagridStateInterface): string { return null; } + +/** + * Get query string from current state, rules as below: + * query string format: q=k=v,k=~v,k=[min~max],k={v1 v2 v3},k=(v1 v2 v3) + * exact match: k=v + * fuzzy match: k=~v + * range: k=[min~max] + * or list: k={v1 v2 v3} + * and list: k=(v1 v2 v3) + * @param state + */ +export function getQueryString(state: ClrDatagridStateInterface): string { + let str: string = ''; + if (state && state.filters && state.filters.length) { + state.filters.forEach(item => { + if (str) { + str += `,${item.property}=~${item.value}`; + } else { + str += `${item.property}=~${item.value}`; + } + }); + return encodeURIComponent(str); + } + return null; +} + /** * if two object are the same * @param a