From 195ff79d49cfbf23d2cd827f5f9ec797a7f8ccc5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ma=C3=ABl=20AUDEON?= <mael.audeon@teamdlab.com>
Date: Tue, 28 Jul 2020 17:02:28 +0200
Subject: [PATCH] [TRTL-206] Add filters on status & level

---
 .../src/app/core/api/group-api.service.ts     | 10 ++-
 .../group-list/group-criteria-builder.util.ts | 74 +++++++++++++++++++
 .../group-list/group-list.component.html      | 40 ++++++++++
 .../group/group-list/group-list.component.ts  | 39 +++++++++-
 .../identity/src/app/group/group.service.ts   |  9 ++-
 5 files changed, 166 insertions(+), 6 deletions(-)
 create mode 100644 ui/ui-frontend/projects/identity/src/app/group/group-list/group-criteria-builder.util.ts

diff --git a/ui/ui-frontend/projects/identity/src/app/core/api/group-api.service.ts b/ui/ui-frontend/projects/identity/src/app/core/api/group-api.service.ts
index 6d7019cd..b686c941 100644
--- a/ui/ui-frontend/projects/identity/src/app/core/api/group-api.service.ts
+++ b/ui/ui-frontend/projects/identity/src/app/core/api/group-api.service.ts
@@ -38,7 +38,7 @@ import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
 import { Inject, Injectable } from '@angular/core';
 import { Observable } from 'rxjs';
 
-import { BASE_URL, BaseHttpClient, Group, PageRequest, PaginatedResponse } from 'ui-frontend-common';
+import {BASE_URL, BaseHttpClient, Group, PageRequest, PaginatedResponse, SearchQuery} from 'ui-frontend-common';
 
 @Injectable({
   providedIn: 'root'
@@ -77,4 +77,12 @@ export class GroupApiService extends BaseHttpClient<Group> {
     return super.patch(groupPartial, headers);
   }
 
+  getLevels(query?: SearchQuery, headers?: HttpHeaders): Observable<string[]> {
+    let params =  new HttpParams();
+    if (query) {
+      params = params.set('criteria', JSON.stringify(query));
+    }
+
+    return this.http.get<string[]>(this.apiUrl + '/levels', { params, headers });
+  }
 }
diff --git a/ui/ui-frontend/projects/identity/src/app/group/group-list/group-criteria-builder.util.ts b/ui/ui-frontend/projects/identity/src/app/group/group-list/group-criteria-builder.util.ts
new file mode 100644
index 00000000..62a7f480
--- /dev/null
+++ b/ui/ui-frontend/projects/identity/src/app/group/group-list/group-criteria-builder.util.ts
@@ -0,0 +1,74 @@
+/*
+ * Copyright French Prime minister Office/SGMAP/DINSIC/Vitam Program (2019-2020)
+ * and the signatories of the "VITAM - Accord du Contributeur" agreement.
+ *
+ * contact@programmevitam.fr
+ *
+ * This software is a computer program whose purpose is to implement
+ * implement a digital archiving front-office system for the secure and
+ * efficient high volumetry VITAM solution.
+ *
+ * This software is governed by the CeCILL-C license under French law and
+ * abiding by the rules of distribution of free software.  You can  use,
+ * modify and/ or redistribute the software under the terms of the CeCILL-C
+ * license as circulated by CEA, CNRS and INRIA at the following URL
+ * "http://www.cecill.info".
+ *
+ * As a counterpart to the access to the source code and  rights to copy,
+ * modify and redistribute granted by the license, users are provided only
+ * with a limited warranty  and the software's author,  the holder of the
+ * economic rights,  and the successive licensors  have only  limited
+ * liability.
+ *
+ * In this respect, the user's attention is drawn to the risks associated
+ * with loading,  using,  modifying and/or developing or reproducing the
+ * software by the user in light of its specific status of free software,
+ * that may mean  that it is complicated to manipulate,  and  that  also
+ * therefore means  that it is reserved for developers  and  experienced
+ * professionals having in-depth computer knowledge. Users are therefore
+ * encouraged to load and test the software's suitability as regards their
+ * requirements in conditions enabling the security of their systems and/or
+ * data to be ensured and,  more generally, to use and operate it in the
+ * same conditions as regards security.
+ *
+ * The fact that you are presently reading this means that you have had
+ * knowledge of the CeCILL-C license and that you accept its terms.
+ */
+import { buildCriteriaFromFilters, Criterion, Operators, SearchQuery } from 'ui-frontend-common';
+
+const GROUP_FILTER_CONVERTER: Readonly<{ [key: string]: (values: any[]) => Array<Criterion | SearchQuery> }> = {
+
+  status: (statusList: string[]): Criterion[] => {
+    if (statusList.length === 0) {
+      return [];
+    }
+
+    const mappedList: boolean[] = [];
+    statusList.forEach((status) => {
+      switch (status) {
+        case 'ENABLED':
+          mappedList.push(true);
+          break;
+        case 'DISABLED':
+          mappedList.push(false);
+          break;
+      }
+    });
+    return [{ key: 'enabled', value: mappedList, operator: Operators.in }];
+  },
+  level: (levelList: string[]): Criterion[] => {
+    if (levelList.length === 0) {
+      return [];
+    }
+
+    if (levelList.some((level) => level === null)) {
+      levelList.push('');
+    }
+
+    return [{ key: 'level', value: levelList, operator: Operators.in }];
+  }
+};
+
+export function buildCriteriaFromGroupFilters(filterMap: { [key: string]: any[] }) {
+  return buildCriteriaFromFilters(filterMap, GROUP_FILTER_CONVERTER);
+}
diff --git a/ui/ui-frontend/projects/identity/src/app/group/group-list/group-list.component.html b/ui/ui-frontend/projects/identity/src/app/group/group-list/group-list.component.html
index 33e84304..3e6e799d 100644
--- a/ui/ui-frontend/projects/identity/src/app/group/group-list/group-list.component.html
+++ b/ui/ui-frontend/projects/identity/src/app/group/group-list/group-list.component.html
@@ -7,6 +7,30 @@
     <tr>
       <th>
         <div class="vitamui-table-header">
+          <button
+            class="vitamui-filter-button"
+            [vitamuiCommonTableFilter]="statusFilterTemplate"
+          >
+            <i class="material-icons vitamui-row-icon">filter_list</i>
+          </button>
+
+          <ng-template #statusFilterTemplate>
+            <vitamui-common-table-filter [(filter)]="this.filterMap['status']" (filterChange)="onFilterChange('status', $event)">
+              <vitamui-common-table-filter-option value="ENABLED">
+                <div class="table-filter-icon" i18n="@@groupStatusEnabled">
+                  <i class="vitamui-icon vitamui-icon-user status-badge status-badge-green close"></i>
+                  <span class="badge-state">Actif</span>
+                </div>
+              </vitamui-common-table-filter-option>
+              <vitamui-common-table-filter-option value="DISABLED">
+                <div class="table-filter-icon" i18n="@@groupStatusDisabled">
+                  <i class="vitamui-icon vitamui-icon-user status-badge status-badge-grey close"></i>
+                  <span class="badge-state">Désactivé</span>
+                </div>
+              </vitamui-common-table-filter-option>
+            </vitamui-common-table-filter>
+          </ng-template>
+
           <i class="vitamui-icon vitamui-icon-keys vitamui-row-icon"></i>
           <vitamui-common-order-by-button orderByKey="status" [(orderBy)]="orderBy" [(direction)]="direction"
               (orderChange)="emitOrderChange()"></vitamui-common-order-by-button>
@@ -35,6 +59,22 @@
       </th>
       <th>
         <div class="vitamui-table-header">
+          <button class="vitamui-filter-button" [vitamuiCommonTableFilter]="levelFilterTemplate"
+                  [class.active]="filterMap['level'] && filterMap['level'].length > 0" #levelFilterTrigger="vitamuiCommonTableFilter">
+            <i class="material-icons vitamui-row-icon">filter_list</i>
+          </button>
+
+          <ng-template #levelFilterTemplate>
+            <vitamui-common-table-filter-search
+              [(filter)]="filterMap['level']"
+              [options]="levelFilterOptions"
+              (filterChange)="onFilterChange('level', $event)"
+              (filterClose)="levelFilterTrigger?.close()"
+              emptyValueOption="-Niveau vide-"
+              i18n-emptyValueOption="@@groupsListLevelFilterEmpty"
+            ></vitamui-common-table-filter-search>
+          </ng-template>
+
           <span i18n="Description@@profileGroupListHeaderLevel">Niveau</span>
           <vitamui-common-order-by-button orderByKey="level" [(orderBy)]="orderBy" [(direction)]="direction"
                                           (orderChange)="emitOrderChange()"></vitamui-common-order-by-button>
diff --git a/ui/ui-frontend/projects/identity/src/app/group/group-list/group-list.component.ts b/ui/ui-frontend/projects/identity/src/app/group/group-list/group-list.component.ts
index 404b6733..dad34589 100644
--- a/ui/ui-frontend/projects/identity/src/app/group/group-list/group-list.component.ts
+++ b/ui/ui-frontend/projects/identity/src/app/group/group-list/group-list.component.ts
@@ -35,7 +35,7 @@
  * knowledge of the CeCILL-C license and that you accept its terms.
  */
 import { animate, state, style, transition, trigger } from '@angular/animations';
-import {Component, EventEmitter, Input, OnDestroy, OnInit, Output} from '@angular/core';
+import {Component, EventEmitter, Inject, Input, LOCALE_ID, OnDestroy, OnInit, Output} from '@angular/core';
 import {merge, Subject, Subscription} from 'rxjs';
 
 import {
@@ -47,6 +47,7 @@ import {
   SearchQuery
 } from 'ui-frontend-common';
 import { GroupService } from '../group.service';
+import {buildCriteriaFromGroupFilters} from './group-criteria-builder.util';
 
 @Component({
   selector: 'app-group-list',
@@ -85,18 +86,29 @@ export class GroupListComponent extends InfiniteScrollTable<Group> implements On
     'description'
   ];
 
+  filterMap: { [key: string]: any[] } = {
+    status: ['ENABLED'],
+    level: null
+  };
+
   orderBy = 'name';
   direction = Direction.ASCENDANT;
 
+  private readonly filterChange = new Subject<{ [key: string]: any[] }>();
   private readonly orderChange = new Subject<string>();
   private readonly searchChange = new Subject<string>();
 
-  constructor(public groupService: GroupService) {
+  levelFilterOptions: Array<{ value: string, label: string }> = [];
+
+  constructor(public groupService: GroupService,
+              @Inject(LOCALE_ID) private locale: string) {
     super(groupService);
   }
 
   ngOnInit() {
     this.search();
+    this.refreshLevelOptions();
+
     this.updatedGroupSub = this.groupService.updated.subscribe((updatedGroup: Group) => {
       const profileGroupIndex = this.dataSource.findIndex((group) => updatedGroup.id === group.id);
       if (profileGroupIndex > -1) {
@@ -105,11 +117,14 @@ export class GroupListComponent extends InfiniteScrollTable<Group> implements On
 
     });
 
-    const searchCriteriaChange = merge(this.searchChange, this.orderChange);
+    const searchCriteriaChange = merge(this.searchChange, this.filterChange, this.orderChange);
 
     searchCriteriaChange.subscribe(() => {
       const query: SearchQuery = {
-        criteria: buildCriteriaFromSearch(this._search, this.searchKeys)
+        criteria: [
+          ...buildCriteriaFromGroupFilters(this.filterMap),
+          ...buildCriteriaFromSearch(this._search, this.searchKeys)
+          ]
       };
       const pageRequest = new PageRequest(0, DEFAULT_PAGE_SIZE, this.orderBy, this.direction, JSON.stringify(query));
 
@@ -118,6 +133,11 @@ export class GroupListComponent extends InfiniteScrollTable<Group> implements On
 
   }
 
+  onFilterChange(key: string, values: any[]) {
+    this.filterMap[key] = values;
+    this.filterChange.next(this.filterMap);
+  }
+
   emitOrderChange() {
     this.orderChange.next();
   }
@@ -126,4 +146,15 @@ export class GroupListComponent extends InfiniteScrollTable<Group> implements On
     this.updatedGroupSub.unsubscribe();
   }
 
+  private refreshLevelOptions(query?: SearchQuery) {
+    this.groupService.getNonEmptyLevels(query).subscribe((levels: string[]) => {
+      this.levelFilterOptions = levels.map((level: string) => ({value: level, label: level }));
+      this.levelFilterOptions.sort(sortByLabel(this.locale));
+    });
+  }
+}
+
+
+function sortByLabel(locale: string): (a: { label: string }, b: { label: string }) => number {
+  return (a: { label: string }, b: { label: string }) => a.label.localeCompare(b.label, locale);
 }
diff --git a/ui/ui-frontend/projects/identity/src/app/group/group.service.ts b/ui/ui-frontend/projects/identity/src/app/group/group.service.ts
index 3d4bbd92..e91f87dd 100644
--- a/ui/ui-frontend/projects/identity/src/app/group/group.service.ts
+++ b/ui/ui-frontend/projects/identity/src/app/group/group.service.ts
@@ -35,7 +35,7 @@
  * knowledge of the CeCILL-C license and that you accept its terms.
  */
 import { Observable, Subject } from 'rxjs';
-import { tap } from 'rxjs/operators';
+import {map, tap} from 'rxjs/operators';
 import { Criterion, Group, Operators, SearchQuery, SearchService } from 'ui-frontend-common';
 
 import { HttpClient, HttpParams } from '@angular/common/http';
@@ -133,4 +133,11 @@ export class GroupService extends SearchService<Group> {
 
     return this.groupApi.getAllByParams(params);
   }
+
+  getNonEmptyLevels(query: SearchQuery) {
+    return this.groupApi.getLevels(query)
+      .pipe(
+        map((levels) => levels.filter((l) => !!l))
+      );
+  }
 }
-- 
GitLab