107873: Add process overview page tables

This commit is contained in:
Andreas Awouters
2024-01-02 08:31:53 +01:00
parent 86cad57845
commit 944b614514
8 changed files with 275 additions and 55 deletions

View File

@@ -13,46 +13,26 @@
</button>
<button class="btn btn-success" routerLink="/processes/new"><i
class="fas fa-plus pr-2"></i>{{'process.overview.new' | translate}}</button>
</div>
<ds-pagination *ngIf="(processesRD$ | async)?.payload?.totalElements > 0"
[paginationOptions]="pageConfig"
[pageInfoState]="(processesRD$ | async)?.payload"
[collectionSize]="(processesRD$ | async)?.payload?.totalElements"
[hideGear]="true"
[hidePagerWhenSinglePage]="true">
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr>
<th scope="col">{{'process.overview.table.id' | translate}}</th>
<th scope="col">{{'process.overview.table.name' | translate}}</th>
<th scope="col">{{'process.overview.table.user' | translate}}</th>
<th scope="col">{{'process.overview.table.start' | translate}}</th>
<th scope="col">{{'process.overview.table.finish' | translate}}</th>
<th scope="col">{{'process.overview.table.status' | translate}}</th>
<th scope="col">{{'process.overview.table.actions' | translate}}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let process of (processesRD$ | async)?.payload?.page"
[class.table-danger]="processBulkDeleteService.isToBeDeleted(process.processId)">
<td><a [routerLink]="['/processes/', process.processId]">{{process.processId}}</a></td>
<td><a [routerLink]="['/processes/', process.processId]">{{process.scriptName}}</a></td>
<td *ngVar="(getEpersonName(process.userId) | async) as ePersonName">{{ePersonName}}</td>
<td>{{process.startTime | date:dateFormat:'UTC'}}</td>
<td>{{process.endTime | date:dateFormat:'UTC'}}</td>
<td>{{process.processStatus}}</td>
<td>
<button class="btn btn-outline-danger"
(click)="processBulkDeleteService.toggleDelete(process.processId)"><i
class="fas fa-trash"></i></button>
</td>
</tr>
</tbody>
</table>
</div>
</ds-pagination>
<div class="sections">
<ds-process-overview-table
[processStatus]="ProcessStatus.RUNNING"
[useAutoRefreshingSearchBy]="true"
[getInfoValueMethod]="processOverviewService.timeStarted"/>
<ds-process-overview-table
[processStatus]="ProcessStatus.SCHEDULED"
[useAutoRefreshingSearchBy]="true"
[getInfoValueMethod]="processOverviewService.timeStarted"/>
<ds-process-overview-table
[processStatus]="ProcessStatus.COMPLETED"
[useAutoRefreshingSearchBy]="true"
[getInfoValueMethod]="processOverviewService.timeCompleted"/>
<ds-process-overview-table
[processStatus]="ProcessStatus.FAILED"
[useAutoRefreshingSearchBy]="true"
[getInfoValueMethod]="processOverviewService.timeCompleted"/>
</div>
</div>
<ng-template #deleteModal>
@@ -88,4 +68,3 @@
</ng-template>

View File

@@ -5,9 +5,7 @@ import { PaginatedList } from '../../core/data/paginated-list.model';
import { Process } from '../processes/process.model';
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
import { EPersonDataService } from '../../core/eperson/eperson-data.service';
import { getFirstSucceededRemoteDataPayload } from '../../core/shared/operators';
import { EPerson } from '../../core/eperson/models/eperson.model';
import { map, switchMap } from 'rxjs/operators';
import { switchMap } from 'rxjs/operators';
import { ProcessDataService } from '../../core/data/processes/process-data.service';
import { PaginationService } from '../../core/pagination/pagination.service';
import { FindListOptions } from '../../core/data/find-list-options.model';
@@ -15,6 +13,8 @@ import { ProcessBulkDeleteService } from './process-bulk-delete.service';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { hasValue } from '../../shared/empty.util';
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
import { ProcessOverviewService } from './process-overview.service';
import { ProcessStatus } from '../processes/process-status.model';
@Component({
selector: 'ds-process-overview',
@@ -25,6 +25,8 @@ import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
*/
export class ProcessOverviewComponent implements OnInit, OnDestroy {
protected readonly ProcessStatus = ProcessStatus;
/**
* List of all processes
*/
@@ -56,6 +58,7 @@ export class ProcessOverviewComponent implements OnInit, OnDestroy {
isProcessingSub: Subscription;
constructor(protected processService: ProcessDataService,
protected processOverviewService: ProcessOverviewService,
protected paginationService: PaginationService,
protected ePersonService: EPersonDataService,
protected modalService: NgbModal,
@@ -78,17 +81,6 @@ export class ProcessOverviewComponent implements OnInit, OnDestroy {
);
}
/**
* Get the name of an EPerson by ID
* @param id ID of the EPerson
*/
getEpersonName(id: string): Observable<string> {
return this.ePersonService.findById(id).pipe(
getFirstSucceededRemoteDataPayload(),
map((eperson: EPerson) => this.dsoNameService.getName(eperson)),
);
}
ngOnDestroy(): void {
this.paginationService.clearPagination(this.pageConfig.id);
if (hasValue(this.isProcessingSub)) {

View File

@@ -0,0 +1,72 @@
import { Injectable } from '@angular/core';
import { ProcessDataService } from '../../core/data/processes/process-data.service';
import { FindListOptions } from '../../core/data/find-list-options.model';
import { Observable } from 'rxjs';
import { RemoteData } from '../../core/data/remote-data';
import { PaginatedList } from '../../core/data/paginated-list.model';
import { Process } from '../processes/process.model';
import { RequestParam } from '../../core/cache/models/request-param.model';
import { ProcessStatus } from '../processes/process-status.model';
import { DatePipe } from '@angular/common';
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
/**
* Service to retrieve
*/
@Injectable({
providedIn: 'root',
})
export class ProcessOverviewService {
constructor(protected processDataService: ProcessDataService) {
}
/**
* Date format to use for start and end time of processes
*/
dateFormat = 'yyyy-MM-dd HH:mm:ss';
datePipe = new DatePipe('en-US');
timeCompleted = (process: Process) => this.datePipe.transform(process.endTime, this.dateFormat, 'UTC');
timeStarted = (process: Process) => this.datePipe.transform(process.startTime, this.dateFormat, 'UTC');
/**
* Retrieve processes by their status
* @param processStatus The status for which to retrieve processes
* @param findListOptions The FindListOptions object
* @param autoRefreshingIntervalInMs Optional: The interval by which to automatically refresh the retrieved processes.
* Leave empty or set to null to only retrieve the processes once.
*/
getProcessesByProcessStatus(processStatus: ProcessStatus, findListOptions?: FindListOptions, autoRefreshingIntervalInMs: number = null): Observable<RemoteData<PaginatedList<Process>>> {
let requestParam = new RequestParam('processStatus', processStatus);
let options: FindListOptions = Object.assign(new FindListOptions(), {
searchParams: [requestParam],
elementsPerPage: 5,
}, findListOptions);
if (autoRefreshingIntervalInMs !== null && autoRefreshingIntervalInMs > 0) {
return this.processDataService.autoRefreshingSearchBy('byProperty', options, autoRefreshingIntervalInMs);
} else {
return this.processDataService.searchBy('byProperty', options);
}
}
/**
* Map the provided paginationOptions to FindListOptions
* @param paginationOptions the PaginationComponentOptions to map
*/
getFindListOptions(paginationOptions: PaginationComponentOptions): FindListOptions {
return Object.assign(
new FindListOptions(),
{
currentPage: paginationOptions.currentPage,
elementsPerPage: paginationOptions.pageSize,
}
);
}
}

View File

@@ -0,0 +1,54 @@
<div class="container">
<div class="d-flex dropdown-toggle" (click)="collapse.toggle()" [attr.aria-expanded]="!collapse.collapsed" role="button">
<h2 class="flex-grow-1">{{'process.overview.table.' + processStatus.toLowerCase() + '.title' | translate}}</h2>
</div>
<div ngbCollapse #collapse="ngbCollapse">
<ng-container *ngVar="(processesRD$ | async) as processesRD">
<ds-themed-loading *ngIf="!processesRD || processesRD.isLoading"/>
<ds-pagination *ngIf="processesRD?.payload?.totalElements > 0"
[paginationOptions]="(paginationOptions$ | async)"
[collectionSize]="processesRD?.payload?.totalElements"
[retainScrollPosition]="true">
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr>
<th scope="col">{{'process.overview.table.status' | translate}}</th>
<th scope="col">{{'process.overview.table.name' | translate}}</th>
<th scope="col">{{'process.overview.table.user' | translate}}</th>
<th scope="col">{{'process.overview.table.' + processStatus.toLowerCase() + '.info' | translate}}</th>
<th scope="col">{{'process.overview.table.actions' | translate}}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let process of processesRD?.payload?.page"
[class.table-danger]="processBulkDeleteService.isToBeDeleted(process.processId)">
<td>{{process.processStatus}}</td>
<td><a [routerLink]="['/processes/', process.processId]">{{process.scriptName}}</a></td>
<td *ngVar="(getEPersonName(process.userId) | async) as ePersonName">{{ePersonName}}</td>
<td>{{getInfoValueMethod(process)}}</td>
<td>
<button class="btn btn-outline-danger"
(click)="processBulkDeleteService.toggleDelete(process.processId)">
<i class="fas fa-trash"></i>
</button>
</td>
</tr>
</tbody>
</table>
</div>
</ds-pagination>
<div *ngIf="processesRD?.payload?.totalElements == 0">
<p>{{'process.overview.table.empty' | translate}}</p>
</div>
</ng-container>
</div>
</div>

View File

@@ -0,0 +1,99 @@
import { Component, Input, OnInit } from '@angular/core';
import { ProcessStatus } from '../../processes/process-status.model';
import { Observable } from 'rxjs';
import { RemoteData } from '../../../core/data/remote-data';
import { PaginatedList } from '../../../core/data/paginated-list.model';
import { Process } from '../../processes/process.model';
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
import { ProcessOverviewService } from '../process-overview.service';
import { ProcessBulkDeleteService } from '../process-bulk-delete.service';
import { EPersonDataService } from '../../../core/eperson/eperson-data.service';
import { DSONameService } from '../../../core/breadcrumbs/dso-name.service';
import { getFirstSucceededRemoteDataPayload } from '../../../core/shared/operators';
import { map, switchMap } from 'rxjs/operators';
import { EPerson } from '../../../core/eperson/models/eperson.model';
import { PaginationService } from 'src/app/core/pagination/pagination.service';
import { FindListOptions } from '../../../core/data/find-list-options.model';
@Component({
selector: 'ds-process-overview-table',
templateUrl: './process-overview-table.component.html'
})
export class ProcessOverviewTableComponent implements OnInit {
/**
* The status of the processes this sections should show
*/
@Input() processStatus: ProcessStatus;
/**
* Whether to use auto refresh for the processes shown in this table.
*/
@Input() useAutoRefreshingSearchBy = false;
/**
* The interval by which to refresh if autoRefreshing is enabled
*/
@Input() autoRefreshInterval = 5000;
/**
* The function used to retrieve the value that will be shown in the 'info' column of the table.
* {@Link ProcessOverviewService} contains some predefined functions.
*/
@Input() getInfoValueMethod: (process: Process) => string;
/**
* List of processes to be shown in this table
*/
processesRD$: Observable<RemoteData<PaginatedList<Process>>>;
/**
* The pagination ID for this overview section
*/
paginationId: string;
/**
* The current pagination options for the overview section
*/
paginationOptions$: Observable<PaginationComponentOptions>;
constructor(protected processOverviewService: ProcessOverviewService,
protected processBulkDeleteService: ProcessBulkDeleteService,
protected ePersonDataService: EPersonDataService,
protected dsoNameService: DSONameService,
protected paginationService: PaginationService) {
}
ngOnInit() {
this.paginationId = 'processOverviewTable' + this.processStatus;
let defaultPaginationOptions = Object.assign(new PaginationComponentOptions(), {
id: this.paginationId,
pageSize: 5,
});
this.paginationOptions$ = this.paginationService.getCurrentPagination(this.paginationId, defaultPaginationOptions);
this.processesRD$ = this.paginationOptions$
.pipe(
map((paginationOptions: PaginationComponentOptions) =>
this.processOverviewService.getFindListOptions(paginationOptions)),
switchMap(
(findListOptions: FindListOptions) => {
return this.processOverviewService.getProcessesByProcessStatus(
this.processStatus, findListOptions, this.useAutoRefreshingSearchBy ? this.autoRefreshInterval : null);
}
));
}
/**
* Get the name of an EPerson by ID
* @param id ID of the EPerson
*/
getEPersonName(id: string): Observable<string> {
return this.ePersonDataService.findById(id).pipe(
getFirstSucceededRemoteDataPayload(),
map((eperson: EPerson) => this.dsoNameService.getName(eperson)),
);
}
}

View File

@@ -16,10 +16,14 @@ import { ProcessDetailFieldComponent } from './detail/process-detail-field/proce
import { ProcessBreadcrumbsService } from './process-breadcrumbs.service';
import { ProcessBreadcrumbResolver } from './process-breadcrumb.resolver';
import { ProcessFormComponent } from './form/process-form.component';
import { NgbCollapseModule } from '@ng-bootstrap/ng-bootstrap';
import { ProcessOverviewTableComponent } from './overview/table/process-overview-table.component';
import { DatePipe } from '@angular/common';
@NgModule({
imports: [
SharedModule,
NgbCollapseModule,
],
declarations: [
NewProcessComponent,
@@ -33,13 +37,15 @@ import { ProcessFormComponent } from './form/process-form.component';
BooleanValueInputComponent,
DateValueInputComponent,
ProcessOverviewComponent,
ProcessOverviewTableComponent,
ProcessDetailComponent,
ProcessDetailFieldComponent,
ProcessFormComponent
],
providers: [
ProcessBreadcrumbResolver,
ProcessBreadcrumbsService
ProcessBreadcrumbsService,
DatePipe,
]
})

View File

@@ -3218,12 +3218,30 @@
"process.detail.refreshing": "Auto-refreshing…",
"process.overview.table.completed.info": "Finish time (UTC)",
"process.overview.table.completed.title": "Completed processes",
"process.overview.table.empty": "No matching processes found.",
"process.overview.table.failed.info": "Finish time (UTC)",
"process.overview.table.failed.title": "Failed processes",
"process.overview.table.finish": "Finish time (UTC)",
"process.overview.table.id": "Process ID",
"process.overview.table.name": "Name",
"process.overview.table.running.info": "Start time (UTC)",
"process.overview.table.running.title": "Running processes",
"process.overview.table.scheduled.info": "Start time (UTC)",
"process.overview.table.scheduled.title": "Scheduled processes",
"process.overview.table.start": "Start time (UTC)",
"process.overview.table.status": "Status",