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 c8f3bb284c6c76ff08b02abdcc13f321fe720c3e
Author: Hennadii_Shpak <[email protected]>
AuthorDate: Wed Jul 27 14:35:48 2022 +0300

    initial commit
---
 .../resources/webapp/src/app/app.routing.module.ts |  15 ++-
 .../resources/webapp/src/app/core/core.module.ts   |   2 +-
 .../app/core/services/image-page-resolve.guard.ts  |  24 ++++
 .../webapp/src/app/core/services/index.ts          |   1 +
 .../app/core/services/user-images-page.service.ts  |  10 +-
 .../page-filter/page-filter.component.html         | 125 +++++++++++++++++++++
 .../page-filter/page-filter.component.scss         |  55 +++++++++
 .../page-filter/page-filter.component.ts           |  71 ++++++++++++
 .../exploratory/page-filter/page-filter.config.ts  |  26 +++++
 .../share-image/share-image-dialog.component.ts    |   2 -
 .../src/app/resources/images/images.component.html |  92 ++++++---------
 .../src/app/resources/images/images.component.scss |  18 +++
 .../src/app/resources/images/images.component.ts   |  97 ++++++++--------
 .../src/app/resources/images/images.config.ts      |   8 +-
 .../src/app/resources/images/images.model.ts       |   8 ++
 .../src/app/resources/images/images.service.ts     |  72 ++++++++++--
 .../webapp/src/app/resources/resources.module.ts   |   2 +
 17 files changed, 505 insertions(+), 123 deletions(-)

diff --git 
a/services/self-service/src/main/resources/webapp/src/app/app.routing.module.ts 
b/services/self-service/src/main/resources/webapp/src/app/app.routing.module.ts
index 8cc601782..9188aec15 100644
--- 
a/services/self-service/src/main/resources/webapp/src/app/app.routing.module.ts
+++ 
b/services/self-service/src/main/resources/webapp/src/app/app.routing.module.ts
@@ -31,11 +31,17 @@ import { ManagementComponent } from 
'./administration/management';
 import { ProjectComponent } from './administration/project/project.component';
 import { RolesComponent } from './administration/roles/roles.component';
 import { SwaggerComponent } from './swagger';
-import { AuthorizationGuard, CheckParamsGuard, CloudProviderGuard, AdminGuard, 
AuditGuard } from './core/services';
+import {
+  AuthorizationGuard,
+  CheckParamsGuard,
+  CloudProviderGuard,
+  AdminGuard,
+  AuditGuard,
+  ImagePageResolveGuard
+} from './core/services';
 import {ConfigurationComponent} from 
'./administration/configuration/configuration.component';
 import {ProjectAdminGuard} from './core/services/projectAdmin.guard';
 import {ReportingComponent} from './reports/reporting/reporting.component';
-import {OdahuComponent} from './administration/odahu/odahu.component';
 import {AuditComponent} from './reports/audit/audit.component';
 import {ImagesComponent} from './resources/images/images.component';
 
@@ -62,7 +68,10 @@ const routes: Routes = [
       {
         path: 'images',
         component: ImagesComponent,
-        canActivate: [AuthorizationGuard]
+        canActivate: [AuthorizationGuard],
+        resolve: {
+          projectList: ImagePageResolveGuard
+        }
       },
       {
         path: 'billing',
diff --git 
a/services/self-service/src/main/resources/webapp/src/app/core/core.module.ts 
b/services/self-service/src/main/resources/webapp/src/app/core/core.module.ts
index 33dc379fd..b1fb27d86 100644
--- 
a/services/self-service/src/main/resources/webapp/src/app/core/core.module.ts
+++ 
b/services/self-service/src/main/resources/webapp/src/app/core/core.module.ts
@@ -49,7 +49,7 @@ import { ErrorInterceptor } from 
'./interceptors/error.interceptor';
 
 import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
 import {  ConfigurationService } from './services/configutration.service';
-import {  AuditGuard, OdahuDeploymentService, UserImagesPageService } from 
'./services';
+import {AuditGuard, OdahuDeploymentService, UserImagesPageService} from 
'./services';
 import {  ProjectAdminGuard } from './services/projectAdmin.guard';
 
 @NgModule({
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
new file mode 100644
index 000000000..77df3956f
--- /dev/null
+++ 
b/services/self-service/src/main/resources/webapp/src/app/core/services/image-page-resolve.guard.ts
@@ -0,0 +1,24 @@
+import { Injectable } from '@angular/core';
+import {ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot} from 
'@angular/router';
+import {EMPTY, Observable, of} from 'rxjs';
+
+import {ImagesService} from '../../resources/images/images.service';
+import {ProjectModel} from '../../resources/images';
+import {switchMap, take} from 'rxjs/operators';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class ImagePageResolveGuard implements Resolve<ProjectModel[]> {
+  constructor(
+    private router: Router,
+    private imagesService: ImagesService
+  ) {}
+
+  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): 
Observable<ProjectModel[]> {
+    return this.imagesService.getUserImagePageInfo().pipe(
+      switchMap((projectList: ProjectModel[]) => of(projectList)),
+      take(1)
+    );
+  }
+}
diff --git 
a/services/self-service/src/main/resources/webapp/src/app/core/services/index.ts
 
b/services/self-service/src/main/resources/webapp/src/app/core/services/index.ts
index 68ce7e151..712ffb4ea 100644
--- 
a/services/self-service/src/main/resources/webapp/src/app/core/services/index.ts
+++ 
b/services/self-service/src/main/resources/webapp/src/app/core/services/index.ts
@@ -40,4 +40,5 @@ export * from './storage.service';
 export * from './project.service';
 export * from './odahu-deployment.service';
 export * from './endpoint.service';
+export * from './image-page-resolve.guard';
 export * from './user-images-page.service';
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 8a4472883..9f9e0afff 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,11 +23,13 @@ import {  catchError } from 'rxjs/operators';
 import {  ErrorUtils } from '../util';
 
 import { ApplicationServiceFacade } from './applicationServiceFacade.service';
-import { ProjectModel, ShareImageAllUsersParams } from 
'../../resources/images';
+import {ProjectModel, ShareImageAllUsersParams} from '../../resources/images';
 
 @Injectable()
 export class UserImagesPageService {
-  constructor(private applicationServiceFacade: ApplicationServiceFacade) { }
+  constructor(
+    private applicationServiceFacade: ApplicationServiceFacade
+  ) { }
 
 
   getUserImagePageInfo(): Observable<ProjectModel[]> {
@@ -37,8 +39,8 @@ export class UserImagesPageService {
       );
   }
 
-  shareImageAllUsers(params: ShareImageAllUsersParams): 
Observable<ProjectModel[]> {
-    return this.applicationServiceFacade.buildShareImageAllUsers(params)
+  shareImagesAllUser(shareParams: ShareImageAllUsersParams): 
Observable<ProjectModel[]> {
+    return this.applicationServiceFacade.buildShareImageAllUsers(shareParams)
       .pipe(
         catchError(ErrorUtils.handleServiceError)
       );
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
new file mode 100644
index 000000000..287f9db8e
--- /dev/null
+++ 
b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/page-filter/page-filter.component.html
@@ -0,0 +1,125 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<div class="dialog-content selection">
+  <div id="scrolling" class="content-box mat-reset scrolling-content">
+    <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">
+        <mat-autocomplete autoActiveFirstOption #auto="matAutocomplete">
+          <mat-option *ngFor="let option of filteredOptions" [value]="option">
+            {{option}}
+          </mat-option>
+        </mat-autocomplete>
+      </div>
+
+      <div class="control-group">
+        <label class="label">Status</label>
+        <div class="control selector-wrapper">
+            <span class="form-field-wrapper">
+              <mat-form-field>
+                <mat-select
+                  formControlName="statuses"
+                  disableOptionCentering
+                  panelClass="create-resources-dialog scrolling"
+                  [placeholder]="placeholders.status"
+                  multiple
+                  (click)="onSelectClick()"
+                >
+                  <mat-option
+                    *ngFor="let status of statuses"
+                    [value]="status"
+                  >
+                    {{ status }}
+                  </mat-option>
+                </mat-select>
+                <button class="caret">
+                  <i class="material-icons">keyboard_arrow_down</i>
+                </button>
+              </mat-form-field>
+            </span>
+        </div>
+      </div>
+
+      <div class="control-group">
+        <label class="label">Provider</label>
+        <div class="control selector-wrapper">
+            <span class="form-field-wrapper">
+              <mat-form-field>
+                <mat-select
+                  formControlName="cloudProviders"
+                  disableOptionCentering
+                  panelClass="create-resources-dialog scrolling"
+                  [placeholder]="placeholders.provider"
+                  multiple
+                  (click)="onSelectClick()"
+                >
+                  <mat-option
+                    *ngFor="let provider of cloudProviders"
+                    [value]="provider"
+                  >
+                    {{ provider }}
+                  </mat-option>
+                </mat-select>
+                <button class="caret">
+                  <i class="material-icons">keyboard_arrow_down</i>
+                </button>
+              </mat-form-field>
+            </span>
+        </div>
+      </div>
+
+      <div class="control-group">
+        <label class="label">Template name</label>
+        <div class="control selector-wrapper">
+            <span class="form-field-wrapper">
+              <mat-form-field>
+                <mat-select
+                  formControlName="templateNames"
+                  disableOptionCentering
+                  panelClass="create-resources-dialog scrolling"
+                  [placeholder]="placeholders.templateName"
+                  multiple
+                  (click)="onSelectClick()"
+                >
+                  <mat-option
+                    *ngFor="let template of templates"
+                    [value]="template"
+                  >
+                    {{ template }}
+                  </mat-option>
+                </mat-select>
+                <button class="caret">
+                  <i class="material-icons">keyboard_arrow_down</i>
+                </button>
+              </mat-form-field>
+            </span>
+        </div>
+      </div>
+
+      <div class="text-center m-top-20">
+        <button type="button" class="butt" mat-raised-button 
(click)="cancelFilter()" >Cancel</button>
+        <button type="button" class="butt butt-success" mat-raised-button 
(click)="confirmFilter()" >Apply</button>
+      </div>
+    </form>
+  </div>
+</div>
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
new file mode 100644
index 000000000..1b6956150
--- /dev/null
+++ 
b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/page-filter/page-filter.component.scss
@@ -0,0 +1,55 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+.filter-table__wrapper {
+  width: 450px;
+  padding: 20px;
+  background-color: white;
+}
+
+.filter-table__wrapper,
+.form-control {
+  box-shadow: 0 3px 1px -2px rgb(0 0 0 / 20%), 0 2px 2px 0 rgb(0 0 0 / 14%), 0 
1px 5px 0 rgb(0 0 0 / 12%);
+}
+
+.form-control {
+  padding: 8px;
+
+  &__wrapper {
+    & > .label {
+      width: 35%;
+    }
+
+    & > .form-control {
+      width: 65%;
+    }
+  }
+}
+
+.form-field-wrapper {
+  width: 100%;
+}
+
+::ng-deep .cdk-overlay-pane.normalized-dropdown-position {
+  transform: translateX(-15px) !important;
+}
+
+.content-box {
+  padding: 25px 0 35px;
+}
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
new file mode 100644
index 000000000..7ed77c714
--- /dev/null
+++ 
b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/page-filter/page-filter.component.ts
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import {Component, EventEmitter, OnInit, Output} from '@angular/core';
+import {FormBuilder, FormGroup} from '@angular/forms';
+
+import {FilterFormPlaceholders} from './page-filter.config';
+
+@Component({
+  selector: 'datalab-page-filter',
+  templateUrl: './page-filter.component.html',
+  styleUrls: ['./page-filter.component.scss']
+})
+export class PageFilterComponent implements OnInit {
+  @Output() filterFormValue: EventEmitter<any> = new EventEmitter<any>();
+  @Output() closeFilter: EventEmitter<any> = new EventEmitter<any>();
+
+  readonly placeholders: typeof FilterFormPlaceholders = 
FilterFormPlaceholders;
+
+  filteredOptions = [];
+  templates = ['Extra cheese', 'Mushroom', 'Onion', 'Pepperoni', 'Sausage', 
'Tomato'];
+  statuses = ['Extra cheese', 'Mushroom', 'Onion', 'Pepperoni', 'Sausage', 
'Tomato'];
+  cloudProviders = ['Extra cheese', 'Mushroom', 'Onion', 'Pepperoni', 
'Sausage', 'Tomato'];
+  filterForm: FormGroup;
+
+  constructor(
+    private fb: FormBuilder
+  ) { }
+
+  ngOnInit(): void {
+    this.initFilterForm();
+  }
+
+  initFilterForm(): void {
+    this.filterForm = this.fb.group({
+      imageName: '',
+      cloudProviders: [[]],
+      statuses: [[]],
+      templateNames: [[]]
+    });
+  }
+
+  onSelectClick(): void {
+    const dropdownList = document.querySelectorAll('.cdk-overlay-pane');
+    dropdownList.forEach(el => 
el.classList.add('normalized-dropdown-position'));
+  }
+
+  confirmFilter(): void {
+    this.filterFormValue.emit(this.filterForm.value);
+  }
+
+  cancelFilter(): void {
+    this.closeFilter.emit();
+  }
+}
diff --git 
a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/page-filter/page-filter.config.ts
 
b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/page-filter/page-filter.config.ts
new file mode 100644
index 000000000..b647ea02d
--- /dev/null
+++ 
b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/page-filter/page-filter.config.ts
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export enum FilterFormPlaceholders {
+  imageName = 'Enter image name',
+  status = 'Select status',
+  provider = 'Select provider',
+  templateName = 'Select template name',
+  sharing = 'Select sharing status'
+}
diff --git 
a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/share-image/share-image-dialog.component.ts
 
b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/share-image/share-image-dialog.component.ts
index 25bd72c62..9d0acbc09 100644
--- 
a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/share-image/share-image-dialog.component.ts
+++ 
b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/share-image/share-image-dialog.component.ts
@@ -20,7 +20,6 @@
 import { Component, Inject } from '@angular/core';
 import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
 import { ImagesService } from '../../images/images.service';
-import { UserImagesPageService } from '../../../core/services';
 import { ModalData, Toaster_Message } from '../../images';
 import { ToastrService } from 'ngx-toastr';
 
@@ -36,7 +35,6 @@ export class ShareImageDialogComponent {
     public dialogRef: MatDialogRef<ShareImageDialogComponent>,
     @Inject(MAT_DIALOG_DATA) public data: ModalData,
     private imagesService: ImagesService,
-    private userImagesPageService: UserImagesPageService,
     private toastr: ToastrService,
   ) { }
 
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 f93c78d1f..ab03ccd93 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
@@ -65,7 +65,7 @@
             type="button"
             class="butt action-button"
             mat-raised-button
-            [disabled]="isImageNotSelected"
+            [disabled]="!isImageSelected"
             (click)="onActionClick()"
           >
             Actions
@@ -91,8 +91,19 @@
           </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>
+      <div *ngIf="isFilterOpened | async" class="filer__wrapper">
+        <datalab-page-filter
+          (filterFormValue)="onFilterApplyClick($event)"
+          (closeFilter)="onFilterCancelClick()"
+        >
+        </datalab-page-filter>
+      </div>
       <span>
-        <button mat-raised-button class="butt" (click)="onRefresh()">
+        <button mat-raised-button class="butt" (click)="onRefreshClick()">
           <i class="material-icons highlight">autorenew</i>
           Refresh
         </button>
@@ -102,20 +113,17 @@
 
   <mat-divider></mat-divider>
 
-  <table mat-table [dataSource]="dataSource" class="mat-elevation-z8 
image-table data-grid">
+  <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">
-          <span  *ngIf="dataSource.length" >
+          <span  *ngIf="(dataSource | async)?.length" >
             <datalab-checkbox
               (click)="allCheckboxToggle()"
               [checked]="checkboxSelected"
               class="image-checkbox"
             ></datalab-checkbox>
           </span>
-          <i class="material-icons">
-            <span>more_vert</span>
-          </i>
         </div>
       </th>
       <td mat-cell *matCellDef="let element" class="image-checkbox--wrapper">
@@ -131,21 +139,26 @@
       <th mat-header-cell *matHeaderCellDef>
         <div class="header-cell--wrapper">
           <span>{{tableHeaderCellTitles.imageName}}</span>
-          <i class="material-icons">
-            <span>more_vert</span>
-          </i>
         </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>
-          <i class="material-icons">
-            <span>more_vert</span>
-          </i>
         </div>
       </th>
       <td mat-cell *matCellDef="let element">
@@ -153,39 +166,28 @@
       </td>
     </ng-container>
 
-    <ng-container matColumnDef="provider">
+    <ng-container matColumnDef="endpoint">
       <th mat-header-cell *matHeaderCellDef>
         <div class="header-cell--wrapper">
-          <span>{{tableHeaderCellTitles.provider}}</span>
-          <i class="material-icons">
-            <span>more_vert</span>
-          </i>
+          <span>{{tableHeaderCellTitles.endpoint}}</span>
         </div>
       </th>
-      <td mat-cell *matCellDef="let element"> {{element.cloudProvider}} </td>
+      <td mat-cell *matCellDef="let element"> {{element.endpoint}} </td>
     </ng-container>
 
-    <ng-container matColumnDef="imageStatus">
+    <ng-container matColumnDef="templateName">
       <th mat-header-cell *matHeaderCellDef>
         <div class="header-cell--wrapper">
-          <span>{{tableHeaderCellTitles.imageStatus}}</span>
-          <i class="material-icons">
-            <span>more_vert</span>
-          </i>
+          <span>{{tableHeaderCellTitles.templateName}}</span>
         </div>
       </th>
-      <td mat-cell *matCellDef="let element" ngClass="{{ 
element.status.toLowerCase() || ''}}">
-        {{element.status | capitalizeFirstLetter}}
-      </td>
+      <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>
-          <i class="material-icons">
-            <span>more_vert</span>
-          </i>
         </div>
       </th>
       <td mat-cell *matCellDef="let element">
@@ -198,36 +200,12 @@
       </td>
     </ng-container>
 
-    <ng-container matColumnDef="templateName">
-      <th mat-header-cell *matHeaderCellDef>
-        <div class="header-cell--wrapper">
-          <span>{{tableHeaderCellTitles.templateName}}</span>
-          <i class="material-icons">
-            <span>more_vert</span>
-          </i>
-        </div>
-      </th>
-      <td mat-cell *matCellDef="let element"> {{element.templateName}} </td>
-    </ng-container>
-
-    <ng-container matColumnDef="instanceName">
-      <th mat-header-cell *matHeaderCellDef>
-        <div class="header-cell--wrapper">
-          <span>{{tableHeaderCellTitles.instanceName}}</span>
-          <i class="material-icons">
-            <span>more_vert</span>
-          </i>
-        </div>
-      </th>
-      <td mat-cell *matCellDef="let element"> {{element.instanceName}} </td>
-    </ng-container>
-
     <ng-container matColumnDef="actions">
       <th mat-header-cell *matHeaderCellDef> {{tableHeaderCellTitles.actions}} 
</th>
       <td mat-cell *matCellDef="let element" class="settings actions-col">
 
         <div class="button--wrapper">
-          <span class="currency_details" (click)="onImageInfo(element)">
+          <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>
@@ -241,7 +219,7 @@
             >
               <button
                 class="action-button__share"
-                (click)="onShare(element)"
+                (click)="onShareClick(element)"
                 [disabled]="userName !== element.user || element.status !== 
imageStatus.active"
               >
                 <i class="material-icons">screen_share</i>
@@ -270,6 +248,6 @@
 
     <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
     <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
-    <tr [hidden]="dataSource.length" mat-footer-row 
*matFooterRowDef="['placeholder']"></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 c032060ce..680be511b 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
@@ -60,6 +60,7 @@
 
 .button--wrapper,
 .header-cell--wrapper {
+  position: relative;
   display: flex;
   justify-content: space-between;
 }
@@ -116,3 +117,20 @@
 .list-unstyled > li {
   margin: 0 !important;
 }
+
+.filer__wrapper {
+  position: absolute;
+  top: 20px;
+  left: 0px;
+  z-index: 10;
+}
+
+.filter__btn {
+  margin-right: 10px;
+  vertical-align: middle;
+  width: 110px;
+
+  &--name {
+    line-height: 0;
+  }
+}
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 cb581f502..bf7d6d34c 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,12 +17,15 @@
  * under the License.
  */
 
-import { Component, 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 { ToastrService } from 'ngx-toastr';
 
 import { GeneralEnvironmentStatus } from 
'../../administration/management/management.model';
-import { HealthStatusService, UserImagesPageService } from 
'../../core/services';
+import { HealthStatusService } from '../../core/services';
 import { ImageModel, ProjectModel } from './images.model';
 import {
   TooltipStatuses,
@@ -33,11 +36,11 @@ import {
   Placeholders,
   Shared_Status,
 } from './images.config';
-import { MatDialog } from '@angular/material/dialog';
 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';
 
 @Component({
   selector: 'datalab-images',
@@ -49,7 +52,7 @@ import { ImageDetailDialogComponent } from 
'../exploratory/image-detail-dialog/i
   ]
 })
 
-export class ImagesComponent implements OnInit {
+export class ImagesComponent implements OnInit, OnDestroy {
   readonly tableHeaderCellTitles: typeof Image_Table_Column_Headers = 
Image_Table_Column_Headers;
   readonly displayedColumns: typeof Image_Table_Titles = Image_Table_Titles;
   readonly placeholder: typeof Placeholders = Placeholders;
@@ -59,27 +62,34 @@ export class ImagesComponent implements OnInit {
 
   isActionsOpen: boolean = false;
   healthStatus: GeneralEnvironmentStatus;
-  dataSource: ImageModel[] = [];
+  dataSource: Observable<ImageModel[]>;
   checkboxSelected: boolean = false;
   projectList: string[] = [];
   activeProjectName: string = '';
   userName!: string;
-
-  private cashedImageListData: ProjectModel[] = [];
+  isProjectsMoreThanOne: boolean;
+  isFilterOpened: Observable<boolean>;
 
   constructor(
     private healthStatusService: HealthStatusService,
     public toastr: ToastrService,
-    private userImagesPageService: UserImagesPageService,
     private dialog: MatDialog,
     private imagesService: ImagesService,
-    private progressBarService: ProgressBarService
+    private progressBarService: ProgressBarService,
+    private route: ActivatedRoute,
   ) { }
 
   ngOnInit(): void {
     this.getEnvironmentHealthStatus();
     this.getUserImagePageInfo();
+
     this.getUserName();
+    this.initImageTable();
+    this.initFilterBtn();
+  }
+
+  ngOnDestroy(): void {
+    this.imagesService.closeFilter();
   }
 
   onCheckboxClick(element: ImageModel): void {
@@ -88,12 +98,7 @@ export class ImagesComponent implements OnInit {
 
   allCheckboxToggle(): void {
     this.checkboxSelected = !this.checkboxSelected;
-
-    if (this.checkboxSelected) {
-      this.dataSource.forEach(image => image.isSelected = true);
-    } else {
-      this.dataSource.forEach(image => image.isSelected = false);
-    }
+    this.imagesService.changeCheckboxValue(this.checkboxSelected);
   }
 
   onActionClick(): void {
@@ -102,20 +107,18 @@ export class ImagesComponent implements OnInit {
 
   onSelectClick(projectName: string = ''): void {
     if (!projectName) {
-      this.dataSource = this.getImageList();
       return;
     }
-    const currentProject = this.cashedImageListData.find(({project}) => 
project === projectName);
-    this.dataSource = [...currentProject.images];
-    this.activeProjectName = currentProject.project;
+    this.imagesService.getActiveProject(projectName);
+    this.activeProjectName = projectName;
   }
 
-  onRefresh(): void {
+  onRefreshClick(): void {
     this.getUserImagePageInfo();
     this.activeProjectName = '';
   }
 
-  onImageInfo(image: ImageModel): void {
+  onImageInfoClick(image: ImageModel): void {
     this.dialog.open(ImageDetailDialogComponent, {
       data: {
         image
@@ -124,23 +127,27 @@ export class ImagesComponent implements OnInit {
     });
   }
 
-  onShare(image: ImageModel): void {
+  onShareClick(image: ImageModel): void {
     this.dialog.open(ShareImageDialogComponent, {
       data: {
         image
       },
       panelClass: 'modal-sm'
     }).afterClosed()
-      .subscribe(() => {
-        if (this.imagesService.projectList) {
-          this.initImageTable(this.imagesService.projectList);
-        }
-        this.progressBarService.stopProgressBar();
-      });
+      .subscribe(() => this.progressBarService.stopProgressBar());
   }
 
-  private getImageList(): ImageModel[] {
-    return this.cashedImageListData.reduce((acc, {images}) => [...acc, 
...images], []);
+  onFilterClick(): void {
+    this.imagesService.openFilter();
+  }
+
+  onFilterApplyClick(filterFormValue): void {
+    console.log(filterFormValue);
+    this.imagesService.closeFilter();
+  }
+
+  onFilterCancelClick(): void {
+    this.imagesService.closeFilter();
   }
 
   private getEnvironmentHealthStatus(): void {
@@ -153,36 +160,36 @@ export class ImagesComponent implements OnInit {
   }
 
   private getUserImagePageInfo(): void {
-    this.userImagesPageService.getUserImagePageInfo().subscribe(imageListData 
=> this.initImageTable(imageListData));
+    this.route.data.pipe(
+      map(data => data['projectList']),
+      tap(projectList => this.getProjectList(projectList))
+    ).subscribe();
   }
 
-  private initImageTable(imagePageList: ProjectModel[]): void {
-    this.cashedImageListData = imagePageList;
-    this.getProjectList(imagePageList);
-    this.dataSource = this.getImageList();
-
-    if (imagePageList.length === 1) {
-      this.activeProjectName = imagePageList[0].project;
-    }
+  private initImageTable(): void {
+    this.dataSource = this.imagesService.$imageList;
   }
 
   private getProjectList(imagePageList: ProjectModel[]): void {
     if (!imagePageList) {
       return;
     }
-    this.projectList = [];
-    imagePageList.forEach(({project}) => this.projectList.push(project));
+    this.projectList = this.imagesService.getProjectNameList(imagePageList);
+    this.isProjectsMoreThanOne = this.projectList.length > 1;
+    if (this.isProjectsMoreThanOne) {
+      this.activeProjectName = this.projectList[0];
+    }
   }
 
   private getUserName(): void {
     this.userName = localStorage.getItem(Localstorage_Key.userName);
   }
 
-  get isProjectsMoreThanOne(): boolean {
-    return this.projectList.length > 1;
+  private initFilterBtn(): void {
+    this.isFilterOpened = this.imagesService.$isFilterOpened;
   }
 
-  get isImageNotSelected(): boolean {
-    return !this.dataSource.some(image => image.isSelected);
+  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 9628420e7..b9c958aa0 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
@@ -26,6 +26,7 @@ export enum Image_Table_Column_Headers {
   templateName = 'Template name',
   instanceName = 'Instance name',
   actions = 'Actions',
+  endpoint = 'Endpoint',
 }
 
 export enum Shared_Status {
@@ -36,12 +37,11 @@ export enum Shared_Status {
 export const Image_Table_Titles = <const>[
   'checkbox',
   'imageName',
-  'creationDate',
-  'provider',
   'imageStatus',
-  'sharedStatus',
+  'creationDate',
+  'endpoint',
   'templateName',
-  'instanceName',
+  'sharedStatus',
   'actions'
 ];
 
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 ab63a8842..7bd30a1df 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
@@ -48,3 +48,11 @@ export interface ClusterConfig {
   Properties: Record<string, any>;
   Configurations: any[];
 }
+
+export interface FilterDropdownValue {
+  imageName: string[];
+  imageStatus: string[];
+  provider: string[];
+  templateName: string[];
+  sharingStatus: string[];
+}
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 3ac70b47c..56a48bb62 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
@@ -1,28 +1,86 @@
 import { Injectable } from '@angular/core';
+import {tap} from 'rxjs/operators';
+import {BehaviorSubject, Observable} from 'rxjs';
+
 import { ImageModel, ProjectModel, ShareImageAllUsersParams } from 
'./images.model';
-import { Observable } from 'rxjs';
-import { UserImagesPageService } from '../../core/services';
-import { tap } from 'rxjs/operators';
+import {ApplicationServiceFacade, UserImagesPageService} from 
'../../core/services';
 
 @Injectable({
   providedIn: 'root'
 })
 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);
 
-  projectList: ProjectModel[];
+  $imageList = this.$$imageList.asObservable();
+  $isFilterOpened = this.$$isFilterOpened.asObservable();
 
   constructor(
+    private applicationServiceFacade: ApplicationServiceFacade,
     private userImagesPageService: UserImagesPageService
   ) { }
 
+  getUserImagePageInfo(): Observable<ProjectModel[]> {
+    return this.userImagesPageService.getUserImagePageInfo()
+      .pipe(
+        tap(value => this.getImagePageData(value))
+      );
+  }
+
   shareImageAllUsers(image: ImageModel): Observable<ProjectModel[]> {
     const shareParams: ShareImageAllUsersParams = {
       imageName: image.name,
       projectName: image.project,
       endpoint: image.endpoint
     };
-    return this.userImagesPageService.shareImageAllUsers(shareParams).pipe(
-      tap((response: ProjectModel[]) => this.projectList = response)
-    );
+
+    return this.userImagesPageService.shareImagesAllUser(shareParams)
+      .pipe(
+        tap(value => this.getImagePageData(value))
+      );
+  }
+
+  getActiveProject(projectName: string): void {
+    const currentProject = this.$$projectList.value.find(({project}) => 
project === projectName);
+    this.updateImageList(currentProject.images);
+  }
+
+  getProjectNameList(imageList: ProjectModel[]): string[] {
+    return imageList.map(({project}) => project);
+  }
+
+  updateImageList(imageList: ImageModel[]): void {
+    this.$$imageList.next(imageList);
+  }
+
+  updateProjectList(projectList: ProjectModel[]): void {
+    this.$$projectList.next(projectList);
+  }
+
+  changeCheckboxValue(value: boolean): void {
+    const updatedImageList = this.$$imageList.value.map(image => {
+      image.isSelected = value;
+      return image;
+    });
+    this.$$imageList.next(updatedImageList);
+  }
+
+  isImageSelected(): boolean {
+    return this.$$imageList.value.some(image => image.isSelected);
+  }
+
+  openFilter(): void {
+    this.$$isFilterOpened.next(true);
+  }
+
+  closeFilter(): void {
+    this.$$isFilterOpened.next(false);
+  }
+
+  private getImagePageData(imagePageData: ProjectModel[]): void {
+    const imageList = imagePageData.reduce((acc: ImageModel[], {images}) => 
[...acc, ...images], []);
+    this.updateProjectList(imagePageData);
+    this.updateImageList(imageList);
   }
 }
diff --git 
a/services/self-service/src/main/resources/webapp/src/app/resources/resources.module.ts
 
b/services/self-service/src/main/resources/webapp/src/app/resources/resources.module.ts
index 931e037d6..488762efa 100644
--- 
a/services/self-service/src/main/resources/webapp/src/app/resources/resources.module.ts
+++ 
b/services/self-service/src/main/resources/webapp/src/app/resources/resources.module.ts
@@ -39,6 +39,7 @@ import { LocalDatePipeModule } from 
'../core/pipes/local-date-pipe';
 import { ShareImageDialogModule } from 
'./exploratory/share-image/share-image-dialog.module';
 import { ImageDetailDialogModule } from 
'./exploratory/image-detail-dialog/image-detail-dialog.module';
 import {LibraryInfoModalModule} from 
'./exploratory/library-info-modal/library-info-modal.module';
+import { PageFilterComponent } from 
'./exploratory/page-filter/page-filter.component';
 
 @NgModule({
   imports: [
@@ -64,6 +65,7 @@ import {LibraryInfoModalModule} from 
'./exploratory/library-info-modal/library-i
     ManageUngitComponent,
     ConfirmDeleteAccountDialogComponent,
     ImagesComponent,
+    PageFilterComponent,
 
   ],
   entryComponents: [ManageUngitComponent, ConfirmDeleteAccountDialogComponent],


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to