This is an automated email from the ASF dual-hosted git repository. hshpak pushed a commit to branch feat/DATALAB-2881/filter-function-to-Images-page in repository https://gitbox.apache.org/repos/asf/incubator-datalab.git
commit 1a671cc15710768a92d8a93df1239640c114a21e Author: Hennadii_Shpak <[email protected]> AuthorDate: Sat Jul 30 10:06:19 2022 +0300 filter list reload implemented --- .../project/project-list/project-list.component.ts | 4 +- .../directives/is-endpoint-active.directive.ts | 4 +- .../resources/webapp/src/app/core/pipes/index.ts | 2 +- .../index.ts | 6 +- .../normalize-dropdown-multi-value.pipe.ts} | 17 +- .../services/applicationServiceFacade.service.ts | 8 +- .../app/core/services/image-page-resolve.guard.ts | 2 +- .../app/core/services/user-images-page.service.ts | 11 +- .../case-insensitive-sort-util.ts} | 9 +- ...EndpointList.ts => check-endpoint-list-util.ts} | 2 +- .../resources/webapp/src/app/core/util/index.ts | 4 +- .../{pipes/index.ts => util/to-title-case-util.ts} | 11 +- .../image-detail-dialog.component.ts | 12 +- .../page-filter/page-filter.component.html | 30 ++- .../page-filter/page-filter.component.scss | 4 + .../page-filter/page-filter.component.ts | 57 ++-- .../src/app/resources/images/images.component.html | 298 ++++++++++++--------- .../src/app/resources/images/images.component.scss | 111 +++++++- .../src/app/resources/images/images.component.ts | 60 +++-- .../src/app/resources/images/images.config.ts | 25 +- .../src/app/resources/images/images.model.ts | 22 +- .../src/app/resources/images/images.service.ts | 66 ++++- .../resources-grid/resources-grid.component.ts | 1 + 23 files changed, 538 insertions(+), 228 deletions(-) diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/project/project-list/project-list.component.ts b/services/self-service/src/main/resources/webapp/src/app/administration/project/project-list/project-list.component.ts index 059758be3..9800bfe5e 100644 --- a/services/self-service/src/main/resources/webapp/src/app/administration/project/project-list/project-list.component.ts +++ b/services/self-service/src/main/resources/webapp/src/app/administration/project/project-list/project-list.component.ts @@ -28,7 +28,7 @@ import {ProgressBarService} from '../../../core/services/progress-bar.service'; import {EdgeActionDialogComponent} from '../../../shared/modal-dialog/edge-action-dialog'; import { EndpointService } from '../../../core/services'; import { Endpoint, ModifiedEndpoint, Project } from '../project.model'; -import { checkEndpointList } from '../../../core/util'; +import { checkEndpointListUtil } from '../../../core/util'; import { EndpointStatus } from '../project.config'; @Component({ @@ -110,7 +110,7 @@ export class ProjectListComponent implements OnInit, OnDestroy { } isRecreateBtnDisabled(endpointList: ModifiedEndpoint[]): boolean { - return checkEndpointList(endpointList); + return checkEndpointListUtil(endpointList); } private getFilteredEndpointList(action: string, project) { diff --git a/services/self-service/src/main/resources/webapp/src/app/core/directives/is-endpoint-active.directive.ts b/services/self-service/src/main/resources/webapp/src/app/core/directives/is-endpoint-active.directive.ts index 0f9db68bd..60c9beedd 100644 --- a/services/self-service/src/main/resources/webapp/src/app/core/directives/is-endpoint-active.directive.ts +++ b/services/self-service/src/main/resources/webapp/src/app/core/directives/is-endpoint-active.directive.ts @@ -20,7 +20,7 @@ import { Directive, ElementRef, HostListener, Input, OnInit, Renderer2 } from '@angular/core'; import { ModifiedEndpoint } from '../../administration/project/project.model'; -import { checkEndpointList } from '../util'; +import { checkEndpointListUtil } from '../util'; @Directive({ @@ -55,6 +55,6 @@ export class IsEndpointsActiveDirective implements OnInit { } private checkEndpointList(endpointList: ModifiedEndpoint[]): void { - this.isButtonDisabled = checkEndpointList(endpointList); + this.isButtonDisabled = checkEndpointListUtil(endpointList); } } diff --git a/services/self-service/src/main/resources/webapp/src/app/core/pipes/index.ts b/services/self-service/src/main/resources/webapp/src/app/core/pipes/index.ts index bc2e2c7b6..b4a481e95 100644 --- a/services/self-service/src/main/resources/webapp/src/app/core/pipes/index.ts +++ b/services/self-service/src/main/resources/webapp/src/app/core/pipes/index.ts @@ -23,4 +23,4 @@ export * from './lib-sort-pipe'; export * from './replace-breaks-pipe'; export * from './highlight.pipe'; export * from './convert-action-pipe'; -export * from './capitalize-first-letter-pipe'; +export * from './normalize-dropdown-multi-value'; diff --git a/services/self-service/src/main/resources/webapp/src/app/core/pipes/capitalize-first-letter-pipe/index.ts b/services/self-service/src/main/resources/webapp/src/app/core/pipes/normalize-dropdown-multi-value/index.ts similarity index 84% rename from services/self-service/src/main/resources/webapp/src/app/core/pipes/capitalize-first-letter-pipe/index.ts rename to services/self-service/src/main/resources/webapp/src/app/core/pipes/normalize-dropdown-multi-value/index.ts index d333ad504..d8b409ce1 100644 --- a/services/self-service/src/main/resources/webapp/src/app/core/pipes/capitalize-first-letter-pipe/index.ts +++ b/services/self-service/src/main/resources/webapp/src/app/core/pipes/normalize-dropdown-multi-value/index.ts @@ -19,12 +19,12 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { CapitalizeFirstLetterPipe } from './capitalize-first-letter.pipe'; +import { NormalizeDropdownMultiValuePipe } from './normalize-dropdown-multi-value.pipe'; @NgModule({ imports: [CommonModule], - declarations: [CapitalizeFirstLetterPipe], - exports: [CapitalizeFirstLetterPipe] + declarations: [NormalizeDropdownMultiValuePipe], + exports: [NormalizeDropdownMultiValuePipe] }) export class CapitalizeFirstLetterPipeModule { } diff --git a/services/self-service/src/main/resources/webapp/src/app/core/pipes/capitalize-first-letter-pipe/capitalize-first-letter.pipe.ts b/services/self-service/src/main/resources/webapp/src/app/core/pipes/normalize-dropdown-multi-value/normalize-dropdown-multi-value.pipe.ts similarity index 72% rename from services/self-service/src/main/resources/webapp/src/app/core/pipes/capitalize-first-letter-pipe/capitalize-first-letter.pipe.ts rename to services/self-service/src/main/resources/webapp/src/app/core/pipes/normalize-dropdown-multi-value/normalize-dropdown-multi-value.pipe.ts index df57fee83..2a81a4ef5 100644 --- a/services/self-service/src/main/resources/webapp/src/app/core/pipes/capitalize-first-letter-pipe/capitalize-first-letter.pipe.ts +++ b/services/self-service/src/main/resources/webapp/src/app/core/pipes/normalize-dropdown-multi-value/normalize-dropdown-multi-value.pipe.ts @@ -17,17 +17,20 @@ * under the License. */ - import { Pipe, PipeTransform } from '@angular/core'; -@Pipe({ name: 'capitalizeFirstLetter' }) +@Pipe({ name: 'normalizeDropdownMultiValue' }) -export class CapitalizeFirstLetterPipe implements PipeTransform { - transform(value: string): string { - if (!value) { +export class NormalizeDropdownMultiValuePipe implements PipeTransform { + transform(value: string[]): string { + if (!value.length) { return ''; } - const firstLetter = value. substring(0, 1). toUpperCase(); - return `${firstLetter}${value.substring(1).toLowerCase()}`; + const [firstValue] = value; + if (value.length === 1) { + return firstValue; + } + + return `${firstValue} (+${value.length - 1} others)`; } } diff --git a/services/self-service/src/main/resources/webapp/src/app/core/services/applicationServiceFacade.service.ts b/services/self-service/src/main/resources/webapp/src/app/core/services/applicationServiceFacade.service.ts index f97ca1da1..5f85bb5fa 100644 --- a/services/self-service/src/main/resources/webapp/src/app/core/services/applicationServiceFacade.service.ts +++ b/services/self-service/src/main/resources/webapp/src/app/core/services/applicationServiceFacade.service.ts @@ -24,7 +24,7 @@ import { HttpClient } from '@angular/common/http'; import { Dictionary } from '../collections'; import { environment } from '../../../environments/environment'; import { HTTPMethod } from '../util'; -import { ShareImageAllUsersParams } from '../../resources/images'; +import { ImageFilterFormValue, ShareImageAllUsersParams} from '../../resources/images'; // we can now access environment.apiUrl const API_URL = environment.apiUrl; @@ -189,6 +189,12 @@ export class ApplicationServiceFacade { null); } + buildFilterUserImagePage(params: ImageFilterFormValue): Observable<any> { + return this.buildRequest(HTTPMethod.POST, + this.requestRegistry.Item(ApplicationServiceFacade.IMAGE_PAGE), + params); + } + buildShareImageAllUsers(params: ShareImageAllUsersParams): Observable<any> { return this.buildRequest(HTTPMethod.POST, this.requestRegistry.Item(ApplicationServiceFacade.SHARE_ALL), diff --git a/services/self-service/src/main/resources/webapp/src/app/core/services/image-page-resolve.guard.ts b/services/self-service/src/main/resources/webapp/src/app/core/services/image-page-resolve.guard.ts index 77df3956f..4f89ede57 100644 --- a/services/self-service/src/main/resources/webapp/src/app/core/services/image-page-resolve.guard.ts +++ b/services/self-service/src/main/resources/webapp/src/app/core/services/image-page-resolve.guard.ts @@ -16,7 +16,7 @@ export class ImagePageResolveGuard implements Resolve<ProjectModel[]> { ) {} resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<ProjectModel[]> { - return this.imagesService.getUserImagePageInfo().pipe( + return this.imagesService.getImagePageInfo().pipe( switchMap((projectList: ProjectModel[]) => of(projectList)), take(1) ); diff --git a/services/self-service/src/main/resources/webapp/src/app/core/services/user-images-page.service.ts b/services/self-service/src/main/resources/webapp/src/app/core/services/user-images-page.service.ts index 9f9e0afff..198e8c8ee 100644 --- a/services/self-service/src/main/resources/webapp/src/app/core/services/user-images-page.service.ts +++ b/services/self-service/src/main/resources/webapp/src/app/core/services/user-images-page.service.ts @@ -23,7 +23,7 @@ import { catchError } from 'rxjs/operators'; import { ErrorUtils } from '../util'; import { ApplicationServiceFacade } from './applicationServiceFacade.service'; -import {ProjectModel, ShareImageAllUsersParams} from '../../resources/images'; +import {ImageFilterFormDropdownData, ImageFilterFormValue, ProjectModel, ShareImageAllUsersParams} from '../../resources/images'; @Injectable() export class UserImagesPageService { @@ -32,13 +32,20 @@ export class UserImagesPageService { ) { } - getUserImagePageInfo(): Observable<ProjectModel[]> { + getFilterImagePage(): Observable<ProjectModel[]> { return this.applicationServiceFacade.buildGetUserImagePage() .pipe( catchError(ErrorUtils.handleServiceError) ); } + filterImagePage(params: ImageFilterFormValue): Observable<ProjectModel[]> { + return this.applicationServiceFacade.buildFilterUserImagePage(params) + .pipe( + catchError(ErrorUtils.handleServiceError) + ); + } + shareImagesAllUser(shareParams: ShareImageAllUsersParams): Observable<ProjectModel[]> { return this.applicationServiceFacade.buildShareImageAllUsers(shareParams) .pipe( diff --git a/services/self-service/src/main/resources/webapp/src/app/core/pipes/index.ts b/services/self-service/src/main/resources/webapp/src/app/core/util/case-insensitive-sort-util.ts similarity index 75% copy from services/self-service/src/main/resources/webapp/src/app/core/pipes/index.ts copy to services/self-service/src/main/resources/webapp/src/app/core/util/case-insensitive-sort-util.ts index bc2e2c7b6..416d6cca2 100644 --- a/services/self-service/src/main/resources/webapp/src/app/core/pipes/index.ts +++ b/services/self-service/src/main/resources/webapp/src/app/core/util/case-insensitive-sort-util.ts @@ -17,10 +17,5 @@ * under the License. */ -export * from './keys-pipe'; -export * from './underscoreless-pipe'; -export * from './lib-sort-pipe'; -export * from './replace-breaks-pipe'; -export * from './highlight.pipe'; -export * from './convert-action-pipe'; -export * from './capitalize-first-letter-pipe'; + +export const caseInsensitiveSortUtil = (arr: string[]): string[] => arr.sort(((a, b) => a.toLowerCase() > b.toLowerCase() ? 1 : -1)); diff --git a/services/self-service/src/main/resources/webapp/src/app/core/util/checkEndpointList.ts b/services/self-service/src/main/resources/webapp/src/app/core/util/check-endpoint-list-util.ts similarity index 93% rename from services/self-service/src/main/resources/webapp/src/app/core/util/checkEndpointList.ts rename to services/self-service/src/main/resources/webapp/src/app/core/util/check-endpoint-list-util.ts index 4e2e0bfd9..b8aa14a74 100644 --- a/services/self-service/src/main/resources/webapp/src/app/core/util/checkEndpointList.ts +++ b/services/self-service/src/main/resources/webapp/src/app/core/util/check-endpoint-list-util.ts @@ -19,7 +19,7 @@ import { ModifiedEndpoint } from '../../administration/project/project.model'; -export const checkEndpointList = (endpointList: ModifiedEndpoint[]): boolean => { +export const checkEndpointListUtil = (endpointList: ModifiedEndpoint[]): boolean => { const isAllInactiveEdgeNodeHaveInactiveEndpoint = endpointList.filter(({status}) => status === 'TERMINATED' || status === 'FAILED') .every(({endpointStatus}) => !endpointStatus || endpointStatus === 'INACTIVE'); return isAllInactiveEdgeNodeHaveInactiveEndpoint; diff --git a/services/self-service/src/main/resources/webapp/src/app/core/util/index.ts b/services/self-service/src/main/resources/webapp/src/app/core/util/index.ts index b00239022..b15943474 100644 --- a/services/self-service/src/main/resources/webapp/src/app/core/util/index.ts +++ b/services/self-service/src/main/resources/webapp/src/app/core/util/index.ts @@ -26,4 +26,6 @@ export * from './fileUtils'; export * from './checkUtils'; export * from './patterns'; export * from './http-methods'; -export * from './checkEndpointList'; +export * from './check-endpoint-list-util'; +export * from './case-insensitive-sort-util'; +export * from './to-title-case-util'; diff --git a/services/self-service/src/main/resources/webapp/src/app/core/pipes/index.ts b/services/self-service/src/main/resources/webapp/src/app/core/util/to-title-case-util.ts similarity index 75% copy from services/self-service/src/main/resources/webapp/src/app/core/pipes/index.ts copy to services/self-service/src/main/resources/webapp/src/app/core/util/to-title-case-util.ts index bc2e2c7b6..42448bb11 100644 --- a/services/self-service/src/main/resources/webapp/src/app/core/pipes/index.ts +++ b/services/self-service/src/main/resources/webapp/src/app/core/util/to-title-case-util.ts @@ -17,10 +17,7 @@ * under the License. */ -export * from './keys-pipe'; -export * from './underscoreless-pipe'; -export * from './lib-sort-pipe'; -export * from './replace-breaks-pipe'; -export * from './highlight.pipe'; -export * from './convert-action-pipe'; -export * from './capitalize-first-letter-pipe'; +export const toTitleCaseUtil = (value: string): string => { + const firstLetter = value.substring(0, 1).toUpperCase(); + return `${firstLetter}${value.substring(1).toLowerCase()}`; +}; diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/image-detail-dialog/image-detail-dialog.component.ts b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/image-detail-dialog/image-detail-dialog.component.ts index 1894f5763..61c8ad117 100644 --- a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/image-detail-dialog/image-detail-dialog.component.ts +++ b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/image-detail-dialog/image-detail-dialog.component.ts @@ -19,9 +19,9 @@ import { Component, Inject, OnInit } from '@angular/core'; import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from '@angular/material/dialog'; -import {Library, ModalData} from '../../images'; +import {LibraryInfoItem, Library, ModalData} from '../../images'; import {LibraryInfoModalComponent} from '../library-info-modal/library-info-modal.component'; - +import {caseInsensitiveSortUtil} from '../../../core/util'; @Component({ selector: 'datalab-image-detail-dialog', @@ -42,7 +42,7 @@ export class ImageDetailDialogComponent implements OnInit { private dialog: MatDialog, ) { } - ngOnInit() { + ngOnInit(): void { this.libraryList = this.normalizeLibraries(); } @@ -55,12 +55,12 @@ export class ImageDetailDialogComponent implements OnInit { }); } - private normalizeLibraries() { + private normalizeLibraries(): LibraryInfoItem[] { return this.data.image.libraries.reduce((acc, item) => { const libraryName = this.normalizeLibraryName(item); const isLibAdded = acc.find(({name}) => item.group === name); if (!isLibAdded) { - const newLibrary = { + const newLibrary: LibraryInfoItem = { name: item.group, libs: [`${libraryName} v ${item.version}`] }; @@ -71,7 +71,7 @@ export class ImageDetailDialogComponent implements OnInit { return acc; }, []) .map(item => { - item.libs.sort((a, b) => a.toLowerCase() > b.toLowerCase() ? 1 : -1); + caseInsensitiveSortUtil(item.libs); return item; }); } diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/page-filter/page-filter.component.html b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/page-filter/page-filter.component.html index 066ee44b6..2782e7f41 100644 --- a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/page-filter/page-filter.component.html +++ b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/page-filter/page-filter.component.html @@ -22,11 +22,16 @@ <form class="filter-table__wrapper" [formGroup]="filterForm"> <div class="form-control__wrapper control-group"> <label class="label">Custom tag</label> - <input type="text" class="form-control" [placeholder]="placeholders.imageName" formControlName="imageName" - matInput - [matAutocomplete]="auto"> + <input + type="text" + class="form-control" + [placeholder]="placeholders.imageName" + [formControlName]="dropdownFieldNames.imageName" + matInput + [matAutocomplete]="auto" + /> <mat-autocomplete autoActiveFirstOption #auto="matAutocomplete"> - <mat-option *ngFor="let option of ($filterDropdownData | async).imageNames" [value]="option"> + <mat-option *ngFor="let option of ($filterDropdownData | async).imageName" [value]="option"> {{option}} </mat-option> </mat-autocomplete> @@ -38,18 +43,21 @@ <span class="form-field-wrapper"> <mat-form-field> <mat-select - formControlName="statuses" + [formControlName]="dropdownFieldNames.statuses" disableOptionCentering panelClass="create-resources-dialog scrolling" [placeholder]="placeholders.status" multiple (click)="onSelectClick()" > + <mat-select-trigger class="select__value"> + {{statuses.value | normalizeDropdownMultiValue | titlecase}} + </mat-select-trigger> <mat-option *ngFor="let status of ($filterDropdownData | async).imageStatuses" [value]="status" > - {{ status }} + {{ status | titlecase }} </mat-option> </mat-select> <button class="caret"> @@ -66,13 +74,16 @@ <span class="form-field-wrapper"> <mat-form-field> <mat-select - formControlName="cloudProviders" + [formControlName]="dropdownFieldNames.endpoints" disableOptionCentering panelClass="create-resources-dialog scrolling" [placeholder]="placeholders.endpoint" multiple (click)="onSelectClick()" > + <mat-select-trigger class="select__value"> + {{endpoints.value | normalizeDropdownMultiValue}} + </mat-select-trigger> <mat-option *ngFor="let endpoint of ($filterDropdownData | async).endpoints" [value]="endpoint" @@ -94,13 +105,16 @@ <span class="form-field-wrapper"> <mat-form-field> <mat-select - formControlName="templateNames" + [formControlName]="dropdownFieldNames.templateNames" disableOptionCentering panelClass="create-resources-dialog scrolling" [placeholder]="placeholders.templateName" multiple (click)="onSelectClick()" > + <mat-select-trigger class="select__value"> + {{templateNames.value | normalizeDropdownMultiValue}} + </mat-select-trigger> <mat-option *ngFor="let template of ($filterDropdownData | async).templateNames" [value]="template" diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/page-filter/page-filter.component.scss b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/page-filter/page-filter.component.scss index 1b6956150..b578cd45d 100644 --- a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/page-filter/page-filter.component.scss +++ b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/page-filter/page-filter.component.scss @@ -53,3 +53,7 @@ .content-box { padding: 25px 0 35px; } + +.select__value { + color: #607d8b; +} diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/page-filter/page-filter.component.ts b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/page-filter/page-filter.component.ts index edc9d4ce7..10184ea2b 100644 --- a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/page-filter/page-filter.component.ts +++ b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/page-filter/page-filter.component.ts @@ -17,13 +17,13 @@ * under the License. */ -import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; -import {AbstractControl, FormBuilder, FormControl, FormGroup} from '@angular/forms'; +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; +import { Observable } from 'rxjs'; +import { tap } from 'rxjs/operators'; -import {FilterFormPlaceholders} from './page-filter.config'; -import {DropdownFieldNames, FilterDropdownValue} from '../../images'; -import {Observable} from 'rxjs'; -import {tap} from 'rxjs/operators'; +import { FilterFormPlaceholders } from './page-filter.config'; +import { DropdownFieldNames, ImageFilterFormDropdownData, ImageFilterFormValue } from '../../images'; @Component({ selector: 'datalab-page-filter', @@ -31,14 +31,16 @@ import {tap} from 'rxjs/operators'; styleUrls: ['./page-filter.component.scss'] }) export class PageFilterComponent implements OnInit { - @Input() $filterDropdownData: Observable<FilterDropdownValue>; + @Input() $filterDropdownData: Observable<ImageFilterFormDropdownData>; + @Input() $filterFormStartValue: Observable<ImageFilterFormValue>; - @Output() filterFormValue: EventEmitter<FilterDropdownValue> = new EventEmitter<FilterDropdownValue>(); + @Output() filterFormValue: EventEmitter<ImageFilterFormValue> = new EventEmitter<ImageFilterFormValue>(); @Output() closeFilter: EventEmitter<any> = new EventEmitter<any>(); @Output() imageNameValue: EventEmitter<string> = new EventEmitter<string>(); @Output() onValueChanges: EventEmitter<string> = new EventEmitter<string>(); readonly placeholders: typeof FilterFormPlaceholders = FilterFormPlaceholders; + readonly dropdownFieldNames: typeof DropdownFieldNames = DropdownFieldNames; filterForm: FormGroup; @@ -47,17 +49,9 @@ export class PageFilterComponent implements OnInit { ) { } ngOnInit(): void { - this.initFilterForm(); + this.createFilterForm(); this.onControlChange(DropdownFieldNames.imageName); - } - - initFilterForm(): void { - this.filterForm = this.fb.group({ - imageName: '', - cloudProviders: [[]], - statuses: [[]], - templateNames: [[]] - }); + this.setFilterValue(); } onSelectClick(): void { @@ -73,11 +67,34 @@ export class PageFilterComponent implements OnInit { this.closeFilter.emit(); } - onControlChange(fieldName: keyof FilterDropdownValue): void { - console.log(this.filterForm.get(fieldName)); + onControlChange(fieldName: keyof ImageFilterFormDropdownData): void { this.filterForm.get(fieldName)?.valueChanges.pipe( tap((inputValue: string) => this.onValueChanges.emit(inputValue)) ).subscribe(); + } + + private createFilterForm(): void { + this.filterForm = this.fb.group({ + imageName: '', + endpoints: [[]], + statuses: [[]], + templateNames: [[]] + }); + } + + private setFilterValue(): void { + this.$filterFormStartValue.subscribe(value => this.filterForm.patchValue(value)); + } + + get statuses() { + return this.filterForm.get(DropdownFieldNames.statuses) as FormControl; + } + + get endpoints() { + return this.filterForm.get(DropdownFieldNames.endpoints) as FormControl; + } + get templateNames() { + return this.filterForm.get(DropdownFieldNames.templateNames) as FormControl; } } diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/images/images.component.html b/services/self-service/src/main/resources/webapp/src/app/resources/images/images.component.html index 49a106309..44f9fad82 100644 --- a/services/self-service/src/main/resources/webapp/src/app/resources/images/images.component.html +++ b/services/self-service/src/main/resources/webapp/src/app/resources/images/images.component.html @@ -60,6 +60,8 @@ </div> <div class="button--wrapper"> + + <span class="action-button--wrapper"> <button type="button" @@ -91,19 +93,38 @@ </button> </div> </span> + <button mat-raised-button [disabled]="!(dataSource | async)?.length" class="butt filter__btn" (click)="onFilterClick()"> <i class="material-icons">filter_list</i> <span class="filter__btn--name">Filter</span> + <button *ngIf="isFiltered" type="button" (click)="onResetFilterClick($event)" class="close__btn">×</button> </button> <div *ngIf="isFilterOpened | async" class="filer__wrapper"> <datalab-page-filter [$filterDropdownData]="$filterDropdownData" + [$filterFormStartValue]="$filterFormValue" (filterFormValue)="onFilterApplyClick($event)" (closeFilter)="onFilterCancelClick()" - (onValueChanges)="onControlChanges(dropdownFieldNames.imageNames, $event)" + (onValueChanges)="onControlChanges(dropdownFieldNames.imageName, $event)" > </datalab-page-filter> </div> + + <button + mat-raised-button + class="butt mr-10 show-active__btn" + (click)="toggleShowActive()" + > + <span *ngIf="isShowActive; else inactive"> + <i class="material-icons">visibility_off</i> Show active + </span> + <ng-template #inactive> + <span> + <i class="material-icons">visibility</i> Show all + </span> + </ng-template> + </button> + <span> <button mat-raised-button class="butt" (click)="onRefreshClick()"> <i class="material-icons highlight">autorenew</i> @@ -115,10 +136,21 @@ <mat-divider></mat-divider> - <table mat-table [dataSource]="dataSource | async" class="mat-elevation-z8 image-table data-grid"> - <ng-container matColumnDef="checkbox"> - <th mat-header-cell *matHeaderCellDef class="image-checkbox--wrapper"> - <div class="header-cell--wrapper"> + <table + mat-table + [dataSource]="projectSource | async" + multiTemplateDataRows + class="data-grid resources mat-elevation-z6" + [trackBy]="trackBy" + > + + <ng-container matColumnDef="project"> + <td mat-cell *matCellDef="let element" [attr.colspan]="8" class="image-page__project"> {{ element.project }} </td> + </ng-container> + + <ng-container matColumnDef="checkbox"> + <th mat-header-cell *matHeaderCellDef class="image-checkbox--wrapper"> + <div class="header-cell--wrapper"> <span *ngIf="(dataSource | async)?.length" > <datalab-checkbox (click)="allCheckboxToggle()" @@ -126,130 +158,142 @@ class="image-checkbox" ></datalab-checkbox> </span> - </div> - </th> - <td mat-cell *matCellDef="let element" class="image-checkbox--wrapper"> - <datalab-checkbox - (click)="onCheckboxClick(element)" - class="image-checkbox" - [checked]="element.isSelected" - ></datalab-checkbox> - </td> - </ng-container> - - <ng-container matColumnDef="imageName"> - <th mat-header-cell *matHeaderCellDef> - <div class="header-cell--wrapper"> - <span>{{tableHeaderCellTitles.imageName}}</span> - </div> - </th> - <td mat-cell *matCellDef="let element"> {{element.name}} </td> - </ng-container> - - <ng-container matColumnDef="imageStatus"> - <th mat-header-cell *matHeaderCellDef> - <div class="header-cell--wrapper"> - <span>{{tableHeaderCellTitles.imageStatus}}</span> - </div> - </th> - <td mat-cell *matCellDef="let element" ngClass="{{ element.status.toLowerCase() || ''}}"> - {{element.status | capitalizeFirstLetter}} - </td> - </ng-container> - - <ng-container matColumnDef="creationDate"> - <th mat-header-cell *matHeaderCellDef> - <div class="header-cell--wrapper"> - <span>{{tableHeaderCellTitles.creationDate}}</span> - </div> - </th> - <td mat-cell *matCellDef="let element"> - <span> {{element.timestamp | localDate : 'short'}} </span> - </td> - </ng-container> - - <ng-container matColumnDef="endpoint"> - <th mat-header-cell *matHeaderCellDef> - <div class="header-cell--wrapper"> - <span>{{tableHeaderCellTitles.endpoint}}</span> - </div> - </th> - <td mat-cell *matCellDef="let element"> {{element.endpoint}} </td> - </ng-container> - - <ng-container matColumnDef="templateName"> - <th mat-header-cell *matHeaderCellDef> - <div class="header-cell--wrapper"> - <span>{{tableHeaderCellTitles.templateName}}</span> - </div> - </th> - <td mat-cell *matCellDef="let element"> {{element.templateName}} </td> - </ng-container> - - <ng-container matColumnDef="sharedStatus"> - <th mat-header-cell *matHeaderCellDef> - <div class="header-cell--wrapper"> - <span>{{tableHeaderCellTitles.sharedStatus}}</span> - </div> - </th> - <td mat-cell *matCellDef="let element"> - <div class="shared-status--wrapper"> - <span class="shared-status"> {{element.shared ? sharedStatus.shared : sharedStatus.private}} </span> - <span *ngIf="element.shared" class="currency_details" > + </div> + </th> + </ng-container> + + <ng-container matColumnDef="imageName"> + <th mat-header-cell *matHeaderCellDef class="name-col header-cell"> + <span class="label image-label">Image name</span> + </th> + </ng-container> + + <ng-container matColumnDef="imageStatus"> + <th mat-header-cell *matHeaderCellDef class="status-col header-cell"> + <span class="label image-label"> Status </span> + </th> + </ng-container> + + <ng-container matColumnDef="creationDate"> + <th mat-header-cell *matHeaderCellDef class="shape-col header-cell"> + <span class="label image-label"> Creation date </span> + </th> + </ng-container> + + <ng-container matColumnDef="endpoint"> + <th mat-header-cell *matHeaderCellDef class="tag-col header-cell"> + <span class="label image-label"> Endpoint </span> + </th> + </ng-container> + + <ng-container matColumnDef="templateName"> + <th mat-header-cell *matHeaderCellDef class="resources-col label-header"> + <span class="label image-label"> Template name </span> + </th> + </ng-container> + + <ng-container matColumnDef="sharedStatus"> + <th mat-header-cell *matHeaderCellDef class="cost-col label-header"> + <span class="label image-label"> Sharing </span> + </th> + </ng-container> + + <ng-container matColumnDef="actions"> + <th mat-header-cell *matHeaderCellDef class="settings label-header"> + <span class="label image-label"> Actions </span> + </th> + </ng-container> + + <!-- ----------------------------------------------------- --> + + <ng-container matColumnDef="expandedDetail"> + <td mat-cell *matCellDef="let element" class="exploratory" [attr.colspan]="8"> + <tr *ngFor="let element of element.images; let i = index" class="element-row mat-row"> + <td mat-cell class="image-checkbox--wrapper"> + <datalab-checkbox + (click)="onCheckboxClick(element)" + class="image-checkbox" + [checked]="element.isSelected" + ></datalab-checkbox> + </td> + + <td class="name-col image-table-cell"> + <span> + {{ element.name }} + </span> + </td> + <td class="status-col status image-table-cell" ngClass="{{ element.status.toLowerCase() || ''}}"> + {{element.status | titlecase}} + </td> + <td class="shape-col image-table-cell"> + <span> {{element.timestamp | localDate : 'short'}} </span> + </td> + + <td class="tag-col selection image-table-cell"> + {{element.endpoint }} + </td> + + <td class="resources-col image-table-cell"> + {{element.templateName }} + </td> + <td class="cost-col image-table-cell"> + <div class="shared-status--wrapper"> + <span class="shared-status"> {{element.shared ? sharedStatus.shared : sharedStatus.private}} </span> + <span *ngIf="element.shared" class="currency_details" > <i class="material-icons">help_outline</i> </span> - </div> - </td> - </ng-container> + </div> + </td> - <ng-container matColumnDef="actions"> - <th mat-header-cell *matHeaderCellDef> {{tableHeaderCellTitles.actions}} </th> - <td mat-cell *matCellDef="let element" class="settings actions-col"> + <td mat-cell class="settings actions-col"> + <div class="button--wrapper action__button--wrapper"> + <span class="currency_details" (click)="onImageInfoClick(element)"> + <i class="material-icons">help_outline</i> + </span> + <span #settings class="actions" (click)="actions.toggle($event, settings)"></span> + </div> + <bubble-up #actions class="list-menu" position="bottom-left" alternative="top-left"> + <ul class="list-unstyled"> + <li [matTooltip]="element.status !== imageStatus.active && tooltipStatuses.activeOnly + || userName !== element.user && tooltipStatuses.creatorOnly" + matTooltipPosition="above" + [matTooltipDisabled]="userName === element.user && element.status === imageStatus.active" + > + <button + class="action-button__share" + (click)="onShareClick(element)" + [disabled]="userName !== element.user || element.status !== imageStatus.active" + > + <i class="material-icons">screen_share</i> + <span>Share</span> + </button> + </li> + <li + [matTooltip]=tooltipStatuses.unableTerminate + matTooltipPosition="above" + > + <button class="action-button__share"> + <i class="material-icons">phonelink_off</i> + <span>Terminate</span> + </button> + </li> + </ul> + </bubble-up> + </td> + </tr> + </td> + </ng-container> - <div class="button--wrapper"> - <span class="currency_details" (click)="onImageInfoClick(element)"> - <i class="material-icons">help_outline</i> - </span> - <span #settings class="actions" (click)="actions.toggle($event, settings)"></span> - </div> - <bubble-up #actions class="list-menu" position="bottom-left" alternative="top-left"> - <ul class="list-unstyled"> - <li [matTooltip]="element.status !== imageStatus.active && tooltipStatuses.activeOnly - || userName !== element.user && tooltipStatuses.creatorOnly" - matTooltipPosition="above" - [matTooltipDisabled]="userName === element.user && element.status === imageStatus.active" - > - <button - class="action-button__share" - (click)="onShareClick(element)" - [disabled]="userName !== element.user || element.status !== imageStatus.active" - > - <i class="material-icons">screen_share</i> - <span>Share</span> - </button> - </li> - <li - [matTooltip]=tooltipStatuses.unableTerminate - matTooltipPosition="above" - > - <button class="action-button__share"> - <i class="material-icons">phonelink_off</i> - <span>Terminate</span> - </button> - </li> - </ul> - </bubble-up> - </td> - </ng-container> - - <ng-container matColumnDef="placeholder"> - <td mat-footer-cell *matFooterCellDef class="info" [colSpan]="displayedColumns.length - 1"> - <span> There are no images yet </span> - </td> - </ng-container> - - <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> - <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr> - <tr [hidden]="(dataSource | async)?.length" mat-footer-row *matFooterRowDef="['placeholder']"></tr> - </table> + <ng-container matColumnDef="placeholder"> + <td mat-footer-cell *matFooterCellDef class="info" [colSpan]="displayedColumns.length - 1"> + <span> There are no images yet </span> + </td> + </ng-container> + + <tr mat-header-row *matHeaderRowDef="displayedColumns;" class="header-row"></tr> + + <tr mat-row *matRowDef="let element; columns: ['project']" class="element-row"></tr> + <tr mat-row *matRowDef="let row; columns: ['expandedDetail']" class="detail-row"></tr> + <tr [hidden]="(dataSource | async)?.length" mat-footer-row *matFooterRowDef="['placeholder']"></tr> + </table> </section> diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/images/images.component.scss b/services/self-service/src/main/resources/webapp/src/app/resources/images/images.component.scss index 680be511b..b7dae4ed1 100644 --- a/services/self-service/src/main/resources/webapp/src/app/resources/images/images.component.scss +++ b/services/self-service/src/main/resources/webapp/src/app/resources/images/images.component.scss @@ -121,11 +121,12 @@ .filer__wrapper { position: absolute; top: 20px; - left: 0px; + left: 170px; z-index: 10; } .filter__btn { + position: relative; margin-right: 10px; vertical-align: middle; width: 110px; @@ -134,3 +135,111 @@ line-height: 0; } } + +.show-active__btn { + line-height: 1; +} + +.close__btn { + position: absolute; + top: 0px; + right: 2px; + width: 12px; + color: rgb(87, 114, 137); + background-color: transparent; + font-size: 15px; + border: none; + outline: none; + + &:hover { + color: #35afd5; + cursor: pointer; + } +} + +.settings { + text-align: end; +} + +.image-page__project { + font-weight: 600; + color: #577289; + padding-left: 21px; +} + +.image-checkbox--wrapper { + width: 4%; +} + +table.resources .header-row > .header-cell { + padding: 5px 0; +} + +table.resources .exploratory .element-row td.actions-col { + padding: 0; +} + +table.resources .exploratory .element-row td, +table.resources .header-row > .header-cell, +table.resources .header-row th.label-header,{ + padding: 0; +} + +table.resources .header-row .label.image-label { + padding-top: 0px; + vertical-align: inherit !important; +} + +.image-table-cell { + padding: 0 !important; +} + +table.resources { + & .name-col.header-cell { + width: 15.5%; + } + + & .shape-col.image-table-cell, + & .tag-col.image-table-cell { + width: 17%; + } + + & .shape-col.header-cell, + & .tag-col.header-cell{ + width: 15.7%; + } + + & .resources-col.image-table-cell { + width: 23%; + } + + & .resources-col.label-header { + width: 21.6%; + } + + & .settings.actions-col, + & .settings.label-header { + width: 7%; + } + + & .status-col.header-cell { + width: 10.1%; + } + + & .cost-col.label-header { + width: 12.5%; + } +} + +.settings.image-table-cell, +.settings.label-header { + width: 12%; +} + +.action__button--wrapper { + width: 100%; +} + +table.resources .label-header.settings { + padding-right: 15px !important; +} diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/images/images.component.ts b/services/self-service/src/main/resources/webapp/src/app/resources/images/images.component.ts index 64d02cf60..139661a44 100644 --- a/services/self-service/src/main/resources/webapp/src/app/resources/images/images.component.ts +++ b/services/self-service/src/main/resources/webapp/src/app/resources/images/images.component.ts @@ -17,16 +17,16 @@ * under the License. */ -import {Component, OnDestroy, OnInit} from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; import { Observable } from 'rxjs'; -import {map, tap} from 'rxjs/operators'; +import { map, tap} from 'rxjs/operators'; import { ToastrService } from 'ngx-toastr'; import { GeneralEnvironmentStatus } from '../../administration/management/management.model'; import { HealthStatusService } from '../../core/services'; -import {FilterDropdownValue, ImageModel, ProjectModel} from './images.model'; +import { ImageFilterFormDropdownData, ImageFilterFormValue, ImageModel, ProjectModel } from './images.model'; import { TooltipStatuses, Image_Table_Column_Headers, @@ -34,13 +34,13 @@ import { ImageStatuses, Localstorage_Key, Placeholders, - Shared_Status, DropdownFieldNames, + Shared_Status, DropdownFieldNames, FilterFormInitialValue, ImageModelKeysForFilter, } from './images.config'; import { ShareImageDialogComponent } from '../exploratory/share-image/share-image-dialog.component'; import { ImagesService } from './images.service'; import { ProgressBarService } from '../../core/services/progress-bar.service'; import { ImageDetailDialogComponent } from '../exploratory/image-detail-dialog/image-detail-dialog.component'; -import {ActivatedRoute} from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; @Component({ selector: 'datalab-images', @@ -64,13 +64,17 @@ export class ImagesComponent implements OnInit, OnDestroy { isActionsOpen: boolean = false; healthStatus: GeneralEnvironmentStatus; dataSource: Observable<ImageModel[]>; + projectSource: Observable<ProjectModel[]>; checkboxSelected: boolean = false; projectList: string[] = []; activeProjectName: string = ''; - userName!: string; + userName: string; isProjectsMoreThanOne: boolean; isFilterOpened: Observable<boolean>; - $filterDropdownData: Observable<FilterDropdownValue>; + $filterDropdownData: Observable<ImageFilterFormDropdownData>; + $filterFormValue: Observable<ImageFilterFormValue>; + isShowActive: boolean = true; + isFiltered: boolean = false; constructor( private healthStatusService: HealthStatusService, @@ -88,10 +92,16 @@ export class ImagesComponent implements OnInit, OnDestroy { this.initImageTable(); this.initFilterBtn(); this.getDropdownList(); + this.getFilterFormValue(); } ngOnDestroy(): void { this.imagesService.closeFilter(); + this.imagesService.setFilterFormValue(FilterFormInitialValue); + } + + public trackBy(index, item) { + return null; } onCheckboxClick(element: ImageModel): void { @@ -116,7 +126,7 @@ export class ImagesComponent implements OnInit, OnDestroy { } onRefreshClick(): void { - this.getUserImagePageInfo(); + this.imagesService.getImagePageInfo().subscribe(); this.activeProjectName = ''; } @@ -143,8 +153,10 @@ export class ImagesComponent implements OnInit, OnDestroy { this.imagesService.openFilter(); } - onFilterApplyClick(filterFormValue): void { - console.log(filterFormValue); + onFilterApplyClick(filterFormValue: ImageFilterFormValue): void { + this.imagesService.filterImagePageInfo(filterFormValue).subscribe(); + this.imagesService.setFilterFormValue(filterFormValue); + this.isFiltered = true; this.imagesService.closeFilter(); } @@ -152,14 +164,21 @@ export class ImagesComponent implements OnInit, OnDestroy { this.imagesService.closeFilter(); } - onControlChanges(controlName: keyof FilterDropdownValue, inputValue: string): void { + onControlChanges(controlName: keyof ImageFilterFormDropdownData, inputValue: string): void { this.imagesService.filterDropdownField(DropdownFieldNames.imageName, inputValue); } - // onImageNameChange1(inputValue: string): void { - // this.imagesService.filterDropdownField(inputValue); - // console.log(inputValue); - // } + toggleShowActive(): void { + this.isShowActive = !this.isShowActive; + this.imagesService.showImage(this.isShowActive, ImageModelKeysForFilter.status, ImageStatuses.active); + } + + onResetFilterClick(event: Event): void { + event.stopPropagation(); + this.imagesService.filterImagePageInfo(FilterFormInitialValue).subscribe(); + this.imagesService.setFilterFormValue(FilterFormInitialValue); + this.isFiltered = false; + } private getEnvironmentHealthStatus(): void { this.healthStatusService.getEnvironmentHealthStatus().subscribe( @@ -173,12 +192,15 @@ export class ImagesComponent implements OnInit, OnDestroy { private getUserImagePageInfo(): void { this.route.data.pipe( map(data => data['projectList']), - tap(projectList => this.getProjectList(projectList)) + tap(projectList => { + return this.getProjectList(projectList); + }) ).subscribe(); } private initImageTable(): void { this.dataSource = this.imagesService.$imageList; + this.projectSource = this.imagesService.$projectList; } private getProjectList(imagePageList: ProjectModel[]): void { @@ -187,7 +209,7 @@ export class ImagesComponent implements OnInit, OnDestroy { } this.projectList = this.imagesService.getProjectNameList(imagePageList); this.isProjectsMoreThanOne = this.projectList.length > 1; - if (this.isProjectsMoreThanOne) { + if (!this.isProjectsMoreThanOne) { this.activeProjectName = this.projectList[0]; } } @@ -204,6 +226,10 @@ export class ImagesComponent implements OnInit, OnDestroy { this.$filterDropdownData = this.imagesService.$filterDropdownData; } + getFilterFormValue(): void { + this.$filterFormValue = this.imagesService.$filterFormValue; + } + get isImageSelected(): boolean { return this.imagesService.isImageSelected(); } diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/images/images.config.ts b/services/self-service/src/main/resources/webapp/src/app/resources/images/images.config.ts index 3db199434..4be84ca19 100644 --- a/services/self-service/src/main/resources/webapp/src/app/resources/images/images.config.ts +++ b/services/self-service/src/main/resources/webapp/src/app/resources/images/images.config.ts @@ -70,10 +70,9 @@ export enum TooltipStatuses { export enum DropdownFieldNames { imageName = 'imageName', - imageStatuses = 'imageStatuses', endpoints = 'endpoints', templateNames = 'templateNames', - sharingStatuses = 'sharingStatuses' + statuses = 'statuses' } export enum ImageModelNames { @@ -83,3 +82,25 @@ export enum ImageModelNames { templateName = 'templateName', shared = 'shared' } + +export const FilterFormInitialValue = { + endpoints: [], + imageName: '', + statuses: [], + templateNames: [], +}; + +export const ChangedColumnStartValue = { + endpoints: false, + imageName: false, + statuses: false, + templateNames: false, +}; + +export enum ImageModelKeysForFilter { + status = 'status', + name = 'name', + endpoint = 'endpoint', + templateName = 'templateName', + shared = 'shared' +} diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/images/images.model.ts b/services/self-service/src/main/resources/webapp/src/app/resources/images/images.model.ts index f1fe519c9..620a9c5ee 100644 --- a/services/self-service/src/main/resources/webapp/src/app/resources/images/images.model.ts +++ b/services/self-service/src/main/resources/webapp/src/app/resources/images/images.model.ts @@ -49,10 +49,30 @@ export interface ClusterConfig { Configurations: any[]; } -export interface FilterDropdownValue { +export interface ImageFilterFormDropdownData { imageName: string[]; imageStatuses: string[]; endpoints: string[]; templateNames: string[]; sharingStatuses: string[]; } + +export interface ImageFilterFormValue { + endpoints: string[]; + imageName: string; + statuses: string[]; + templateNames: string[]; +} + + +export interface LibraryInfoItem { + name: string; + libs: string[]; +} + +export interface FilteredColumnList { + imageName: boolean; + statuses: boolean; + endpoints: boolean; + templateNames: boolean; +} diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/images/images.service.ts b/services/self-service/src/main/resources/webapp/src/app/resources/images/images.service.ts index 054e45bf7..2e62e0350 100644 --- a/services/self-service/src/main/resources/webapp/src/app/resources/images/images.service.ts +++ b/services/self-service/src/main/resources/webapp/src/app/resources/images/images.service.ts @@ -2,9 +2,17 @@ import { Injectable } from '@angular/core'; import { tap } from 'rxjs/operators'; import { BehaviorSubject, Observable } from 'rxjs'; -import { FilterDropdownValue, ImageModel, ProjectModel, ShareImageAllUsersParams } from './images.model'; +import { + FilteredColumnList, + ImageFilterFormDropdownData, + ImageFilterFormValue, + ImageModel, + ProjectModel, + ShareImageAllUsersParams +} from './images.model'; import { ApplicationServiceFacade, UserImagesPageService } from '../../core/services'; -import {ImageModelNames} from './images.config'; +import { ChangedColumnStartValue, FilterFormInitialValue, ImageModelNames } from './images.config'; +import { caseInsensitiveSortUtil } from '../../core/util'; @Injectable({ providedIn: 'root' @@ -13,25 +21,40 @@ export class ImagesService { private $$projectList: BehaviorSubject<ProjectModel[]> = new BehaviorSubject<ProjectModel[]>([]); private $$imageList: BehaviorSubject<ImageModel[]> = new BehaviorSubject<ImageModel[]>([]); private $$isFilterOpened: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false); - private $$filterDropdownData: BehaviorSubject<FilterDropdownValue> = new BehaviorSubject<FilterDropdownValue>({} as FilterDropdownValue); - private dropdownStartValue: FilterDropdownValue; + // tslint:disable-next-line:max-line-length + private $$filterDropdownData: BehaviorSubject<ImageFilterFormDropdownData> = new BehaviorSubject<ImageFilterFormDropdownData>({} as ImageFilterFormDropdownData); + private $$filterFormValue: BehaviorSubject<ImageFilterFormValue> = new BehaviorSubject<ImageFilterFormValue>(FilterFormInitialValue); + private $$changedColumn: BehaviorSubject<FilteredColumnList> = new BehaviorSubject<FilteredColumnList>(ChangedColumnStartValue); + private dropdownStartValue: ImageFilterFormDropdownData; + $projectList = this.$$projectList.asObservable(); $imageList = this.$$imageList.asObservable(); $isFilterOpened = this.$$isFilterOpened.asObservable(); $filterDropdownData = this.$$filterDropdownData.asObservable(); + $filterFormValue = this.$$filterFormValue.asObservable(); constructor( private applicationServiceFacade: ApplicationServiceFacade, private userImagesPageService: UserImagesPageService ) { } - getUserImagePageInfo(): Observable<ProjectModel[]> { - return this.userImagesPageService.getUserImagePageInfo() + getImagePageInfo(): Observable<ProjectModel[]> { + return this.userImagesPageService.getFilterImagePage() .pipe( tap(value => this.getImagePageData(value)) ); } + filterImagePageInfo(params: ImageFilterFormValue): Observable<ProjectModel[]> { + return this.userImagesPageService.filterImagePage(params) + .pipe( + tap(value => { + console.log(value); + return this.getImagePageData(value); + }) + ); + } + shareImageAllUsers(image: ImageModel): Observable<ProjectModel[]> { const shareParams: ShareImageAllUsersParams = { imageName: image.name, @@ -82,11 +105,29 @@ export class ImagesService { this.$$isFilterOpened.next(false); } - filterDropdownField(field: keyof FilterDropdownValue, value: string, ) { + filterDropdownField(field: keyof ImageFilterFormDropdownData, value: string, ) { const filteredDropdownList = this.dropdownStartValue[field].filter(item => item.toLowerCase().includes(value)); this.addFilterDropdownData({...this.$$filterDropdownData.value, imageName: filteredDropdownList}); } + setFilterFormValue(value: ImageFilterFormValue): void { + this.$$filterFormValue.next(value); + } + + showImage(flag: boolean, field: keyof ImageModel, comparedValue: string) { + const imageList = this.getImageList(this.$$projectList.getValue()); + if (flag) { + this.updateImageList(imageList); + } else { + const filteredImageList = this.filterByCondition(imageList, field, comparedValue); + this.updateImageList(filteredImageList); + } + } + + private filterByCondition(arr: ImageModel[], field: keyof ImageModel, comparedValue: string) { + return arr.filter(item => item[field] === comparedValue); + } + private getDropdownDataList(): void { const dropdownList = { imageName: this.getDropdownDataItem(ImageModelNames.name), @@ -95,7 +136,6 @@ export class ImagesService { templateNames: this.getDropdownDataItem(ImageModelNames.templateName), sharingStatuses: this.getDropdownDataItem(ImageModelNames.shared), }; - this.addFilterDropdownData(dropdownList); this.dropdownStartValue = dropdownList; } @@ -105,17 +145,21 @@ export class ImagesService { acc.add(item[key].toString()); return acc; }, new Set<string>()); - return [...dropdownItem]; + return caseInsensitiveSortUtil([...dropdownItem]); } - private addFilterDropdownData(data: FilterDropdownValue): void { + private addFilterDropdownData(data: ImageFilterFormDropdownData): void { this.$$filterDropdownData.next(data); } private getImagePageData(imagePageData: ProjectModel[]): void { - const imageList = imagePageData.reduce((acc: ImageModel[], {images}) => [...acc, ...images], []); + const imageList = this.getImageList(imagePageData); this.updateProjectList(imagePageData); this.updateImageList(imageList); this.getDropdownDataList(); } + + private getImageList(imagePageData: ProjectModel[]): ImageModel[] { + return imagePageData.reduce((acc: ImageModel[], {images}) => [...acc, ...images], []); + } } diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.component.ts b/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.component.ts index 4677f57d2..0fa17510e 100644 --- a/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.component.ts +++ b/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.component.ts @@ -444,6 +444,7 @@ export class ResourcesGridComponent implements OnInit { this.toastr.error('Creating computation resource failed!', 'Oops!'); } } + console.log(filteredData); this.filteredEnvironments = filteredData; } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
