From b91cbc9671b8416694ef53c507dc8431f5009555 Mon Sep 17 00:00:00 2001
From: Alex ORLUC <alex.orluc@maarch.org>
Date: Thu, 23 Jan 2020 11:12:03 +0100
Subject: [PATCH] FEAT #11882 TIME 1:10 front history batch

---
 .../administration-routing.module.ts          |   2 +
 .../administration/administration.module.ts   |   2 +
 ...istory-batch-administration.component.html | 161 ++++++++++
 ...istory-batch-administration.component.scss |  63 ++++
 .../history-batch-administration.component.ts | 294 ++++++++++++++++++
 src/frontend/service/privileges.service.ts    |   2 +-
 6 files changed, 523 insertions(+), 1 deletion(-)
 create mode 100644 src/frontend/app/administration/history/batch/history-batch-administration.component.html
 create mode 100644 src/frontend/app/administration/history/batch/history-batch-administration.component.scss
 create mode 100644 src/frontend/app/administration/history/batch/history-batch-administration.component.ts

diff --git a/src/frontend/app/administration/administration-routing.module.ts b/src/frontend/app/administration/administration-routing.module.ts
index 735427b23cf..09528a92f8a 100755
--- a/src/frontend/app/administration/administration-routing.module.ts
+++ b/src/frontend/app/administration/administration-routing.module.ts
@@ -24,6 +24,7 @@ import { ReportsAdministrationComponent }               from './report/reports-a
 import { NotificationsAdministrationComponent }         from './notification/notifications-administration.component';
 import { NotificationAdministrationComponent }          from './notification/notification-administration.component';
 import { HistoryAdministrationComponent }               from './history/history-administration.component';
+import { HistoryBatchAdministrationComponent }          from './history/batch/history-batch-administration.component';
 import { UpdateStatusAdministrationComponent }          from './updateStatus/update-status-administration.component';
 import { ContactsGroupsAdministrationComponent }        from './contact/group/contacts-groups-administration.component';
 import { ContactsGroupAdministrationComponent }         from './contact/group/contacts-group-administration.component';
@@ -81,6 +82,7 @@ import { ContactsPageAdministrationComponent } from './contact/page/contacts-pag
             { path: 'administration/notifications/new', canActivate: [AppGuard], component: NotificationAdministrationComponent },
             { path: 'administration/notifications/:identifier', canActivate: [AppGuard], component: NotificationAdministrationComponent },
             { path: 'administration/history', canActivate: [AppGuard], component: HistoryAdministrationComponent },
+            { path: 'administration/history-batch', canActivate: [AppGuard], component: HistoryBatchAdministrationComponent },
             { path: 'administration/update-status', canActivate: [AppGuard], component: UpdateStatusAdministrationComponent },
             { path: 'administration/contacts', canActivate: [AppGuard], component: ContactsListAdministrationComponent },
             { path: 'administration/contacts/list', redirectTo: 'administration/contacts', pathMatch: 'full' },
diff --git a/src/frontend/app/administration/administration.module.ts b/src/frontend/app/administration/administration.module.ts
index f35bd496b96..20ded5a9545 100755
--- a/src/frontend/app/administration/administration.module.ts
+++ b/src/frontend/app/administration/administration.module.ts
@@ -27,6 +27,7 @@ import { PrioritiesAdministrationComponent }            from './priority/priorit
 import { PriorityAdministrationComponent }              from './priority/priority-administration.component';
 import { ReportsAdministrationComponent }               from './report/reports-administration.component';
 import { HistoryAdministrationComponent }               from './history/history-administration.component';
+import { HistoryBatchAdministrationComponent }               from './history/batch/history-batch-administration.component';
 import { UpdateStatusAdministrationComponent }          from './updateStatus/update-status-administration.component';
 import { NotificationsAdministrationComponent }         from './notification/notifications-administration.component';
 import { NotificationAdministrationComponent }          from './notification/notification-administration.component';
@@ -79,6 +80,7 @@ import { ContactsPageAdministrationComponent }              from './contact/page
         PriorityAdministrationComponent,
         ReportsAdministrationComponent,
         HistoryAdministrationComponent,
+        HistoryBatchAdministrationComponent,
         UpdateStatusAdministrationComponent,
         ContactsGroupsAdministrationComponent,
         ContactsGroupAdministrationComponent,
diff --git a/src/frontend/app/administration/history/batch/history-batch-administration.component.html b/src/frontend/app/administration/history/batch/history-batch-administration.component.html
new file mode 100644
index 00000000000..6a496d78dbb
--- /dev/null
+++ b/src/frontend/app/administration/history/batch/history-batch-administration.component.html
@@ -0,0 +1,161 @@
+<mat-sidenav-container autosize class="maarch-container">
+    <mat-sidenav #snav [mode]="appService.getViewMode() ? 'over' : 'side'" [fixedInViewport]="appService.getViewMode()"
+        [opened]="appService.getViewMode() ? false : true">
+        <header-panel [snavLeft]="snav"></header-panel>
+        <menu-shortcut></menu-shortcut>
+        <menu-nav></menu-nav>
+        <mat-nav-list>
+            <a mat-list-item *ngFor="let menu of subMenus" [class.active]="menu.current" [routerLink]="menu.route">
+                <mat-icon color="primary" mat-list-icon [class]="menu.icon"></mat-icon>
+                <p mat-line>
+                    {{menu.label}}
+                </p>
+            </a>
+        </mat-nav-list>
+        <mat-divider></mat-divider>
+    </mat-sidenav>
+    <mat-sidenav-content>
+        <div class="bg-head">
+            <div class="bg-head-title" [class.customContainerRight]="appService.getViewMode()">
+                <div class="bg-head-title-label">
+                    <header-left [snavLeft]="snav"></header-left>
+                </div>
+                <div class="bg-head-title-tool">
+                    <header-right></header-right>
+                </div>
+            </div>
+            <div class="bg-head-content" [class.fullContainer]="appService.getViewMode()">
+                <div style="display: grid;grid-template-columns: repeat(2, 1fr);grid-gap: 10px;width: 100%;">
+                    <mat-form-field (click)="startPicker.open()" style="cursor:pointer;" class="dateFilter">
+                        <mat-label style="color:white;">{{lang.since}}
+                        </mat-label>
+                        <input [(ngModel)]="startDateFilter" matInput [matDatepicker]="startPicker"
+                            [placeholder]="lang.since" [max]="endDateFilter" readonly style="cursor:pointer;"
+                            (dateChange)="filterStartDate()">
+                        <mat-datepicker-toggle matSuffix [for]="startPicker" *ngIf="!startDateFilter">
+                        </mat-datepicker-toggle>
+                        <mat-datepicker [touchUi]="appService.getViewMode()" #startPicker>
+                        </mat-datepicker>
+                        <button mat-button color="warn" matSuffix mat-icon-button *ngIf="startDateFilter"
+                            (click)="$event.stopPropagation();startDateFilter = '';filterStartDate()"
+                            [title]="lang.eraseValue">
+                            <mat-icon color="warn" class="fa fa-calendar-times">
+                            </mat-icon>
+                        </button>
+                    </mat-form-field>
+                    <mat-form-field (click)="endPicker.open()" style="cursor:pointer;" class="dateFilter">
+                        <mat-label style="color:white;">{{lang.until}}
+                        </mat-label>
+                        <input [(ngModel)]="endDateFilter" matInput [matDatepicker]="endPicker"
+                            [placeholder]="lang.until" [min]="startDateFilter" readonly style="cursor:pointer;"
+                            (dateChange)="filterEndDate()">
+                        <mat-datepicker-toggle matSuffix [for]="endPicker" *ngIf="!endDateFilter">
+                        </mat-datepicker-toggle>
+                        <mat-datepicker [touchUi]="appService.getViewMode()" #endPicker>
+                        </mat-datepicker>
+                        <button mat-button color="warn" matSuffix mat-icon-button *ngIf="endDateFilter"
+                            (click)="$event.stopPropagation();endDateFilter = '';filterEndDate()"
+                            [title]="lang.eraseValue">
+                            <mat-icon color="warn" class="fa fa-calendar-times">
+                            </mat-icon>
+                        </button>
+                    </mat-form-field>
+                </div>
+            </div>
+        </div>
+        <div class="container" [class.fullContainer]="appService.getViewMode()">
+            <div class="container-content">
+                <div class="example-loading-shade" *ngIf="isLoadingResults">
+                    <mat-spinner *ngIf="isLoadingResults"></mat-spinner>
+                </div>
+                <div class="table-head">
+                    <div class="table-head-result">
+                        <mat-form-field floatLabel="never" style="font-size: 13px;">
+                            <input type="text" #autoCompleteInput [matAutocomplete]="auto" [placeholder]="lang.filterBy"
+                                matInput [formControl]="searchHistory" (click)="$event.stopPropagation()"
+                                maxlength="128">
+                            <mat-autocomplete #auto="matAutocomplete" (optionSelected)="addItemFilter($event.option)"
+                                (opened)="initFilterListHistory()">
+                                <mat-option disabled *ngIf="loadingFilters">
+                                    <div style="display: flex;justify-content: center;">
+                                        <mat-spinner diameter="35"></mat-spinner>
+                                    </div>
+                                </mat-option>
+                                <ng-container *ngIf="filterList!==null && !loadingFilters">
+                                    <ng-container *ngFor="let keyVal of filterList | keyvalue">
+                                        <mat-optgroup *ngIf="(filteredList[keyVal.key] | async)?.length > 0"
+                                            [label]="lang[keyVal.key]" class="filterList">
+                                            <mat-option [id]="keyVal.key"
+                                                [style.color]="!filter.used ? filterColor[keyVal.key] : ''"
+                                                *ngFor="let filter of filteredList[keyVal.key] | async | sortBy : 'label'"
+                                                [value]="filter" [disabled]="filter.used">
+                                                {{filter.label}}
+                                            </mat-option>
+                                        </mat-optgroup>
+                                    </ng-container>
+                                </ng-container>
+
+                            </mat-autocomplete>
+                        </mat-form-field>
+                    </div>
+                    <div class="table-head-tool">
+                        <mat-paginator #paginatorHistoryList [length]="resultsLength" [hidePageSize]="true"
+                            [pageSize]="10" class="paginatorResultList"></mat-paginator>
+                    </div>
+                </div>
+                <div style="height:90%;overflow:auto;position:absolute;width:100%;">
+                    <div class="filterBadges">
+                        <ng-container *ngFor="let keyVal of filterUsed | keyvalue">
+                            <ng-container *ngIf="['startDate','endDate'].indexOf(keyVal.key) === -1">
+                                <span *ngFor="let filter of filterUsed[keyVal.key]; let i=index;" class="label"
+                                    [style.background]="filterColor[keyVal.key]" [title]="lang[keyVal.key]"
+                                    (click)="removeItemFilter(filter,keyVal.key,i)">{{filter.label}}
+                                    <i class="fa fa-times-circle"></i></span>
+                            </ng-container>
+                        </ng-container>
+                    </div>
+                    <mat-table id="history-list" #tableHistoryListSort="matSort" [dataSource]="data" matSort
+                        matSortActive="event_date" matSortDirection="desc" style="width:100%;">
+                        <ng-container matColumnDef="event_date">
+                            <mat-header-cell *matHeaderCellDef mat-sort-header>{{lang.event}}</mat-header-cell>
+                            <mat-cell mat-cell *matCellDef="let element" [title]="element.event_date | fullDate">
+                                {{element.event_date  | timeAgo : 'full' | ucfirst}} </mat-cell>
+                        </ng-container>
+                        <ng-container matColumnDef="total_processed">
+                            <mat-header-cell *matHeaderCellDef mat-sort-header>{{lang.totalProcessed | ucfirst}}
+                            </mat-header-cell>
+                            <mat-cell *matCellDef="let element" [class.empty]="element.total_processed === 0">
+                                {{element.total_processed}} </mat-cell>
+                        </ng-container>
+                        <ng-container matColumnDef="total_errors">
+                            <mat-header-cell *matHeaderCellDef mat-sort-header>{{lang.totalErrors}}
+                            </mat-header-cell>
+                            <mat-cell *matCellDef="let element" [class.empty]="element.total_errors === 0" [class.error]="element.total_errors > 0">
+                                {{element.total_errors}} </mat-cell>
+                        </ng-container>
+                        <ng-container matColumnDef="info">
+                            <mat-header-cell *matHeaderCellDef mat-sort-header style="flex: 2;">{{lang.information}}
+                            </mat-header-cell>
+                            <mat-cell *matCellDef="let element" style="flex: 2;">
+                                {{element.info}} </mat-cell>
+                        </ng-container>
+                        <ng-container matColumnDef="module_name">
+                            <mat-header-cell *matHeaderCellDef mat-sort-header>{{lang.module}}
+                            </mat-header-cell>
+                            <mat-cell *matCellDef="let element">
+                                {{element.module_name}} </mat-cell>
+                        </ng-container>
+                        <mat-header-row *matHeaderRowDef="displayedColumnsHistory"></mat-header-row>
+                        <mat-row *matRowDef="let row; columns: displayedColumnsHistory;">
+                        </mat-row>
+                    </mat-table>
+                    <div class="mat-paginator"
+                        style="min-height:48px;min-height: 48px;display: flex;justify-content: end;align-items: center;padding-right: 20px;">
+                        {{resultsLength}} {{lang.elements}}</div>
+                </div>
+                <div class="table-head">
+                </div>
+            </div>
+        </div>
+    </mat-sidenav-content>
+</mat-sidenav-container>
\ No newline at end of file
diff --git a/src/frontend/app/administration/history/batch/history-batch-administration.component.scss b/src/frontend/app/administration/history/batch/history-batch-administration.component.scss
new file mode 100644
index 00000000000..475b4db0990
--- /dev/null
+++ b/src/frontend/app/administration/history/batch/history-batch-administration.component.scss
@@ -0,0 +1,63 @@
+@import '../../../../css/vars.scss';
+
+.active,
+.active:hover,
+.active:active,
+.active:focus {
+    color: $primary;
+    border-left: solid 5px $primary;
+    background: rgba($primary, 0.14);
+}
+
+
+.paginatorResultList {
+    ::ng-deep.mat-paginator-range-label {
+        justify-content: flex-end;
+        display: flex;
+    }
+}
+
+.filterList {
+    ::ng-deep.mat-optgroup-label {
+        color: $primary;
+        position: sticky;
+        top: 0px;
+        background: white !important;
+        z-index: 1;
+    }
+}
+
+.label {
+    cursor: pointer;
+    margin: 5px;
+}
+
+.bg-head-content {
+    ::ng-deep .mat-focused .mat-form-field-label {
+        /*change color of label*/
+        color: white !important;
+    }
+
+    ::ng-deep.mat-form-field-underline {
+        /*change color of underline*/
+        background-color: white !important;
+    }
+
+    ::ng-deep.mat-form-field-ripple {
+        /*change color of underline when focused*/
+        background-color: white !important;
+    }
+
+    .mat-icon,.mat-datepicker-toggle {
+        color:white;
+    }
+}
+
+.empty {
+    opacity: 0.5;
+}
+
+.error {
+    color: $warn;
+    font-weight: bold;
+}
\ No newline at end of file
diff --git a/src/frontend/app/administration/history/batch/history-batch-administration.component.ts b/src/frontend/app/administration/history/batch/history-batch-administration.component.ts
new file mode 100644
index 00000000000..193237f3cf2
--- /dev/null
+++ b/src/frontend/app/administration/history/batch/history-batch-administration.component.ts
@@ -0,0 +1,294 @@
+import { Component, OnInit, ViewChild, EventEmitter, ElementRef } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { LANG } from '../../../translate.component';
+import { NotificationService } from '../../../notification.service';
+import { HeaderService } from '../../../../service/header.service';
+import { MatSidenav } from '@angular/material/sidenav';
+import { AppService } from '../../../../service/app.service';
+import { Observable, merge, Subject, of as observableOf, of } from 'rxjs';
+import { MatPaginator, MatSort, MatDialog } from '@angular/material';
+import { takeUntil, startWith, switchMap, map, catchError, filter, exhaustMap, tap, debounceTime, distinctUntilChanged, finalize } from 'rxjs/operators';
+import { FormControl } from '@angular/forms';
+import { FunctionsService } from '../../../../service/functions.service';
+import { LatinisePipe } from 'ngx-pipes';
+import { PrivilegeService } from '../../../../service/privileges.service';
+
+@Component({
+    templateUrl: "history-batch-administration.component.html",
+    styleUrls: ['history-batch-administration.component.scss'],
+    providers: [AppService]
+})
+export class HistoryBatchAdministrationComponent implements OnInit {
+
+    @ViewChild('snav', { static: true }) public sidenavLeft: MatSidenav;
+    @ViewChild('snav2', { static: true }) public sidenavRight: MatSidenav;
+
+    lang: any = LANG;
+    loading: boolean = false;
+
+    filtersChange = new EventEmitter();
+
+    data: any;
+
+    displayedColumnsHistory: string[] = ['event_date', 'total_processed', 'total_errors', 'info', 'module_name'];
+
+    isLoadingResults = true;
+    routeUrl: string = '../../rest/batchHistory';
+    resultListDatabase: HistoryListHttpDao | null;
+    resultsLength = 0;
+
+    searchHistory = new FormControl();
+    startDateFilter: any = '';
+    endDateFilter: any = '';
+    filterUrl: string = '';
+    filterList: any = null;
+    filteredList: any = {};
+    filterUsed: any = {};
+
+    filterColor = {
+        startDate: '#b5cfd8',
+        endDate: '#7393a7',
+        errors: '#7d5ba6'
+    };
+
+    loadingFilters: boolean = true;
+
+    @ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
+    @ViewChild('tableHistoryListSort', { static: true }) sort: MatSort;
+    @ViewChild('autoCompleteInput', { static: true }) autoCompleteInput: ElementRef;
+
+    private destroy$ = new Subject<boolean>();
+
+    subMenus: any[] = [];
+
+    constructor(
+        public http: HttpClient,
+        private notify: NotificationService,
+        private headerService: HeaderService,
+        public appService: AppService,
+        public dialog: MatDialog,
+        public functions: FunctionsService,
+        private latinisePipe: LatinisePipe,
+        private privilegeService: PrivilegeService) { }
+
+    ngOnInit(): void {
+        if (this.privilegeService.hasCurrentUserPrivilege('view_history')) {
+            this.subMenus = [
+                {
+                    icon: 'fa fa-history',
+                    route: '/administration/history',
+                    label: this.lang.history,
+                    current: false
+                },
+                {
+                    icon: 'fa fa-history',
+                    route: '/administration/history-batch',
+                    label: this.lang.historyBatch,
+                    current: true
+                }
+            ];
+        } else {
+            this.subMenus = [
+                {
+                    icon: 'fa fa-history',
+                    route: '/administration/history-batch',
+                    label: this.lang.historyBatch,
+                    current: true
+                }
+            ];
+        }
+        this.loading = true;
+        this.initHistoryList();
+    }
+
+    initHistoryList() {
+        this.resultListDatabase = new HistoryListHttpDao(this.http);
+        this.paginator.pageIndex = 0;
+        this.sort.active = 'event_date';
+        this.sort.direction = 'desc';
+        this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0);
+
+        // When list is refresh (sort, page, filters)
+        merge(this.sort.sortChange, this.paginator.page, this.filtersChange)
+            .pipe(
+                takeUntil(this.destroy$),
+                startWith({}),
+                switchMap(() => {
+                    this.isLoadingResults = true;
+                    return this.resultListDatabase!.getRepoIssues(
+                        this.sort.active, this.sort.direction, this.paginator.pageIndex, this.routeUrl, this.filterUrl);
+                }),
+                map(data => {
+                    this.isLoadingResults = false;
+                    data = this.processPostData(data);
+                    this.resultsLength = data.count;
+                    this.headerService.setHeader(this.lang.administration + ' ' + this.lang.historyBatch.toLowerCase(), '', '');
+                    return data.history;
+                }),
+                catchError((err: any) => {
+                    this.notify.handleErrors(err);
+                    this.isLoadingResults = false;
+                    return observableOf([]);
+                })
+            ).subscribe(data => this.data = data);
+    }
+
+    processPostData(data: any) {
+        data.history = data.history.map((item: any) => {
+            return {
+                ...item,
+                total_errors : item.total_errors === null ? 0 : item.total_errors
+            }
+        })
+        return data;
+    }
+
+
+    refreshDao() {
+        this.paginator.pageIndex = 0;
+        this.filtersChange.emit();
+    }
+
+    initFilterListHistory() {
+
+        if (this.filterList === null) {
+            this.filterList = {};
+            this.loadingFilters = true;
+
+            this.http.get("../../rest/history/availableFilters").pipe(
+                map((data: any) => {
+                    data = {};
+                    data.modules = [
+                        {
+                            id : 'retrieveMailsFromSignatoryBook',
+                            label : 'retrieveMailsFromSignatoryBook'
+                        }
+                    ];
+
+                    data.totalErrors = [
+                        {
+                            id : 'errorElement',
+                            label : 'Éléments en erreur'
+                        }
+                    ];
+
+                    return data;
+                }),
+                tap((data: any) => {
+                    Object.keys(data).forEach((filterType: any) => {
+                        if (this.functions.empty(this.filterList[filterType])) {
+                            this.filterList[filterType] = [];
+                            this.filteredList[filterType] = [];
+                        }
+                        data[filterType].forEach((element: any) => {
+                            this.filterList[filterType].push(element);
+                        });
+
+                        this.filteredList[filterType] = this.searchHistory.valueChanges
+                            .pipe(
+                                startWith(''),
+                                map(element => element ? this.filter(element, filterType) : this.filterList[filterType].slice())
+                            );
+                    });
+
+                }),
+                finalize(() => this.loadingFilters = false),
+                catchError((err: any) => {
+                    this.notify.handleSoftErrors(err);
+                    return of(false);
+                })
+            ).subscribe();
+
+        }
+    }
+
+
+    filterStartDate() {
+        if (this.functions.empty(this.filterUsed['startDate'])) {
+            this.filterUsed['startDate'] = [];
+        }
+        this.filterUsed['startDate'][0] = {
+            id: this.functions.empty(this.startDateFilter) ? '' : this.functions.formatDateObjectToDateString(this.startDateFilter),
+            label: this.functions.empty(this.startDateFilter) ? '' : this.functions.formatDateObjectToDateString(this.startDateFilter)
+        };
+        this.generateUrlFilter();
+        this.refreshDao();
+    }
+
+    filterEndDate() {
+        if (this.functions.empty(this.filterUsed['endDate'])) {
+            this.filterUsed['endDate'] = [];
+        }
+        this.filterUsed['endDate'][0] = {
+            id: this.functions.empty(this.endDateFilter) ? '' : this.functions.formatDateObjectToDateString(this.endDateFilter, true),
+            label: this.functions.empty(this.endDateFilter) ? '' : this.functions.formatDateObjectToDateString(this.endDateFilter)
+        };
+        this.generateUrlFilter();
+        this.refreshDao();
+    }
+
+    addItemFilter(elem: any) {
+        elem.value.used = true;
+        if (this.functions.empty(this.filterUsed[elem.id])) {
+            this.filterUsed[elem.id] = [];
+        }
+        this.filterUsed[elem.id].push(elem.value);
+        this.generateUrlFilter();
+        this.searchHistory.reset();
+        this.autoCompleteInput.nativeElement.blur();
+        this.refreshDao();
+    }
+
+    removeItemFilter(elem: any, type: string, index: number) {
+        elem.used = false;
+        this.filterUsed[type].splice(index, 1);
+        this.generateUrlFilter();
+        this.refreshDao();
+    }
+
+
+    generateUrlFilter() {
+        this.filterUrl = '';
+        let arrTmpUrl: any[] = [];
+        Object.keys(this.filterUsed).forEach((type: any) => {
+            this.filterUsed[type].forEach((filter: any) => {
+                if (!this.functions.empty(filter.id)) {
+                    if (['startDate', 'endDate'].indexOf(type) > -1) {
+                        arrTmpUrl.push(`${type}=${filter.id}`);
+                    } else {
+                        arrTmpUrl.push(`${type}[]=${filter.id}`);
+                    }
+                }
+            });
+        });
+        if (arrTmpUrl.length > 0) {
+            this.filterUrl = '&' + arrTmpUrl.join('&');
+        }
+    }
+
+    private filter(value: string, type: string): any[] {
+        if (typeof value === 'string') {
+            const filterValue = this.latinisePipe.transform(value.toLowerCase());
+            return this.filterList[type].filter((elem: any) => this.latinisePipe.transform(elem.label.toLowerCase()).includes(filterValue));
+        } else {
+            return this.filterList[type];
+        }
+    }
+}
+
+export interface HistoryList {
+    history: any[];
+    count: number;
+}
+export class HistoryListHttpDao {
+
+    constructor(private http: HttpClient) { }
+
+    getRepoIssues(sort: string, order: string, page: number, href: string, search: string): Observable<HistoryList> {
+
+        let offset = page * 10;
+        const requestUrl = `${href}?limit=10&offset=${offset}&order=${order}&orderBy=${sort}${search}`;
+
+        return this.http.get<HistoryList>(requestUrl);
+    }
+}
\ No newline at end of file
diff --git a/src/frontend/service/privileges.service.ts b/src/frontend/service/privileges.service.ts
index 68b7ca039f5..6e4a7311808 100644
--- a/src/frontend/service/privileges.service.ts
+++ b/src/frontend/service/privileges.service.ts
@@ -271,7 +271,7 @@ export class PrivilegeService {
             "id": "view_history_batch",
             "label": this.lang.historyBatch,
             "comment": this.lang.historyBatchAdmin,
-            "route": "/administration/history",
+            "route": "/administration/history-batch",
             "unit": "supervision",
             "style": "fa fa-history",
             "angular" : true,
-- 
GitLab