From cef8273fe300f130876e2ef7daa179ae0171f4f9 Mon Sep 17 00:00:00 2001 From: faustin Date: Thu, 6 Feb 2025 16:15:58 +0100 Subject: [PATCH 01/47] feat: table groups --- src/app/components/table/table.component.html | 9 +- src/app/services/invert-filter.service.ts | 164 ++++++++++++++++++ src/app/sessions/index.component.ts | 2 + .../services/sessions-data.service.ts | 11 ++ src/app/types/groups.ts | 14 ++ src/app/types/services/data-filter.service.ts | 6 +- 6 files changed, 203 insertions(+), 3 deletions(-) create mode 100644 src/app/services/invert-filter.service.ts create mode 100644 src/app/types/groups.ts diff --git a/src/app/components/table/table.component.html b/src/app/components/table/table.component.html index 825bf0d5f..b24948fcf 100644 --- a/src/app/components/table/table.component.html +++ b/src/app/components/table/table.component.html @@ -1,9 +1,13 @@ - + + + + @for (column of columns; track column.key) { @@ -35,7 +39,8 @@ - + +
test
{ + private readonly dataFilterService = inject(DataFilterService); + + private readonly emptyFilter: Filter = { + for: null, + field: null, + operator: null, + value: null, + }; + + invert(filters: FiltersOr): FiltersOr { + return filters.map((filterAnd) => filterAnd.map((filter) => this.invertFilter(filter))); + } + + private invertFilter(filter: Filter) { + const type = this.dataFilterService.getType(filter); + switch (type) { + case 'string': + return this.invertStringFilter(filter); + case 'number': + return this.invertNumberFilter(filter); + case 'array': + return this.invertArrayFilter(filter); + case 'boolean': + return this.invertBooleanFilter(filter); + case 'date': + return this.invertDateFilter(filter); + case 'duration': + return this.invertDurationFilter(filter); + case 'status': + return this.invertStatusFilter(filter); + default: + return { + ...this.emptyFilter + }; + } + } + + private updateOperator(filter: Filter, operator: FilterOperators): Filter { + return { + ...filter, + operator + }; + } + + private invertStringFilter(filter: Filter) { + switch(filter.operator) { + case FilterStringOperator.FILTER_STRING_OPERATOR_CONTAINS: + return this.updateOperator(filter, FilterStringOperator.FILTER_STRING_OPERATOR_NOT_CONTAINS); + case FilterStringOperator.FILTER_STRING_OPERATOR_NOT_CONTAINS: + return this.updateOperator(filter, FilterStringOperator.FILTER_STRING_OPERATOR_CONTAINS); + case FilterStringOperator.FILTER_STRING_OPERATOR_EQUAL: + return this.updateOperator(filter, FilterStringOperator.FILTER_STRING_OPERATOR_NOT_EQUAL); + case FilterStringOperator.FILTER_STRING_OPERATOR_NOT_EQUAL: + return this.updateOperator(filter, FilterStringOperator.FILTER_STRING_OPERATOR_EQUAL); + default: + return { + ...this.emptyFilter + }; + } + } + + private invertNumberFilter(filter: Filter) { + switch(filter.operator) { + case FilterNumberOperator.FILTER_NUMBER_OPERATOR_EQUAL: + return this.updateOperator(filter, FilterNumberOperator.FILTER_NUMBER_OPERATOR_NOT_EQUAL); + case FilterNumberOperator.FILTER_NUMBER_OPERATOR_NOT_EQUAL: + return this.updateOperator(filter, FilterNumberOperator.FILTER_NUMBER_OPERATOR_EQUAL); + case FilterNumberOperator.FILTER_NUMBER_OPERATOR_GREATER_THAN: + return this.updateOperator(filter, FilterNumberOperator.FILTER_NUMBER_OPERATOR_LESS_THAN_OR_EQUAL); + case FilterNumberOperator.FILTER_NUMBER_OPERATOR_GREATER_THAN_OR_EQUAL: + return this.updateOperator(filter, FilterNumberOperator.FILTER_NUMBER_OPERATOR_LESS_THAN); + case FilterNumberOperator.FILTER_NUMBER_OPERATOR_LESS_THAN: + return this.updateOperator(filter, FilterNumberOperator.FILTER_NUMBER_OPERATOR_GREATER_THAN_OR_EQUAL); + case FilterNumberOperator.FILTER_NUMBER_OPERATOR_LESS_THAN_OR_EQUAL: + return this.updateOperator(filter, FilterNumberOperator.FILTER_NUMBER_OPERATOR_GREATER_THAN); + default: + return { + ...this.emptyFilter + }; + } + } + + private invertDateFilter(filter: Filter) { + switch(filter.operator) { + case FilterDateOperator.FILTER_DATE_OPERATOR_EQUAL: + return this.updateOperator(filter, FilterDateOperator.FILTER_DATE_OPERATOR_NOT_EQUAL); + case FilterDateOperator.FILTER_DATE_OPERATOR_NOT_EQUAL: + return this.updateOperator(filter, FilterDateOperator.FILTER_DATE_OPERATOR_EQUAL); + case FilterDateOperator.FILTER_DATE_OPERATOR_AFTER: + return this.updateOperator(filter, FilterDateOperator.FILTER_DATE_OPERATOR_BEFORE_OR_EQUAL); + case FilterDateOperator.FILTER_DATE_OPERATOR_AFTER_OR_EQUAL: + return this.updateOperator(filter, FilterDateOperator.FILTER_DATE_OPERATOR_BEFORE); + case FilterDateOperator.FILTER_DATE_OPERATOR_BEFORE: + return this.updateOperator(filter, FilterDateOperator.FILTER_DATE_OPERATOR_AFTER_OR_EQUAL); + case FilterDateOperator.FILTER_DATE_OPERATOR_BEFORE_OR_EQUAL: + return this.updateOperator(filter, FilterDateOperator.FILTER_DATE_OPERATOR_AFTER); + default: + return { + ...this.emptyFilter + }; + } + } + + private invertArrayFilter(filter: Filter) { + switch(filter.operator as FilterArrayOperator) { + case FilterArrayOperator.FILTER_ARRAY_OPERATOR_CONTAINS: + return this.updateOperator(filter, FilterArrayOperator.FILTER_ARRAY_OPERATOR_NOT_CONTAINS); + case FilterArrayOperator.FILTER_ARRAY_OPERATOR_NOT_CONTAINS: + return this.updateOperator(filter, FilterArrayOperator.FILTER_ARRAY_OPERATOR_CONTAINS); + default: + return { + ...this.emptyFilter + }; + } + } + + private invertStatusFilter(filter: Filter) { + switch(filter.operator as FilterStatusOperator) { + case FilterStatusOperator.FILTER_STATUS_OPERATOR_EQUAL: + return this.updateOperator(filter, FilterStatusOperator.FILTER_STATUS_OPERATOR_NOT_EQUAL); + case FilterStatusOperator.FILTER_STATUS_OPERATOR_NOT_EQUAL: + return this.updateOperator(filter, FilterStatusOperator.FILTER_STATUS_OPERATOR_EQUAL); + default: + return { + ...this.emptyFilter + }; + } + } + + private invertBooleanFilter(filter: Filter) { + return { + ...filter, + value: !filter.value + }; + } + + private invertDurationFilter(filter: Filter) { + switch(filter.operator as FilterDurationOperator) { + case FilterDurationOperator.FILTER_DURATION_OPERATOR_EQUAL: + return this.updateOperator(filter, FilterDurationOperator.FILTER_DURATION_OPERATOR_NOT_EQUAL); + case FilterDurationOperator.FILTER_DURATION_OPERATOR_NOT_EQUAL: + return this.updateOperator(filter, FilterDurationOperator.FILTER_DURATION_OPERATOR_EQUAL); + case FilterDurationOperator.FILTER_DURATION_OPERATOR_LONGER_THAN: + return this.updateOperator(filter, FilterDurationOperator.FILTER_DURATION_OPERATOR_SHORTER_THAN_OR_EQUAL); + case FilterDurationOperator.FILTER_DURATION_OPERATOR_LONGER_THAN_OR_EQUAL: + return this.updateOperator(filter, FilterDurationOperator.FILTER_DURATION_OPERATOR_SHORTER_THAN); + case FilterDurationOperator.FILTER_DURATION_OPERATOR_SHORTER_THAN: + return this.updateOperator(filter, FilterDurationOperator.FILTER_DURATION_OPERATOR_LONGER_THAN_OR_EQUAL); + case FilterDurationOperator.FILTER_DURATION_OPERATOR_SHORTER_THAN_OR_EQUAL: + return this.updateOperator(filter, FilterDurationOperator.FILTER_DURATION_OPERATOR_LONGER_THAN); + default: + return { + ...this.emptyFilter + }; + } + } +} \ No newline at end of file diff --git a/src/app/sessions/index.component.ts b/src/app/sessions/index.component.ts index a31a11cb7..228cc732e 100644 --- a/src/app/sessions/index.component.ts +++ b/src/app/sessions/index.component.ts @@ -23,6 +23,7 @@ import { TableIndexActionsToolbarComponent } from '@components/table-index-actio import { AutoRefreshService } from '@services/auto-refresh.service'; import { FiltersService } from '@services/filters.service'; import { GrpcSortFieldService } from '@services/grpc-sort-field.service'; +import { InvertFilterService } from '@services/invert-filter.service'; import { NotificationService } from '@services/notification.service'; import { QueryParamsService } from '@services/query-params.service'; import { ShareUrlService } from '@services/share-url.service'; @@ -71,6 +72,7 @@ import { SessionRaw } from './types'; NotificationService, TasksGrpcService, GrpcSortFieldService, + InvertFilterService, ], imports: [ PageHeaderComponent, diff --git a/src/app/sessions/services/sessions-data.service.ts b/src/app/sessions/services/sessions-data.service.ts index df89c0cdf..a692af8ce 100644 --- a/src/app/sessions/services/sessions-data.service.ts +++ b/src/app/sessions/services/sessions-data.service.ts @@ -5,9 +5,11 @@ import { TaskOptions, TaskSummaryFilters } from '@app/tasks/types'; import { Scope } from '@app/types/config'; import { ColumnKey, SessionData } from '@app/types/data'; import { Filter, FiltersOr } from '@app/types/filters'; +import { GroupConditions } from '@app/types/groups'; import { ListOptions } from '@app/types/options'; import { AbstractTableDataService } from '@app/types/services/table-data.service'; import { Duration, Timestamp } from '@ngx-grpc/well-known-types'; +import { InvertFilterService } from '@services/invert-filter.service'; import { Subject, map, mergeAll } from 'rxjs'; import { SessionsGrpcService } from './sessions-grpc.service'; import { SessionRaw } from '../types'; @@ -15,9 +17,17 @@ import { SessionRaw } from '../types'; @Injectable() export class SessionsDataService extends AbstractTableDataService { readonly grpcService = inject(SessionsGrpcService); + readonly invertFiltersService: InvertFilterService = inject(InvertFilterService); scope: Scope = 'sessions'; + groupsConditions: GroupConditions[] = [ + { + name: 'Group 1', + conditions: [] + } + ]; + constructor() { super(); this.subscribeToDurationSubjects(); @@ -54,6 +64,7 @@ export class SessionsDataService extends AbstractTableDataService { const filtersOr = super.preparefilters(); + this.groupsConditions.forEach((groupConditions) => (filtersOr.push(...this.invertFiltersService.invert(groupConditions.conditions)))); if(this.isDurationDisplayed && this.options.sort.active === 'duration') { const date = new Date(); date.setDate(date.getDate() - 3); diff --git a/src/app/types/groups.ts b/src/app/types/groups.ts new file mode 100644 index 000000000..9b9b660e6 --- /dev/null +++ b/src/app/types/groups.ts @@ -0,0 +1,14 @@ +import { TaskOptions } from '@app/tasks/types'; +import { ArmonikData, DataRaw } from './data'; +import { FiltersEnums, FiltersOptionsEnums, FiltersOr } from './filters'; + +export type GroupConditions = { + name: string; + conditions: FiltersOr, +} + +export type Group = { + name: string; + opened: boolean; + data: ArmonikData[] +} \ No newline at end of file diff --git a/src/app/types/services/data-filter.service.ts b/src/app/types/services/data-filter.service.ts index 9c9335030..166f7fe77 100644 --- a/src/app/types/services/data-filter.service.ts +++ b/src/app/types/services/data-filter.service.ts @@ -9,7 +9,7 @@ import { FiltersCacheService } from '@services/filters-cache.service'; import { TableService } from '@services/table.service'; import { Scope } from '../config'; import { FilterDefinition } from '../filter-definition'; -import { FiltersEnums, FiltersOptionsEnums, FiltersOr } from '../filters'; +import { Filter, FiltersEnums, FiltersOptionsEnums, FiltersOr } from '../filters'; import { Status, StatusService } from '../status'; export type FilterFor = TaskFilterFor | ResultFilterFor | SessionFilterFor | PartitionFilterFor | ApplicationFilterFor; @@ -55,6 +55,10 @@ export abstract class DataFilterService) { + return this.filtersDefinitions.find(definition => definition.field === filter.field && definition.for === filter.for)?.type; + } + abstract retrieveLabel(filterFor: FilterFor, filterField: FilterField): string; abstract retrieveField(filterField: string): FilterField; } From 02d8f8a085246bc7a095d567d72f73bdcd275991 Mon Sep 17 00:00:00 2001 From: Faustin Date: Fri, 7 Feb 2025 09:58:22 +0100 Subject: [PATCH 02/47] feat: group fetching data --- .../services/sessions-data.service.ts | 71 +++++++++++++++++-- src/app/types/services/table-data.service.ts | 9 ++- 2 files changed, 73 insertions(+), 7 deletions(-) diff --git a/src/app/sessions/services/sessions-data.service.ts b/src/app/sessions/services/sessions-data.service.ts index a692af8ce..8deac07fa 100644 --- a/src/app/sessions/services/sessions-data.service.ts +++ b/src/app/sessions/services/sessions-data.service.ts @@ -1,36 +1,55 @@ import { FilterDateOperator, FilterStringOperator, ListSessionsResponse, ResultRawEnumField, SessionRawEnumField, TaskOptionEnumField, TaskSummaryEnumField } from '@aneoconsultingfr/armonik.api.angular'; -import { Injectable, inject } from '@angular/core'; +import { Injectable, OnDestroy, inject } from '@angular/core'; import { Params } from '@angular/router'; import { TaskOptions, TaskSummaryFilters } from '@app/tasks/types'; import { Scope } from '@app/types/config'; import { ColumnKey, SessionData } from '@app/types/data'; import { Filter, FiltersOr } from '@app/types/filters'; -import { GroupConditions } from '@app/types/groups'; +import { Group, GroupConditions } from '@app/types/groups'; import { ListOptions } from '@app/types/options'; import { AbstractTableDataService } from '@app/types/services/table-data.service'; import { Duration, Timestamp } from '@ngx-grpc/well-known-types'; import { InvertFilterService } from '@services/invert-filter.service'; -import { Subject, map, mergeAll } from 'rxjs'; +import { Subject, Subscription, map, mergeAll, switchMap } from 'rxjs'; import { SessionsGrpcService } from './sessions-grpc.service'; import { SessionRaw } from '../types'; @Injectable() -export class SessionsDataService extends AbstractTableDataService { +export class SessionsDataService extends AbstractTableDataService implements OnDestroy { readonly grpcService = inject(SessionsGrpcService); readonly invertFiltersService: InvertFilterService = inject(InvertFilterService); scope: Scope = 'sessions'; + groups: Group[] = []; + private readonly groupsSubscriptions: Map = new Map(); + groupsConditions: GroupConditions[] = [ { name: 'Group 1', - conditions: [] + conditions: [ + [ + { + field: SessionRawEnumField.SESSION_RAW_ENUM_FIELD_SESSION_ID, + for: 'root', + operator: FilterStringOperator.FILTER_STRING_OPERATOR_EQUAL, + value: '7ce2655a-b811-4845-802a-9969cf92ff68' + } + ] + ] } ]; constructor() { super(); this.subscribeToDurationSubjects(); + this.setGroups(); + } + + ngOnDestroy(): void { + this.onDestroy(); + this.groupsSubscriptions.forEach((groupSubscription) => groupSubscription.unsubscribe()); + this.groupsSubscriptions.clear(); } computeGrpcData(entries: ListSessionsResponse): SessionRaw[] | undefined { @@ -189,6 +208,48 @@ export class SessionsDataService extends AbstractTableDataService) { + const group: Group = { + name: groupCondition.name, + opened: false, + data: [] + }; + const groupSubscription = this.refresh$.pipe( + switchMap(() => this.grpcService.list$(this.options, groupCondition.conditions)), + map((data) => this.computeGrpcData(data)), + map((data) => { + if (data) { + return data.map(entry => this.createNewLine(entry)); + } + return undefined; + }) + ).subscribe((data) => { + if (data) { + group.data = data; + console.log(data); + } + }); + + this.groupsSubscriptions.set(groupCondition.name, groupSubscription); + } + + setGroups() { + this.groupsConditions.forEach((g) => this.initGroup(g)); + } + + addGroup(groupCondition: GroupConditions) { + this.groupsConditions.push(groupCondition); + this.initGroup(groupCondition); + } + + removeGroup(groupName: string) { + const subscription = this.groupsSubscriptions.get(groupName); + if (subscription) { + subscription.unsubscribe(); + this.groupsSubscriptions.delete(groupName); + } + } + // Duration computation isDurationDisplayed = false; private dataRaw: SessionRaw[]; diff --git a/src/app/types/services/table-data.service.ts b/src/app/types/services/table-data.service.ts index 9b3cca2a2..aebd988f5 100644 --- a/src/app/types/services/table-data.service.ts +++ b/src/app/types/services/table-data.service.ts @@ -7,7 +7,7 @@ import { GrpcStatusEvent } from '@ngx-grpc/common'; import { CacheService } from '@services/cache.service'; import { FiltersService } from '@services/filters.service'; import { NotificationService } from '@services/notification.service'; -import { Subject, catchError, map, of, switchMap } from 'rxjs'; +import { Subject, Subscription, catchError, map, of, switchMap } from 'rxjs'; import { GrpcTableService } from './grpcService'; import { Scope } from '../config'; @@ -26,6 +26,7 @@ export abstract class AbstractTableDataService(false); readonly total = signal(0); readonly data = signal[]>([]); + protected dataSubscription: Subscription; filters: FiltersOr = []; options: ListOptions; @@ -57,7 +58,7 @@ export abstract class AbstractTableDataService { this.loading.set(true); @@ -131,6 +132,10 @@ export abstract class AbstractTableDataService Date: Fri, 7 Feb 2025 14:03:19 +0100 Subject: [PATCH 03/47] chore: update display in table --- src/app/components/table/table.component.html | 36 ++++++++++--------- src/app/components/table/table.component.ts | 22 +++++++++--- .../sessions/components/table.component.html | 2 +- .../sessions/components/table.component.ts | 5 +-- .../services/sessions-data.service.ts | 5 +-- src/app/types/data.ts | 2 +- 6 files changed, 44 insertions(+), 28 deletions(-) diff --git a/src/app/components/table/table.component.html b/src/app/components/table/table.component.html index b24948fcf..58725eb43 100644 --- a/src/app/components/table/table.component.html +++ b/src/app/components/table/table.component.html @@ -1,13 +1,9 @@ - - - - - @for (column of columns; track column.key) { @@ -17,19 +13,26 @@ (statusesChange)="onPersonnalizeTasksByStatus()" /> - @if (column.type !== 'actions') { - - } @else { - - } + + @if (isData(element)) { + @if (column.type !== 'actions') { + + } @else { + + } + } + } + + @if (!isData(element)) {} + @@ -40,7 +43,6 @@ -
test - - - - + + + +
[]; + @Input({ required: true }) total: number; @Input({ required: true }) options: ListOptions; @@ -63,7 +66,7 @@ export class TableComponent boolean) | undefined; // eslint-disable-next-line @typescript-eslint/no-unused-vars - @Input({ required: false }) trackBy(index: number, item: ArmonikData): number | string { + @Input({ required: false }) trackBy(index: number, item: ArmonikData |Group): number | string { return index; } @@ -80,8 +83,8 @@ export class TableComponent[]; private _isAllSelected: boolean = false; - get data(): ArmonikData[] { - return this._data; + get data(): (ArmonikData | Group)[] { + return [...this.groups, ...this._data]; } get columns(): TableColumn[] { @@ -109,7 +112,7 @@ export class TableComponent { - if (this.options.pageSize > this.paginator.pageSize) this.data = this.data.slice(0, this.paginator.pageSize); + if (this.options.pageSize > this.paginator.pageSize) this._data = this._data.slice(0, this.paginator.pageSize); this.options.pageIndex = this.paginator.pageIndex; this.options.pageSize = this.paginator.pageSize; this.optionsChange.emit(); @@ -147,7 +150,7 @@ export class TableComponent d.raw))); + this.selection.select(...(this._data.map(d => d.raw))); this._isAllSelected = true; } this.emitSelectionChange(); @@ -165,4 +168,13 @@ export class TableComponent | Group) { + return (element as ArmonikData).raw !== undefined && (element as Group).name === undefined; + } + + test(e: unknown) { + console.log(e); + return ''; + } } \ No newline at end of file diff --git a/src/app/sessions/components/table.component.html b/src/app/sessions/components/table.component.html index b2f394052..d3ab4e45d 100644 --- a/src/app/sessions/components/table.component.html +++ b/src/app/sessions/components/table.component.html @@ -1,3 +1,3 @@ - diff --git a/src/app/sessions/components/table.component.ts b/src/app/sessions/components/table.component.ts index b30913cd8..eaee5f645 100644 --- a/src/app/sessions/components/table.component.ts +++ b/src/app/sessions/components/table.component.ts @@ -159,7 +159,8 @@ export class SessionsTableComponent extends AbstractTaskByStatusTableComponent) { - return item.raw.sessionId; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + trackBy(index: number, _item: ArmonikData) { + return index; } } diff --git a/src/app/sessions/services/sessions-data.service.ts b/src/app/sessions/services/sessions-data.service.ts index 8deac07fa..7b79e0914 100644 --- a/src/app/sessions/services/sessions-data.service.ts +++ b/src/app/sessions/services/sessions-data.service.ts @@ -33,7 +33,7 @@ export class SessionsDataService extends AbstractTableDataService { if (data) { group.data = data; - console.log(data); } }); + this.groups.push(group); + this.groupsSubscriptions.set(groupCondition.name, groupSubscription); } diff --git a/src/app/types/data.ts b/src/app/types/data.ts index df2650d4e..0939eb3fe 100644 --- a/src/app/types/data.ts +++ b/src/app/types/data.ts @@ -17,7 +17,7 @@ export type DataRaw = SessionRaw | ApplicationRaw | PartitionRaw | ResultRaw | T export type CustomColumn = `options.options.${string}`; export interface ArmonikData { - raw: T, + raw: T; queryParams?: Map, Params>; } From 922e2ed27894730117630de7ddd6a458fc06c603 Mon Sep 17 00:00:00 2001 From: Faustin Date: Mon, 10 Feb 2025 16:11:49 +0100 Subject: [PATCH 04/47] feat: working groups --- .../table/group/group.component.css | 47 +++++++++++ .../table/group/group.component.html | 41 +++++++++ .../table/group/group.component.spec.ts | 0 .../components/table/group/group.component.ts | 84 +++++++++++++++++++ .../group-tasks-by-status.component.css | 0 .../group-tasks-by-status.component.html | 1 + .../group-tasks-by-status.component.spec.ts | 0 .../group-tasks-by-status.component.ts | 36 ++++++++ src/app/components/table/table.component.html | 16 +++- src/app/components/table/table.component.ts | 13 ++- .../view-tasks-by-status.component.ts | 27 +++--- src/app/services/icons.service.ts | 1 + .../services/sessions-data.service.ts | 16 +++- src/app/types/groups.ts | 1 + 14 files changed, 256 insertions(+), 27 deletions(-) create mode 100644 src/app/components/table/group/group.component.css create mode 100644 src/app/components/table/group/group.component.html create mode 100644 src/app/components/table/group/group.component.spec.ts create mode 100644 src/app/components/table/group/group.component.ts create mode 100644 src/app/components/table/grouped-tasks-by-status/group-tasks-by-status.component.css create mode 100644 src/app/components/table/grouped-tasks-by-status/group-tasks-by-status.component.html create mode 100644 src/app/components/table/grouped-tasks-by-status/group-tasks-by-status.component.spec.ts create mode 100644 src/app/components/table/grouped-tasks-by-status/group-tasks-by-status.component.ts diff --git a/src/app/components/table/group/group.component.css b/src/app/components/table/group/group.component.css new file mode 100644 index 000000000..5a4deb33a --- /dev/null +++ b/src/app/components/table/group/group.component.css @@ -0,0 +1,47 @@ +.detailed-row { + display: flex; + justify-content: space-between; + align-items: center; + padding-left: 2rem; + padding-right: 2rem; +} + +.general-data { + display: flex; + justify-content: space-between; + align-items: center; + min-width: 20rem; +} + +.general-actions { + display: flex; + align-items: center; +} + +.group-info { + display: flex; + align-items: center; + gap: 1rem; + font-weight: bolder; +} + +.table-actions { + display: flex; + justify-content: space-between; + padding-top: 1rem; +} + +app-refresh-button { + padding-left: 1rem; +} + +mat-card { + padding-top: 0.5rem; + padding-bottom: 0.5rem; + border-radius: 0; + background-color: var(--mat-sidenav-content-background-color) !important; +} + +p { + margin: 0; +} \ No newline at end of file diff --git a/src/app/components/table/group/group.component.html b/src/app/components/table/group/group.component.html new file mode 100644 index 000000000..8072065b5 --- /dev/null +++ b/src/app/components/table/group/group.component.html @@ -0,0 +1,41 @@ +
+
+
+ +

{{ group.name }}

+
+

+ Total: + {{ group.total }} +

+
+
+ + +
+
+
+
+ + +
+ + + @for (column of displayedColumns; track $index) { + + + + + } + +
+ @if (column.type !== 'actions') { + + } @else { + + } +
+
+
\ No newline at end of file diff --git a/src/app/components/table/group/group.component.spec.ts b/src/app/components/table/group/group.component.spec.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/components/table/group/group.component.ts b/src/app/components/table/group/group.component.ts new file mode 100644 index 000000000..d57b64f32 --- /dev/null +++ b/src/app/components/table/group/group.component.ts @@ -0,0 +1,84 @@ +import { animate, state, style, transition, trigger } from '@angular/animations'; +import { Component, EventEmitter, Input, Output, inject } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatCardModule } from '@angular/material/card'; +import { MatIconModule } from '@angular/material/icon'; +import { MatPaginatorModule, PageEvent } from '@angular/material/paginator'; +import { MatTableModule } from '@angular/material/table'; +import { TasksStatusesGroup } from '@app/dashboard/types'; +import { TaskOptions } from '@app/tasks/types'; +import { TableColumn } from '@app/types/column.type'; +import { ColumnKey, DataRaw } from '@app/types/data'; +import { Group } from '@app/types/groups'; +import { Status, StatusService } from '@app/types/status'; +import { ActionTable } from '@app/types/table'; +import { IconsService } from '@services/icons.service'; +import { RefreshButtonComponent } from '../../refresh-button.component'; +import { GroupTasksByStatusComponent } from '../grouped-tasks-by-status/group-tasks-by-status.component'; +import { TableActionsComponent } from '../table-actions.component'; +import { TableCellComponent } from '../table-cell.component'; + +@Component({ + selector: 'app-table-group', + templateUrl: 'group.component.html', + styleUrl: 'group.component.css', + standalone: true, + imports: [ + MatButtonModule, + MatIconModule, + MatTableModule, + MatPaginatorModule, + MatCardModule, + TableCellComponent, + TableActionsComponent, + RefreshButtonComponent, + GroupTasksByStatusComponent + ], + providers: [ + IconsService + ], + animations: [ + trigger('rotateFull', [ + state('true', style({ transform: 'rotate(-180deg)' })), + state('false', style({ transform: 'rotate(0deg)' })), + transition('true <=> false', [animate('125ms ease-in')]) + ]), + trigger('expand', [ + state('false', style({ 'height': '0' })), + state('true', style({})), + transition('true <=> false', [animate('500ms cubic-bezier(0.4, 0.0, 0.2, 1)')]) + ]) + ] +}) +export class TableGroupComponent { + @Input({ required: true }) group: Group; + @Input({ required: true }) set columns(entry: TableColumn[]) { + this.displayedColumns = entry; + this.columnsKeys = entry.map(column => column.key); + } + + @Input({ required: false }) actions: ActionTable[]; + @Input({ required: false }) statusesService: StatusService; + @Input({ required: false }) statusesGroups: TasksStatusesGroup[]; + + columnsKeys: ColumnKey[]; + displayedColumns: TableColumn[]; + + @Output() page = new EventEmitter(); + + private readonly iconsService = inject(IconsService); + + pageIndex = 0; + + getIcon(name: string) { + return this.iconsService.getIcon(name); + } + + switchView() { + this.group.opened = !this.group.opened; + } + + pageChange(event: PageEvent) { + this.page.emit(event.pageIndex); + } +} \ No newline at end of file diff --git a/src/app/components/table/grouped-tasks-by-status/group-tasks-by-status.component.css b/src/app/components/table/grouped-tasks-by-status/group-tasks-by-status.component.css new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/components/table/grouped-tasks-by-status/group-tasks-by-status.component.html b/src/app/components/table/grouped-tasks-by-status/group-tasks-by-status.component.html new file mode 100644 index 000000000..9d14ba291 --- /dev/null +++ b/src/app/components/table/grouped-tasks-by-status/group-tasks-by-status.component.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/app/components/table/grouped-tasks-by-status/group-tasks-by-status.component.spec.ts b/src/app/components/table/grouped-tasks-by-status/group-tasks-by-status.component.spec.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/components/table/grouped-tasks-by-status/group-tasks-by-status.component.ts b/src/app/components/table/grouped-tasks-by-status/group-tasks-by-status.component.ts new file mode 100644 index 000000000..c9f0875b2 --- /dev/null +++ b/src/app/components/table/grouped-tasks-by-status/group-tasks-by-status.component.ts @@ -0,0 +1,36 @@ +import { Component, Input } from '@angular/core'; +import { TasksStatusesGroup } from '@app/dashboard/types'; +import { TaskOptions, TaskSummaryFilters } from '@app/tasks/types'; +import { ApplicationData, ArmonikData, DataRaw, PartitionData, SessionData } from '@app/types/data'; +import { CountTasksByStatusComponent } from '@components/count-tasks-by-status.component'; +import { Subject } from 'rxjs'; + +@Component({ + selector: 'app-group-tasks-by-status', + templateUrl: 'group-tasks-by-status.component.html', + styleUrl: 'group-tasks-by-status.component.css', + standalone: true, + imports: [ + CountTasksByStatusComponent, + ] +}) +export class GroupTasksByStatusComponent { + @Input({ required: true }) set groupData(entry: ArmonikData[]) { + this.filters = []; + this.queryParams = {}; + const groupData = entry as unknown as (SessionData | ApplicationData | PartitionData)[]; + groupData.forEach((data) => { + this.filters.push(...data.filters); + }); + groupData.forEach((data, index) => { + const keys = Object.keys(data.queryTasksParams); + keys.forEach((key) => (this.queryParams[`${index}${key.slice(1)}`] = data.queryTasksParams[key])); + }); + } + + @Input({ required: true }) statusesGroups: TasksStatusesGroup[]; + + filters: TaskSummaryFilters; + queryParams: Record; + refresh = new Subject(); +} \ No newline at end of file diff --git a/src/app/components/table/table.component.html b/src/app/components/table/table.component.html index 58725eb43..b60489533 100644 --- a/src/app/components/table/table.component.html +++ b/src/app/components/table/table.component.html @@ -4,6 +4,17 @@ [cdkDropListDisabled]="lockColumns" (cdkDropListDropped)="onDrop($event)" [dataSource]="data" i18n-aria-label aria-label="Data Table"> + + + + @if (!isData(element)) { + + + + } + + + @for (column of columns; track column.key) { @@ -30,9 +41,6 @@ } - - @if (!isData(element)) {} - @@ -42,7 +50,7 @@ - + boolean) | undefined; // eslint-disable-next-line @typescript-eslint/no-unused-vars - @Input({ required: false }) trackBy(index: number, item: ArmonikData |Group): number | string { + @Input({ required: false }) trackBy(index: number, item: ArmonikData | Group): number | string { return index; } @@ -84,7 +86,7 @@ export class TableComponent | Group)[] { - return [...this.groups, ...this._data]; + return [...(this.groups ?? []), ...this._data]; } get columns(): TableColumn[] { @@ -92,7 +94,7 @@ export class TableComponent[] { - return this._columnsKeys; + return [...this._columnsKeys, 'group'] as ColumnKey[]; } get isAllSelected(): boolean { @@ -172,9 +174,4 @@ export class TableComponent | Group) { return (element as ArmonikData).raw !== undefined && (element as Group).name === undefined; } - - test(e: unknown) { - console.log(e); - return ''; - } } \ No newline at end of file diff --git a/src/app/components/view-tasks-by-status.component.ts b/src/app/components/view-tasks-by-status.component.ts index 2bc872d41..1ea963476 100644 --- a/src/app/components/view-tasks-by-status.component.ts +++ b/src/app/components/view-tasks-by-status.component.ts @@ -14,7 +14,7 @@ import { SpinnerComponent } from './spinner.component'; @if (loading) { } @else { - @for (group of statusesGroups; track group.name) { + @for (group of groups; track group.name) { = {}; - - @Input() set statusesGroups(entries: TasksStatusesGroup[]) { - this._statusesGroups = entries.map(group => this.completeGroup(group)); + @Input({ required: true }) set defaultQueryParams(entry: Record) { + this.queryParams = entry; + this.groups.forEach((group) => group.queryParams = this.createQueryParams(group)); } - private _statusesGroups: TasksStatusesGroup[] = []; + private queryParams: Record = {}; - get statusesGroups(): TasksStatusesGroup[] { - return this._statusesGroups; + @Input() set statusesGroups(entries: TasksStatusesGroup[]) { + this.groups = entries.map(group => this.completeGroup(group)); } + groups: TasksStatusesGroup[] = []; + @Input() set statusesCount(entries: StatusCount[] | null) { - this.statusesGroups.forEach(group => group.statusCount = 0); + this.groups.forEach(group => group.statusCount = 0); entries?.forEach((entry) => { - this.statusesGroups.forEach(group => { + this.groups.forEach(group => { if (group.statuses.includes(entry.status) && group.statusCount !== undefined) { group.statusCount += entry.count; } @@ -69,9 +70,9 @@ export class ViewTasksByStatusComponent { } createQueryParams(group: TasksStatusesGroup) { - const queryOrs = Object.keys(this.defaultQueryParams).map(key => key[0]).filter((key, index, self) => self.indexOf(key) === index); - const queryParamsKeys = Object.keys(this.defaultQueryParams); - const queryParamsValues = Object.values(this.defaultQueryParams); + const queryOrs = Object.keys(this.queryParams).map(key => key[0]).filter((key, index, self) => self.indexOf(key) === index); + const queryParamsKeys = Object.keys(this.queryParams); + const queryParamsValues = Object.values(this.queryParams); const taskStatusQueryParams: Record = {}; let orGroups = 0; diff --git a/src/app/services/icons.service.ts b/src/app/services/icons.service.ts index e9adf973a..777630813 100644 --- a/src/app/services/icons.service.ts +++ b/src/app/services/icons.service.ts @@ -83,6 +83,7 @@ export class IconsService { 'processed': 'offline_pin', 'retry': 'repeat_on', 'pending': 'pending', + 'table-group': 'diversity_2' }; getIcon(name: string | null | undefined): string { diff --git a/src/app/sessions/services/sessions-data.service.ts b/src/app/sessions/services/sessions-data.service.ts index 7b79e0914..b344c9cbe 100644 --- a/src/app/sessions/services/sessions-data.service.ts +++ b/src/app/sessions/services/sessions-data.service.ts @@ -33,7 +33,15 @@ export class SessionsDataService extends AbstractTableDataService = { name: groupCondition.name, opened: false, + total: 0, data: [] }; const groupSubscription = this.refresh$.pipe( switchMap(() => this.grpcService.list$(this.options, groupCondition.conditions)), - map((data) => this.computeGrpcData(data)), + map((data) => { + group.total = data.total; + return this.computeGrpcData(data); + }), map((data) => { if (data) { return data.map(entry => this.createNewLine(entry)); diff --git a/src/app/types/groups.ts b/src/app/types/groups.ts index 9b9b660e6..d3e3927b3 100644 --- a/src/app/types/groups.ts +++ b/src/app/types/groups.ts @@ -10,5 +10,6 @@ export type GroupConditions = { name: string; opened: boolean; + total: number; data: ArmonikData[] } \ No newline at end of file From 87a8d40e95735618f1662ce767a0124c0e469c74 Mon Sep 17 00:00:00 2001 From: Faustin Date: Mon, 10 Feb 2025 16:21:07 +0100 Subject: [PATCH 05/47] feat: group settings button --- src/app/components/table/group/group.component.html | 5 ++++- src/app/components/table/group/group.component.ts | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/app/components/table/group/group.component.html b/src/app/components/table/group/group.component.html index 8072065b5..b6191219f 100644 --- a/src/app/components/table/group/group.component.html +++ b/src/app/components/table/group/group.component.html @@ -8,9 +8,12 @@ Total: {{ group.total }}

+
- + diff --git a/src/app/components/table/group/group.component.ts b/src/app/components/table/group/group.component.ts index d57b64f32..7323e3975 100644 --- a/src/app/components/table/group/group.component.ts +++ b/src/app/components/table/group/group.component.ts @@ -69,6 +69,7 @@ export class TableGroupComponent Date: Mon, 10 Feb 2025 16:47:56 +0100 Subject: [PATCH 06/47] chore: better ui --- src/app/components/table/group/group.component.css | 13 ++++++++++--- src/app/components/table/group/group.component.html | 11 +++++++---- .../group-tasks-by-status.component.html | 4 +++- .../group-tasks-by-status.component.ts | 3 +++ src/app/services/invert-filter.service.ts | 3 ++- src/app/sessions/services/sessions-data.service.ts | 2 +- 6 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/app/components/table/group/group.component.css b/src/app/components/table/group/group.component.css index 5a4deb33a..7455dee56 100644 --- a/src/app/components/table/group/group.component.css +++ b/src/app/components/table/group/group.component.css @@ -13,6 +13,11 @@ min-width: 20rem; } +.tasks-statuses { + display: flex; + align-items: center; +} + .general-actions { display: flex; align-items: center; @@ -28,18 +33,20 @@ .table-actions { display: flex; justify-content: space-between; - padding-top: 1rem; + align-items: center; + background-color: var(--mat-table-background-color); } app-refresh-button { - padding-left: 1rem; + padding-left: 0.5rem; } mat-card { + padding: 1rem; padding-top: 0.5rem; padding-bottom: 0.5rem; border-radius: 0; - background-color: var(--mat-sidenav-content-background-color) !important; + background-color: var(--mat-sidenav-content-background-color); } p { diff --git a/src/app/components/table/group/group.component.html b/src/app/components/table/group/group.component.html index b6191219f..a5458ab17 100644 --- a/src/app/components/table/group/group.component.html +++ b/src/app/components/table/group/group.component.html @@ -8,6 +8,9 @@ Total: {{ group.total }}

+
+
+

Tasks statuses:

@@ -20,11 +23,11 @@
-
- - -
+
+ + +
@for (column of displayedColumns; track $index) { diff --git a/src/app/components/table/grouped-tasks-by-status/group-tasks-by-status.component.html b/src/app/components/table/grouped-tasks-by-status/group-tasks-by-status.component.html index 9d14ba291..96e18c463 100644 --- a/src/app/components/table/grouped-tasks-by-status/group-tasks-by-status.component.html +++ b/src/app/components/table/grouped-tasks-by-status/group-tasks-by-status.component.html @@ -1 +1,3 @@ - \ No newline at end of file +@if (queryParamsLength !== 0 && filters.length !== 0) { + +} \ No newline at end of file diff --git a/src/app/components/table/grouped-tasks-by-status/group-tasks-by-status.component.ts b/src/app/components/table/grouped-tasks-by-status/group-tasks-by-status.component.ts index c9f0875b2..9330e5ba9 100644 --- a/src/app/components/table/grouped-tasks-by-status/group-tasks-by-status.component.ts +++ b/src/app/components/table/grouped-tasks-by-status/group-tasks-by-status.component.ts @@ -22,15 +22,18 @@ export class GroupTasksByStatusComponent { this.filters.push(...data.filters); }); + groupData.forEach((data, index) => { const keys = Object.keys(data.queryTasksParams); keys.forEach((key) => (this.queryParams[`${index}${key.slice(1)}`] = data.queryTasksParams[key])); }); + this.queryParamsLength = Object.keys(this.queryParams).length; } @Input({ required: true }) statusesGroups: TasksStatusesGroup[]; filters: TaskSummaryFilters; queryParams: Record; + queryParamsLength = 0; refresh = new Subject(); } \ No newline at end of file diff --git a/src/app/services/invert-filter.service.ts b/src/app/services/invert-filter.service.ts index 968fd4c76..08ac61d6f 100644 --- a/src/app/services/invert-filter.service.ts +++ b/src/app/services/invert-filter.service.ts @@ -15,7 +15,8 @@ export class InvertFilterService): FiltersOr { - return filters.map((filterAnd) => filterAnd.map((filter) => this.invertFilter(filter))); + return [filters.map((filterAnd) => filterAnd.map((filter) => this.invertFilter(filter))).flat()]; + } private invertFilter(filter: Filter) { diff --git a/src/app/sessions/services/sessions-data.service.ts b/src/app/sessions/services/sessions-data.service.ts index b344c9cbe..7f235643d 100644 --- a/src/app/sessions/services/sessions-data.service.ts +++ b/src/app/sessions/services/sessions-data.service.ts @@ -41,7 +41,7 @@ export class SessionsDataService extends AbstractTableDataService Date: Mon, 10 Feb 2025 17:17:31 +0100 Subject: [PATCH 07/47] feat: group change page --- .../table/group/group.component.css | 2 +- .../table/group/group.component.html | 9 ++-- .../components/table/group/group.component.ts | 18 ++++--- .../group-tasks-by-status.component.ts | 28 +++++----- src/app/components/table/table.component.html | 10 +++- src/app/components/table/table.component.ts | 5 ++ .../sessions/components/table.component.html | 2 +- .../sessions/components/table.component.ts | 4 ++ .../services/sessions-data.service.ts | 54 +++++++++---------- src/app/types/groups.ts | 5 +- 10 files changed, 79 insertions(+), 58 deletions(-) diff --git a/src/app/components/table/group/group.component.css b/src/app/components/table/group/group.component.css index 7455dee56..a1bb3e51d 100644 --- a/src/app/components/table/group/group.component.css +++ b/src/app/components/table/group/group.component.css @@ -32,7 +32,7 @@ .table-actions { display: flex; - justify-content: space-between; + justify-content: flex-end; align-items: center; background-color: var(--mat-table-background-color); } diff --git a/src/app/components/table/group/group.component.html b/src/app/components/table/group/group.component.html index a5458ab17..8f9c8553c 100644 --- a/src/app/components/table/group/group.component.html +++ b/src/app/components/table/group/group.component.html @@ -11,7 +11,7 @@

Tasks statuses:

- +
@for (column of displayedColumns; track $index) { diff --git a/src/app/components/table/group/group.component.ts b/src/app/components/table/group/group.component.ts index 7323e3975..c825d52cb 100644 --- a/src/app/components/table/group/group.component.ts +++ b/src/app/components/table/group/group.component.ts @@ -1,4 +1,5 @@ import { animate, state, style, transition, trigger } from '@angular/animations'; +import { AsyncPipe } from '@angular/common'; import { Component, EventEmitter, Input, Output, inject } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; @@ -8,12 +9,11 @@ import { MatTableModule } from '@angular/material/table'; import { TasksStatusesGroup } from '@app/dashboard/types'; import { TaskOptions } from '@app/tasks/types'; import { TableColumn } from '@app/types/column.type'; -import { ColumnKey, DataRaw } from '@app/types/data'; +import { ArmonikData, ColumnKey, DataRaw } from '@app/types/data'; import { Group } from '@app/types/groups'; import { Status, StatusService } from '@app/types/status'; import { ActionTable } from '@app/types/table'; import { IconsService } from '@services/icons.service'; -import { RefreshButtonComponent } from '../../refresh-button.component'; import { GroupTasksByStatusComponent } from '../grouped-tasks-by-status/group-tasks-by-status.component'; import { TableActionsComponent } from '../table-actions.component'; import { TableCellComponent } from '../table-cell.component'; @@ -31,8 +31,8 @@ import { TableCellComponent } from '../table-cell.component'; MatCardModule, TableCellComponent, TableActionsComponent, - RefreshButtonComponent, - GroupTasksByStatusComponent + GroupTasksByStatusComponent, + AsyncPipe, ], providers: [ IconsService @@ -60,15 +60,18 @@ export class TableGroupComponent[]; @Input({ required: false }) statusesService: StatusService; @Input({ required: false }) statusesGroups: TasksStatusesGroup[]; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + @Input({ required: false }) trackBy(index: number, item: ArmonikData | Group): number | string { + return index; + } columnsKeys: ColumnKey[]; displayedColumns: TableColumn[]; - @Output() page = new EventEmitter(); + @Output() page = new EventEmitter(); private readonly iconsService = inject(IconsService); - pageIndex = 0; settingsRotate = false; getIcon(name: string) { @@ -80,6 +83,7 @@ export class TableGroupComponent { - @Input({ required: true }) set groupData(entry: ArmonikData[]) { - this.filters = []; - this.queryParams = {}; - const groupData = entry as unknown as (SessionData | ApplicationData | PartitionData)[]; - groupData.forEach((data) => { - this.filters.push(...data.filters); - }); - - groupData.forEach((data, index) => { - const keys = Object.keys(data.queryTasksParams); - keys.forEach((key) => (this.queryParams[`${index}${key.slice(1)}`] = data.queryTasksParams[key])); - }); - this.queryParamsLength = Object.keys(this.queryParams).length; + @Input({ required: true }) set groupData(entry: ArmonikData[] | null) { + if (entry !== null) { + this.filters = []; + this.queryParams = {}; + const groupData = entry as unknown as (SessionData | ApplicationData | PartitionData)[]; + groupData.forEach((data) => { + this.filters.push(...data.filters); + }); + + groupData.forEach((data, index) => { + const keys = Object.keys(data.queryTasksParams); + keys.forEach((key) => (this.queryParams[`${index}${key.slice(1)}`] = data.queryTasksParams[key])); + }); + this.queryParamsLength = Object.keys(this.queryParams).length; + } } @Input({ required: true }) statusesGroups: TasksStatusesGroup[]; diff --git a/src/app/components/table/table.component.html b/src/app/components/table/table.component.html index b60489533..602234b7c 100644 --- a/src/app/components/table/table.component.html +++ b/src/app/components/table/table.component.html @@ -9,7 +9,15 @@ @if (!isData(element)) { } diff --git a/src/app/components/table/table.component.ts b/src/app/components/table/table.component.ts index 3517334bd..1d7150d41 100644 --- a/src/app/components/table/table.component.ts +++ b/src/app/components/table/table.component.ts @@ -76,6 +76,7 @@ export class TableComponent(); @Output() selectionChange = new EventEmitter(); @Output() personnalizeTasksByStatus = new EventEmitter(); + @Output() groupPageChange = new EventEmitter(); @ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatSort) sort: MatSort; @@ -174,4 +175,8 @@ export class TableComponent | Group) { return (element as ArmonikData).raw !== undefined && (element as Group).name === undefined; } + + groupPageUpdate(groupName: string) { + this.groupPageChange.emit(groupName); + } } \ No newline at end of file diff --git a/src/app/sessions/components/table.component.html b/src/app/sessions/components/table.component.html index d3ab4e45d..2e2e31db8 100644 --- a/src/app/sessions/components/table.component.html +++ b/src/app/sessions/components/table.component.html @@ -1,3 +1,3 @@ + (columnDrop)="onDrop($event)" (optionsChange)="onOptionsChange()" (personnalizeTasksByStatus)="personalizeTasksByStatus()" (groupPageChange)="groupPageChange($event)" /> diff --git a/src/app/sessions/components/table.component.ts b/src/app/sessions/components/table.component.ts index eaee5f645..2a1f2f702 100644 --- a/src/app/sessions/components/table.component.ts +++ b/src/app/sessions/components/table.component.ts @@ -163,4 +163,8 @@ export class SessionsTableComponent extends AbstractTaskByStatusTableComponent) { return index; } + + groupPageChange(groupName: string) { + this.tableDataService.refreshGroup(groupName); + } } diff --git a/src/app/sessions/services/sessions-data.service.ts b/src/app/sessions/services/sessions-data.service.ts index 7f235643d..0d6b4882e 100644 --- a/src/app/sessions/services/sessions-data.service.ts +++ b/src/app/sessions/services/sessions-data.service.ts @@ -10,7 +10,7 @@ import { ListOptions } from '@app/types/options'; import { AbstractTableDataService } from '@app/types/services/table-data.service'; import { Duration, Timestamp } from '@ngx-grpc/well-known-types'; import { InvertFilterService } from '@services/invert-filter.service'; -import { Subject, Subscription, map, mergeAll, switchMap } from 'rxjs'; +import { Subject, map, merge, mergeAll, switchMap } from 'rxjs'; import { SessionsGrpcService } from './sessions-grpc.service'; import { SessionRaw } from '../types'; @@ -22,7 +22,6 @@ export class SessionsDataService extends AbstractTableDataService[] = []; - private readonly groupsSubscriptions: Map = new Map(); groupsConditions: GroupConditions[] = [ { @@ -56,8 +55,6 @@ export class SessionsDataService extends AbstractTableDataService groupSubscription.unsubscribe()); - this.groupsSubscriptions.clear(); } computeGrpcData(entries: ListSessionsResponse): SessionRaw[] | undefined { @@ -217,33 +214,29 @@ export class SessionsDataService extends AbstractTableDataService) { + const groupRefresh$ = new Subject(); const group: Group = { name: groupCondition.name, opened: false, total: 0, - data: [] + page: 0, + refresh$: groupRefresh$, + data: merge(this.refresh$, groupRefresh$).pipe( + switchMap(() => this.grpcService.list$({pageSize: 100, pageIndex: group.page, sort: this.options.sort}, groupCondition.conditions)), + map((data) => { + group.total = data.total; + return this.computeGrpcData(data); + }), + map((data) => { + if (data) { + return data.map(entry => this.createNewLine(entry)); + } + return []; + }) + ) }; - const groupSubscription = this.refresh$.pipe( - switchMap(() => this.grpcService.list$(this.options, groupCondition.conditions)), - map((data) => { - group.total = data.total; - return this.computeGrpcData(data); - }), - map((data) => { - if (data) { - return data.map(entry => this.createNewLine(entry)); - } - return undefined; - }) - ).subscribe((data) => { - if (data) { - group.data = data; - } - }); this.groups.push(group); - - this.groupsSubscriptions.set(groupCondition.name, groupSubscription); } setGroups() { @@ -255,14 +248,17 @@ export class SessionsDataService extends AbstractTableDataService group.name === groupName); + if (group) { + group.refresh$.next(); } } + removeGroup(groupName: string) { + this.groups = this.groups.filter((group) => group.name !== groupName); + } + // Duration computation isDurationDisplayed = false; private dataRaw: SessionRaw[]; diff --git a/src/app/types/groups.ts b/src/app/types/groups.ts index d3e3927b3..8b37135cf 100644 --- a/src/app/types/groups.ts +++ b/src/app/types/groups.ts @@ -1,4 +1,5 @@ import { TaskOptions } from '@app/tasks/types'; +import { Observable, Subject } from 'rxjs'; import { ArmonikData, DataRaw } from './data'; import { FiltersEnums, FiltersOptionsEnums, FiltersOr } from './filters'; @@ -11,5 +12,7 @@ export type Group = { name: string; opened: boolean; total: number; - data: ArmonikData[] + page: number; + refresh$: Subject, + data: Observable[]> } \ No newline at end of file From 4f230ccee5b24067682a81e4dd28b158870fa2b8 Mon Sep 17 00:00:00 2001 From: Faustin Date: Mon, 10 Feb 2025 17:39:27 +0100 Subject: [PATCH 08/47] chore: better html --- src/app/components/table/group/group.component.html | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/app/components/table/group/group.component.html b/src/app/components/table/group/group.component.html index 8f9c8553c..bfe9cea7c 100644 --- a/src/app/components/table/group/group.component.html +++ b/src/app/components/table/group/group.component.html @@ -1,5 +1,5 @@
-
+

{{ group.name }}

@@ -8,19 +8,19 @@ Total: {{ group.total }}

-
-
+
+

Tasks statuses:

-
-
+ +
-
+
From e49b265ebc91bd2bf18cdd47f425eeea64d145f6 Mon Sep 17 00:00:00 2001 From: Faustin Date: Mon, 10 Feb 2025 17:49:20 +0100 Subject: [PATCH 09/47] chore: update animations location --- eslint.config.mjs | 3 ++- jest.config.ts | 3 ++- src/app/components/table/group/group.component.ts | 14 +++----------- src/app/shared/animations.ts | 13 +++++++++++++ tsconfig.json | 3 +++ 5 files changed, 23 insertions(+), 13 deletions(-) create mode 100644 src/app/shared/animations.ts diff --git a/eslint.config.mjs b/eslint.config.mjs index da6b6d418..74d397510 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -62,7 +62,8 @@ export default tseslint.config( "^@components/", "^@services/", "^@angular/", - "^@pipes/" + "^@pipes/", + "^@shared/", ] } ], diff --git a/jest.config.ts b/jest.config.ts index f0c6d4279..65d11fa92 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -97,7 +97,8 @@ const config: Config = { '@components/(.*)': '/src/app/components/$1', '@services/(.*)': '/src/app/services/$1', '@pipes/(.*)': '/src/app/pipes/$1', - '@app/(.*)': '/src/app/$1' + '@app/(.*)': '/src/app/$1', + '@shared/(.*)': '/src/app/shared/$1', }, // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader diff --git a/src/app/components/table/group/group.component.ts b/src/app/components/table/group/group.component.ts index c825d52cb..fbc53229f 100644 --- a/src/app/components/table/group/group.component.ts +++ b/src/app/components/table/group/group.component.ts @@ -1,4 +1,3 @@ -import { animate, state, style, transition, trigger } from '@angular/animations'; import { AsyncPipe } from '@angular/common'; import { Component, EventEmitter, Input, Output, inject } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; @@ -14,6 +13,7 @@ import { Group } from '@app/types/groups'; import { Status, StatusService } from '@app/types/status'; import { ActionTable } from '@app/types/table'; import { IconsService } from '@services/icons.service'; +import { rotateFull, expand } from '@shared/animations'; import { GroupTasksByStatusComponent } from '../grouped-tasks-by-status/group-tasks-by-status.component'; import { TableActionsComponent } from '../table-actions.component'; import { TableCellComponent } from '../table-cell.component'; @@ -38,16 +38,8 @@ import { TableCellComponent } from '../table-cell.component'; IconsService ], animations: [ - trigger('rotateFull', [ - state('true', style({ transform: 'rotate(-180deg)' })), - state('false', style({ transform: 'rotate(0deg)' })), - transition('true <=> false', [animate('125ms ease-in')]) - ]), - trigger('expand', [ - state('false', style({ 'height': '0' })), - state('true', style({})), - transition('true <=> false', [animate('500ms cubic-bezier(0.4, 0.0, 0.2, 1)')]) - ]) + rotateFull, + expand, ] }) export class TableGroupComponent { diff --git a/src/app/shared/animations.ts b/src/app/shared/animations.ts new file mode 100644 index 000000000..298332053 --- /dev/null +++ b/src/app/shared/animations.ts @@ -0,0 +1,13 @@ +import { animate, state, style, transition, trigger } from '@angular/animations'; + +export const rotateFull = trigger('rotateFull', [ + state('true', style({ transform: 'rotate(-180deg)' })), + state('false', style({ transform: 'rotate(0deg)' })), + transition('true <=> false', [animate('125ms ease-in')]) +]); + +export const expand = trigger('expand', [ + state('false', style({ 'height': '0' })), + state('true', style({})), + transition('true <=> false', [animate('500ms cubic-bezier(0.4, 0.0, 0.2, 1)')]) +]); \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 3ed0cd3ef..7d2dc8eab 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,6 +16,9 @@ "@app/*": [ "src/app/*" ], + "@shared/*": [ + "src/app/shared/*" + ] }, "rootDir": "./", "outDir": "./dist/out-tsc", From f2a89effee5bc4a95533dce7f7fda51ad7c9facd Mon Sep 17 00:00:00 2001 From: faustin Date: Tue, 11 Feb 2025 09:42:53 +0100 Subject: [PATCH 10/47] chore: moved group components location --- .../table/group/{ => group-row}/group.component.css | 0 .../table/group/{ => group-row}/group.component.html | 0 .../table/group/{ => group-row}/group.component.spec.ts | 0 .../components/table/group/{ => group-row}/group.component.ts | 4 ++-- .../group-tasks-by-status.component.css | 0 .../group-tasks-by-status.component.html | 0 .../group-tasks-by-status.component.spec.ts | 0 .../group-tasks-by-status.component.ts | 0 src/app/components/table/table.component.ts | 2 +- 9 files changed, 3 insertions(+), 3 deletions(-) rename src/app/components/table/group/{ => group-row}/group.component.css (100%) rename src/app/components/table/group/{ => group-row}/group.component.html (100%) rename src/app/components/table/group/{ => group-row}/group.component.spec.ts (100%) rename src/app/components/table/group/{ => group-row}/group.component.ts (94%) rename src/app/components/table/{ => group}/grouped-tasks-by-status/group-tasks-by-status.component.css (100%) rename src/app/components/table/{ => group}/grouped-tasks-by-status/group-tasks-by-status.component.html (100%) rename src/app/components/table/{ => group}/grouped-tasks-by-status/group-tasks-by-status.component.spec.ts (100%) rename src/app/components/table/{ => group}/grouped-tasks-by-status/group-tasks-by-status.component.ts (100%) diff --git a/src/app/components/table/group/group.component.css b/src/app/components/table/group/group-row/group.component.css similarity index 100% rename from src/app/components/table/group/group.component.css rename to src/app/components/table/group/group-row/group.component.css diff --git a/src/app/components/table/group/group.component.html b/src/app/components/table/group/group-row/group.component.html similarity index 100% rename from src/app/components/table/group/group.component.html rename to src/app/components/table/group/group-row/group.component.html diff --git a/src/app/components/table/group/group.component.spec.ts b/src/app/components/table/group/group-row/group.component.spec.ts similarity index 100% rename from src/app/components/table/group/group.component.spec.ts rename to src/app/components/table/group/group-row/group.component.spec.ts diff --git a/src/app/components/table/group/group.component.ts b/src/app/components/table/group/group-row/group.component.ts similarity index 94% rename from src/app/components/table/group/group.component.ts rename to src/app/components/table/group/group-row/group.component.ts index fbc53229f..84b66e433 100644 --- a/src/app/components/table/group/group.component.ts +++ b/src/app/components/table/group/group-row/group.component.ts @@ -12,11 +12,11 @@ import { ArmonikData, ColumnKey, DataRaw } from '@app/types/data'; import { Group } from '@app/types/groups'; import { Status, StatusService } from '@app/types/status'; import { ActionTable } from '@app/types/table'; +import { TableActionsComponent } from '@components/table/table-actions.component'; +import { TableCellComponent } from '@components/table/table-cell.component'; import { IconsService } from '@services/icons.service'; import { rotateFull, expand } from '@shared/animations'; import { GroupTasksByStatusComponent } from '../grouped-tasks-by-status/group-tasks-by-status.component'; -import { TableActionsComponent } from '../table-actions.component'; -import { TableCellComponent } from '../table-cell.component'; @Component({ selector: 'app-table-group', diff --git a/src/app/components/table/grouped-tasks-by-status/group-tasks-by-status.component.css b/src/app/components/table/group/grouped-tasks-by-status/group-tasks-by-status.component.css similarity index 100% rename from src/app/components/table/grouped-tasks-by-status/group-tasks-by-status.component.css rename to src/app/components/table/group/grouped-tasks-by-status/group-tasks-by-status.component.css diff --git a/src/app/components/table/grouped-tasks-by-status/group-tasks-by-status.component.html b/src/app/components/table/group/grouped-tasks-by-status/group-tasks-by-status.component.html similarity index 100% rename from src/app/components/table/grouped-tasks-by-status/group-tasks-by-status.component.html rename to src/app/components/table/group/grouped-tasks-by-status/group-tasks-by-status.component.html diff --git a/src/app/components/table/grouped-tasks-by-status/group-tasks-by-status.component.spec.ts b/src/app/components/table/group/grouped-tasks-by-status/group-tasks-by-status.component.spec.ts similarity index 100% rename from src/app/components/table/grouped-tasks-by-status/group-tasks-by-status.component.spec.ts rename to src/app/components/table/group/grouped-tasks-by-status/group-tasks-by-status.component.spec.ts diff --git a/src/app/components/table/grouped-tasks-by-status/group-tasks-by-status.component.ts b/src/app/components/table/group/grouped-tasks-by-status/group-tasks-by-status.component.ts similarity index 100% rename from src/app/components/table/grouped-tasks-by-status/group-tasks-by-status.component.ts rename to src/app/components/table/group/grouped-tasks-by-status/group-tasks-by-status.component.ts diff --git a/src/app/components/table/table.component.ts b/src/app/components/table/table.component.ts index 1d7150d41..98e7d5ab8 100644 --- a/src/app/components/table/table.component.ts +++ b/src/app/components/table/table.component.ts @@ -13,7 +13,7 @@ import { ListOptions } from '@app/types/options'; import { Status, StatusService } from '@app/types/status'; import { ActionTable } from '@app/types/table'; import { TableContainerComponent } from '@components/table-container.component'; -import { TableGroupComponent } from './group/group.component'; +import { TableGroupComponent } from './group/group-row/group.component'; import { TableActionsComponent } from './table-actions.component'; import { TableCellComponent } from './table-cell.component'; import { TableColumnHeaderComponent } from './table-column-header.component'; From 85eb0e34392c8bbaec4c7ce0a64b45b53ce357a7 Mon Sep 17 00:00:00 2001 From: faustin Date: Tue, 11 Feb 2025 16:31:26 +0100 Subject: [PATCH 11/47] feat: manage groups dialog component --- .../manage-groups-dialog.component.css | 66 +++++++++ .../manage-groups-dialog.component.html | 41 ++++++ .../manage-groups-dialog.component.spec.ts | 0 .../manage-groups-dialog.component.ts | 139 ++++++++++++++++++ src/app/services/icons.service.ts | 3 +- src/app/sessions/index.component.html | 6 + src/app/sessions/index.component.ts | 18 ++- 7 files changed, 269 insertions(+), 4 deletions(-) create mode 100644 src/app/components/table/group/manage-groups-dialog/manage-groups-dialog.component.css create mode 100644 src/app/components/table/group/manage-groups-dialog/manage-groups-dialog.component.html create mode 100644 src/app/components/table/group/manage-groups-dialog/manage-groups-dialog.component.spec.ts create mode 100644 src/app/components/table/group/manage-groups-dialog/manage-groups-dialog.component.ts diff --git a/src/app/components/table/group/manage-groups-dialog/manage-groups-dialog.component.css b/src/app/components/table/group/manage-groups-dialog/manage-groups-dialog.component.css new file mode 100644 index 000000000..0c78c41d6 --- /dev/null +++ b/src/app/components/table/group/manage-groups-dialog/manage-groups-dialog.component.css @@ -0,0 +1,66 @@ +mat-dialog-content { + display: flex; + justify-content: space-between; + min-height: 40vh; + max-height: 65vh; + width: 80vw; +} + +mat-divider { + margin-top: 1rem; +} + +#groups-names { + display: flex; + flex-direction: column; + padding-right: 1rem; + margin-right: 1rem; + border-right: 1px solid white; + width: 25%; + max-height: 55vh; +} + +#group-conditions { + display: flex; + flex-direction: column; + width: 100%; + align-items: center; +} + +#no-groups { + height: 100%; + display: flex; + align-items: center; +} + +#conditions-area { + max-height: 55vh; + overflow-y: auto; +} + +mat-form-field { + width: 50vw; +} + +span { + width: fit-content; +} + +ul { + padding: 0; + overflow: auto; + max-height: 55vh; +} + +li { + display: flex; + align-items: center; + width: 100%; + justify-content: space-between; +} + +#or-button { + width: fit-content; + margin-left: 5rem; + margin-top: 1rem; +} \ No newline at end of file diff --git a/src/app/components/table/group/manage-groups-dialog/manage-groups-dialog.component.html b/src/app/components/table/group/manage-groups-dialog/manage-groups-dialog.component.html new file mode 100644 index 000000000..a3734428d --- /dev/null +++ b/src/app/components/table/group/manage-groups-dialog/manage-groups-dialog.component.html @@ -0,0 +1,41 @@ + +
+ + +
    + @for (group of groups; track $index) { +
  • + + +
  • + } +
+
+
+ @if (selectedGroup) { + + + +
+ @for (filters of selectedGroup.conditions; track $index) { + + } +
+ + } @else { +
+

Please select or create a group.

+
+ } +
+
\ No newline at end of file diff --git a/src/app/components/table/group/manage-groups-dialog/manage-groups-dialog.component.spec.ts b/src/app/components/table/group/manage-groups-dialog/manage-groups-dialog.component.spec.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/components/table/group/manage-groups-dialog/manage-groups-dialog.component.ts b/src/app/components/table/group/manage-groups-dialog/manage-groups-dialog.component.ts new file mode 100644 index 000000000..658db78f4 --- /dev/null +++ b/src/app/components/table/group/manage-groups-dialog/manage-groups-dialog.component.ts @@ -0,0 +1,139 @@ +import { Component, inject, Inject } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; +import { MatDividerModule } from '@angular/material/divider'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { Filter, FiltersEnums, FiltersOptionsEnums } from '@app/types/filters'; +import { GroupConditions } from '@app/types/groups'; +import { FiltersDialogOrComponent } from '@components/filters/filters-dialog-or.component'; +import { FiltersService } from '@services/filters.service'; +import { IconsService } from '@services/icons.service'; + +export type ManageGroupsDialogInput = { + groups: GroupConditions[]; + selected?: string; +} + +export type ManageGroupsDialogResult = { + addedGroups: GroupConditions[]; + editedGroups: Record>; + deletedGroups: string[]; +} + +@Component({ + selector: 'app-manage-groups-dialog', + templateUrl: 'manage-groups-dialog.component.html', + styleUrl: 'manage-groups-dialog.component.css', + standalone: true, + imports: [ + MatDialogModule, + MatButtonModule, + MatIconModule, + FiltersDialogOrComponent, + MatDividerModule, + MatFormFieldModule, + MatInputModule, + ], + providers: [ + FiltersService + ] +}) +export class ManageGroupsDialogComponent { + private readonly dialogRef: MatDialogRef, ManageGroupsDialogResult> = inject(MatDialogRef); + constructor(@Inject(MAT_DIALOG_DATA) private readonly dialogData: ManageGroupsDialogInput) { + this.groups = [...dialogData.groups]; + if (dialogData.selected !== undefined) { + this.selectedGroup = this.groups.find(group => group.name === dialogData.selected); + } + } + + private readonly iconsService = inject(IconsService); + + groups: GroupConditions[]; + + addedGroups: GroupConditions[]; + editedGroups: Record>; + deletedGroups: string[]; + + selectedGroup: GroupConditions | undefined; + + selectGroup(group: GroupConditions, setAsEdited: boolean = false) { + this.selectedGroup = group; + if (setAsEdited) { + const groupName = `${group.name}`; + this.editedGroups[groupName] = group; + } + } + + addGroup() { + const group: GroupConditions = { + name: $localize`New Group`, + conditions: [[{ + field: null, + for: null, + operator: null, + value: null, + }]] + }; + this.groups.push(group); + this.selectGroup(group); + this.addedGroups.push(group); + } + + removeGroup(index: number) { + const deletedGroup = this.groups.splice(index, 1)[0]; + if (this.selectedGroup === deletedGroup) { + this.selectedGroup = undefined; + } + if (this.addedGroups.includes(deletedGroup)) { + const index = this.addedGroups.indexOf(deletedGroup); + this.addedGroups.splice(index, 1); + } else { + this.deletedGroups.push(deletedGroup.name); + } + } + + updateName(event: Event) { + if (this.selectedGroup) { + const name = (event.target as HTMLInputElement).value; + this.selectedGroup.name = name; + } + } + + addOrFilter() { + if (this.selectedGroup) { + this.selectedGroup.conditions.push([{ + field: null, + for: null, + operator: null, + value: null, + }]); + } + } + + removeOrFilter(filter: Filter[]) { + if (this.selectedGroup) { + const index = this.selectedGroup.conditions.indexOf(filter); + if (index !== -1) { + this.selectedGroup.conditions.splice(index, 1); + } + if (this.selectedGroup.conditions.length === 0) { + this.addOrFilter(); + } + } + } + + getIcon(name: string) { + return this.iconsService.getIcon(name); + } + + confirmClose() { + this.dialogRef.close({ + addedGroups: this.addedGroups, + deletedGroups: this.deletedGroups, + editedGroups: this.editedGroups + }); + } +} \ No newline at end of file diff --git a/src/app/services/icons.service.ts b/src/app/services/icons.service.ts index 777630813..7081174b8 100644 --- a/src/app/services/icons.service.ts +++ b/src/app/services/icons.service.ts @@ -83,7 +83,8 @@ export class IconsService { 'processed': 'offline_pin', 'retry': 'repeat_on', 'pending': 'pending', - 'table-group': 'diversity_2' + 'table-group': 'diversity_2', + 'save': 'save', }; getIcon(name: string | null | undefined): string { diff --git a/src/app/sessions/index.component.html b/src/app/sessions/index.component.html index 4fd82d3b9..d44015621 100644 --- a/src/app/sessions/index.component.html +++ b/src/app/sessions/index.component.html @@ -21,6 +21,12 @@ (lockColumnsChange)="onLockColumnsChange()" (addToDashboard)="onAddToDashboard()" > + + +
- \ No newline at end of file + + + + + + \ No newline at end of file diff --git a/src/app/components/table/group/manage-groups-dialog/manage-groups-dialog.component.ts b/src/app/components/table/group/manage-groups-dialog/manage-groups-dialog.component.ts index 658db78f4..fc88fbb25 100644 --- a/src/app/components/table/group/manage-groups-dialog/manage-groups-dialog.component.ts +++ b/src/app/components/table/group/manage-groups-dialog/manage-groups-dialog.component.ts @@ -136,4 +136,8 @@ export class ManageGroupsDialogComponent Date: Tue, 11 Feb 2025 17:03:43 +0100 Subject: [PATCH 14/47] chore: handle manage group result --- .../manage-groups-dialog.component.ts | 8 +++---- src/app/sessions/index.component.ts | 8 ++++++- .../services/sessions-data.service.ts | 24 ++++++++++++++++++- 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/src/app/components/table/group/manage-groups-dialog/manage-groups-dialog.component.ts b/src/app/components/table/group/manage-groups-dialog/manage-groups-dialog.component.ts index fc88fbb25..d391c29d2 100644 --- a/src/app/components/table/group/manage-groups-dialog/manage-groups-dialog.component.ts +++ b/src/app/components/table/group/manage-groups-dialog/manage-groups-dialog.component.ts @@ -43,7 +43,7 @@ export type ManageGroupsDialogResult { private readonly dialogRef: MatDialogRef, ManageGroupsDialogResult> = inject(MatDialogRef); constructor(@Inject(MAT_DIALOG_DATA) private readonly dialogData: ManageGroupsDialogInput) { - this.groups = [...dialogData.groups]; + this.groups = structuredClone(dialogData.groups); if (dialogData.selected !== undefined) { this.selectedGroup = this.groups.find(group => group.name === dialogData.selected); } @@ -53,9 +53,9 @@ export class ManageGroupsDialogComponent[]; - addedGroups: GroupConditions[]; - editedGroups: Record>; - deletedGroups: string[]; + addedGroups: GroupConditions[] = []; + editedGroups: Record> = {}; + deletedGroups: string[] = []; selectedGroup: GroupConditions | undefined; diff --git a/src/app/sessions/index.component.ts b/src/app/sessions/index.component.ts index 031dc5714..be038617b 100644 --- a/src/app/sessions/index.component.ts +++ b/src/app/sessions/index.component.ts @@ -124,11 +124,17 @@ export class IndexComponent extends TableHandlerCustomValues, ManageGroupsDialogInput>(ManageGroupsDialogComponent, { + const dialogRef = this.dialog.open, ManageGroupsDialogInput>(ManageGroupsDialogComponent, { data: { groups: this.tableDataService.groupsConditions }, viewContainerRef: this.viewContainerRef }); + + dialogRef.afterClosed().subscribe((result) => { + if (result) { + this.tableDataService.manageGroupDialogResult(result); + } + }); } } diff --git a/src/app/sessions/services/sessions-data.service.ts b/src/app/sessions/services/sessions-data.service.ts index 0d6b4882e..3a5dd42c7 100644 --- a/src/app/sessions/services/sessions-data.service.ts +++ b/src/app/sessions/services/sessions-data.service.ts @@ -8,6 +8,7 @@ import { Filter, FiltersOr } from '@app/types/filters'; import { Group, GroupConditions } from '@app/types/groups'; import { ListOptions } from '@app/types/options'; import { AbstractTableDataService } from '@app/types/services/table-data.service'; +import { ManageGroupsDialogResult } from '@components/table/group/manage-groups-dialog/manage-groups-dialog.component'; import { Duration, Timestamp } from '@ngx-grpc/well-known-types'; import { InvertFilterService } from '@services/invert-filter.service'; import { Subject, map, merge, mergeAll, switchMap } from 'rxjs'; @@ -239,6 +240,24 @@ export class SessionsDataService extends AbstractTableDataService) { + const editedKeys = Object.keys(dialogResult.editedGroups); + editedKeys.forEach((key) => { + const conditionsIndex = this.groupsConditions.findIndex((group) => group.name === key); + if (conditionsIndex !== -1) { + this.groupsConditions[conditionsIndex] = dialogResult.editedGroups[key]; + } + const groupIndex = this.groups.findIndex((group) => group.name === key); + if (groupIndex) { + this.groupsConditions[groupIndex].name = dialogResult.editedGroups[key].name; + } + }); + + dialogResult.addedGroups.forEach((group) => (this.addGroup(group))); + + dialogResult.deletedGroups.forEach((groupName) => (this.removeGroup(groupName))); + } + setGroups() { this.groupsConditions.forEach((g) => this.initGroup(g)); } @@ -256,7 +275,10 @@ export class SessionsDataService extends AbstractTableDataService group.name !== groupName); + const index = this.groups.findIndex((group) => group.name === groupName); + if (index !== -1) { + this.groups.splice(index, 1); + } } // Duration computation From 6bd2d56dcae2e26715820da42ab14830e017f2a3 Mon Sep 17 00:00:00 2001 From: Faustin Date: Wed, 12 Feb 2025 10:18:59 +0100 Subject: [PATCH 15/47] fix: informations update on groups --- .../group/group-row/group.component.html | 2 +- .../table/group/group-row/group.component.ts | 2 +- .../manage-groups-dialog.component.ts | 7 ++- .../sessions/components/table.component.ts | 8 +++- .../services/sessions-data.service.ts | 48 ++++++++++++------- src/app/types/groups.ts | 3 +- 6 files changed, 48 insertions(+), 22 deletions(-) diff --git a/src/app/components/table/group/group-row/group.component.html b/src/app/components/table/group/group-row/group.component.html index 4aae17b27..2f195a0f9 100644 --- a/src/app/components/table/group/group-row/group.component.html +++ b/src/app/components/table/group/group-row/group.component.html @@ -2,7 +2,7 @@
-

{{ group.name }}

+

{{ group.name() }}

Total: diff --git a/src/app/components/table/group/group-row/group.component.ts b/src/app/components/table/group/group-row/group.component.ts index e70741915..a69a4c5db 100644 --- a/src/app/components/table/group/group-row/group.component.ts +++ b/src/app/components/table/group/group-row/group.component.ts @@ -81,6 +81,6 @@ export class TableGroupComponent { private readonly dialogRef: MatDialogRef, ManageGroupsDialogResult> = inject(MatDialogRef); - constructor(@Inject(MAT_DIALOG_DATA) private readonly dialogData: ManageGroupsDialogInput) { + constructor(@Inject(MAT_DIALOG_DATA) dialogData: ManageGroupsDialogInput) { this.groups = structuredClone(dialogData.groups); if (dialogData.selected !== undefined) { - this.selectedGroup = this.groups.find(group => group.name === dialogData.selected); + const group = this.groups.find(group => group.name === dialogData.selected); + if (group) { + this.selectGroup(group, true); + } } } diff --git a/src/app/sessions/components/table.component.ts b/src/app/sessions/components/table.component.ts index 41ab79184..8b1f0399c 100644 --- a/src/app/sessions/components/table.component.ts +++ b/src/app/sessions/components/table.component.ts @@ -171,12 +171,18 @@ export class SessionsTableComponent extends AbstractTaskByStatusTableComponent, ManageGroupsDialogInput>(ManageGroupsDialogComponent, { + const dialogRef = this.dialog.open, ManageGroupsDialogInput>(ManageGroupsDialogComponent, { data: { groups: this.tableDataService.groupsConditions, selected: groupName, }, viewContainerRef: this.viewContainerRef }); + + dialogRef.afterClosed().subscribe((result) => { + if (result) { + this.tableDataService.manageGroupDialogResult(result); + } + }); } } diff --git a/src/app/sessions/services/sessions-data.service.ts b/src/app/sessions/services/sessions-data.service.ts index 3a5dd42c7..919414137 100644 --- a/src/app/sessions/services/sessions-data.service.ts +++ b/src/app/sessions/services/sessions-data.service.ts @@ -1,5 +1,5 @@ import { FilterDateOperator, FilterStringOperator, ListSessionsResponse, ResultRawEnumField, SessionRawEnumField, TaskOptionEnumField, TaskSummaryEnumField } from '@aneoconsultingfr/armonik.api.angular'; -import { Injectable, OnDestroy, inject } from '@angular/core'; +import { Injectable, OnDestroy, inject, signal } from '@angular/core'; import { Params } from '@angular/router'; import { TaskOptions, TaskSummaryFilters } from '@app/tasks/types'; import { Scope } from '@app/types/config'; @@ -11,7 +11,7 @@ import { AbstractTableDataService } from '@app/types/services/table-data.service import { ManageGroupsDialogResult } from '@components/table/group/manage-groups-dialog/manage-groups-dialog.component'; import { Duration, Timestamp } from '@ngx-grpc/well-known-types'; import { InvertFilterService } from '@services/invert-filter.service'; -import { Subject, map, merge, mergeAll, switchMap } from 'rxjs'; +import { Subject, map, merge, mergeAll, of, switchMap } from 'rxjs'; import { SessionsGrpcService } from './sessions-grpc.service'; import { SessionRaw } from '../types'; @@ -51,7 +51,7 @@ export class SessionsDataService extends AbstractTableDataService) { + setGroup(groupName: string) { const groupRefresh$ = new Subject(); const group: Group = { - name: groupCondition.name, + name: signal(groupName), opened: false, total: 0, page: 0, refresh$: groupRefresh$, data: merge(this.refresh$, groupRefresh$).pipe( - switchMap(() => this.grpcService.list$({pageSize: 100, pageIndex: group.page, sort: this.options.sort}, groupCondition.conditions)), + switchMap(() => { + const options = { + pageSize: 100, + pageIndex: group.page, + sort: this.options.sort + }; + const groupConditions = this.groupsConditions.find((condition) => condition.name === group.name()); + if (groupConditions) { + return this.grpcService.list$(options, groupConditions.conditions); + } + return of(undefined); + }), map((data) => { - group.total = data.total; - return this.computeGrpcData(data); + if (data) { + group.total = data.total; + return this.computeGrpcData(data); + } + return undefined; }), map((data) => { if (data) { @@ -247,35 +261,37 @@ export class SessionsDataService extends AbstractTableDataService group.name === key); - if (groupIndex) { - this.groupsConditions[groupIndex].name = dialogResult.editedGroups[key].name; + const groupIndex = this.groups.findIndex((group) => group.name() === key); + if (groupIndex !== -1) { + this.groups[groupIndex].name.set(dialogResult.editedGroups[key].name); } }); dialogResult.addedGroups.forEach((group) => (this.addGroup(group))); dialogResult.deletedGroups.forEach((groupName) => (this.removeGroup(groupName))); + + this.refresh$.next(); } - setGroups() { - this.groupsConditions.forEach((g) => this.initGroup(g)); + initGroups() { + this.groupsConditions.forEach((g) => this.setGroup(g.name)); } addGroup(groupCondition: GroupConditions) { this.groupsConditions.push(groupCondition); - this.initGroup(groupCondition); + this.setGroup(groupCondition.name); } refreshGroup(groupName: string) { - const group = this.groups.find((group) => group.name === groupName); + const group = this.groups.find((group) => group.name() === groupName); if (group) { group.refresh$.next(); } } removeGroup(groupName: string) { - const index = this.groups.findIndex((group) => group.name === groupName); + const index = this.groups.findIndex((group) => group.name() === groupName); if (index !== -1) { this.groups.splice(index, 1); } diff --git a/src/app/types/groups.ts b/src/app/types/groups.ts index 8b37135cf..b0feeac4a 100644 --- a/src/app/types/groups.ts +++ b/src/app/types/groups.ts @@ -1,3 +1,4 @@ +import { WritableSignal } from '@angular/core'; import { TaskOptions } from '@app/tasks/types'; import { Observable, Subject } from 'rxjs'; import { ArmonikData, DataRaw } from './data'; @@ -9,7 +10,7 @@ export type GroupConditions = { - name: string; + name: WritableSignal; opened: boolean; total: number; page: number; From 4cd68a844db9bafd0f5da2fa625ecb9455919faf Mon Sep 17 00:00:00 2001 From: Faustin Date: Wed, 12 Feb 2025 11:09:02 +0100 Subject: [PATCH 16/47] feat: added groups everywhere --- src/app/applications/index.component.html | 1 + .../components/columns-button.component.ts | 1 - .../table-actions-toolbar.component.html | 5 + .../table-actions-toolbar.component.ts | 5 + ...e-dashboard-actions-toolbar.component.html | 1 + ...ble-dashboard-actions-toolbar.component.ts | 5 + ...table-index-actions-toolbar.component.html | 1 + .../table-index-actions-toolbar.component.ts | 5 + .../manage-groups-dialog.component.ts | 10 +- .../lines/applications-line.component.html | 1 + .../lines/partitions-line.component.html | 1 + .../lines/results-line.component.html | 1 + .../lines/sessions-line.component.html | 1 + .../lines/tasks-line.component.html | 1 + src/app/dashboard/index.component.ts | 15 +-- src/app/dashboard/types.ts | 2 + src/app/partitions/index.component.html | 1 + src/app/results/index.component.html | 1 + src/app/services/default-config.service.ts | 5 + src/app/services/table.service.ts | 24 +++- .../sessions/components/table.component.html | 10 +- .../sessions/components/table.component.ts | 24 +--- src/app/sessions/index.component.html | 5 +- src/app/sessions/index.component.ts | 19 +-- .../services/sessions-data.service.ts | 116 +----------------- src/app/tasks/index.component.html | 1 + .../types/components/dashboard-line-table.ts | 29 ++++- src/app/types/components/index.ts | 28 ++++- src/app/types/components/table.ts | 28 ++++- src/app/types/config.ts | 3 +- src/app/types/services/data-filter.service.ts | 15 ++- src/app/types/services/table-data.service.ts | 90 +++++++++++++- 32 files changed, 266 insertions(+), 189 deletions(-) diff --git a/src/app/applications/index.component.html b/src/app/applications/index.component.html index 33d861f3b..3d1fd39e5 100644 --- a/src/app/applications/index.component.html +++ b/src/app/applications/index.component.html @@ -20,6 +20,7 @@ (resetFilters)="onFiltersReset()" (lockColumnsChange)="onLockColumnsChange()" (addToDashboard)="onAddToDashboard()" + (groupSettings)="openGroupsSettings()" > diff --git a/src/app/components/columns-button.component.ts b/src/app/components/columns-button.component.ts index 62f1c13ed..8c17165f4 100644 --- a/src/app/components/columns-button.component.ts +++ b/src/app/components/columns-button.component.ts @@ -20,7 +20,6 @@ import { ColumnsModifyDialogComponent } from './columns-modify-dialog.component' `], standalone: true, imports: [ - ColumnsModifyDialogComponent, MatDialogModule, MatButtonModule, MatIconModule diff --git a/src/app/components/table-actions-toolbar.component.html b/src/app/components/table-actions-toolbar.component.html index fbe6deb0a..aee9c9c7d 100644 --- a/src/app/components/table-actions-toolbar.component.html +++ b/src/app/components/table-actions-toolbar.component.html @@ -9,6 +9,11 @@ + + = new EventEmitter(); @Output() resetFilters: EventEmitter = new EventEmitter(); @Output() lockColumnsChange = new EventEmitter(); + @Output() groupSettings = new EventEmitter(); getIcon(name: string): string { return this.iconsService.getIcon(name); @@ -78,4 +79,8 @@ export class TableActionsToolbarComponent diff --git a/src/app/components/table-dashboard-actions-toolbar.component.ts b/src/app/components/table-dashboard-actions-toolbar.component.ts index 2004f76f0..67e966b1e 100644 --- a/src/app/components/table-dashboard-actions-toolbar.component.ts +++ b/src/app/components/table-dashboard-actions-toolbar.component.ts @@ -35,6 +35,7 @@ export class TableDashboardActionsToolbarComponent(); @Output() editNameLine = new EventEmitter(); @Output() deleteLine = new EventEmitter(); + @Output() groupSettings = new EventEmitter(); getIcon(name: string): string { return this.iconsService.getIcon(name); @@ -71,4 +72,8 @@ export class TableDashboardActionsToolbarComponent diff --git a/src/app/components/table-index-actions-toolbar.component.ts b/src/app/components/table-index-actions-toolbar.component.ts index 37a7a1b5c..6da3e9a69 100644 --- a/src/app/components/table-index-actions-toolbar.component.ts +++ b/src/app/components/table-index-actions-toolbar.component.ts @@ -35,6 +35,7 @@ export class TableIndexActionsToolbarComponent = new EventEmitter(); @Output() lockColumnsChange = new EventEmitter(); @Output() addToDashboard = new EventEmitter(); + @Output() groupSettings = new EventEmitter(); getIcon(value: string): string { return this.iconsService.getIcon(value); @@ -67,4 +68,8 @@ export class TableIndexActionsToolbarComponent = { +export type ManageGroupsTableDialogInput = { groups: GroupConditions[]; selected?: string; } -export type ManageGroupsDialogResult = { +export type ManageGroupsTableDialogResult = { addedGroups: GroupConditions[]; editedGroups: Record>; deletedGroups: string[]; @@ -40,9 +40,9 @@ export type ManageGroupsDialogResult { - private readonly dialogRef: MatDialogRef, ManageGroupsDialogResult> = inject(MatDialogRef); - constructor(@Inject(MAT_DIALOG_DATA) dialogData: ManageGroupsDialogInput) { +export class ManageTableGroupsDialogComponent { + private readonly dialogRef: MatDialogRef, ManageGroupsTableDialogResult> = inject(MatDialogRef); + constructor(@Inject(MAT_DIALOG_DATA) dialogData: ManageGroupsTableDialogInput) { this.groups = structuredClone(dialogData.groups); if (dialogData.selected !== undefined) { const group = this.groups.find(group => group.name === dialogData.selected); diff --git a/src/app/dashboard/components/lines/applications-line.component.html b/src/app/dashboard/components/lines/applications-line.component.html index a4b26d040..822e99dca 100644 --- a/src/app/dashboard/components/lines/applications-line.component.html +++ b/src/app/dashboard/components/lines/applications-line.component.html @@ -16,6 +16,7 @@ (lockColumnsChange)="onLockColumnsChange()" (deleteLine)="onDeleteLine()" (editNameLine)="onEditNameLine()" + (groupSettings)="openGroupsSettings()" > diff --git a/src/app/dashboard/components/lines/partitions-line.component.html b/src/app/dashboard/components/lines/partitions-line.component.html index 402b611be..d5f15d323 100644 --- a/src/app/dashboard/components/lines/partitions-line.component.html +++ b/src/app/dashboard/components/lines/partitions-line.component.html @@ -16,6 +16,7 @@ (lockColumnsChange)="onLockColumnsChange()" (deleteLine)="onDeleteLine()" (editNameLine)="onEditNameLine()" + (groupSettings)="openGroupsSettings()" > diff --git a/src/app/dashboard/components/lines/results-line.component.html b/src/app/dashboard/components/lines/results-line.component.html index 3754cc45d..35740959d 100644 --- a/src/app/dashboard/components/lines/results-line.component.html +++ b/src/app/dashboard/components/lines/results-line.component.html @@ -16,6 +16,7 @@ (lockColumnsChange)="onLockColumnsChange()" (deleteLine)="onDeleteLine()" (editNameLine)="onEditNameLine()" + (groupSettings)="openGroupsSettings()" > diff --git a/src/app/dashboard/components/lines/sessions-line.component.html b/src/app/dashboard/components/lines/sessions-line.component.html index 0f628f8b5..7c2fef4c6 100644 --- a/src/app/dashboard/components/lines/sessions-line.component.html +++ b/src/app/dashboard/components/lines/sessions-line.component.html @@ -16,6 +16,7 @@ (lockColumnsChange)="onLockColumnsChange()" (deleteLine)="onDeleteLine()" (editNameLine)="onEditNameLine()" + (groupSettings)="openGroupsSettings()" >

- +
+
@for (column of displayedColumns; track $index) { diff --git a/src/app/partitions/components/table.component.ts b/src/app/partitions/components/table.component.ts index a39e62491..c0e6c527d 100644 --- a/src/app/partitions/components/table.component.ts +++ b/src/app/partitions/components/table.component.ts @@ -2,10 +2,12 @@ import { PartitionRawEnumField } from '@aneoconsultingfr/armonik.api.angular'; import { Component, OnInit, inject } from '@angular/core'; import { AbstractTaskByStatusTableComponent } from '@app/types/components/table'; import { ArmonikData } from '@app/types/data'; +import { Group } from '@app/types/groups'; import { TableComponent } from '@components/table/table.component'; import { FiltersService } from '@services/filters.service'; import { TableTasksByStatus, TasksByStatusService } from '@services/tasks-by-status.service'; import PartitionsDataService from '../services/partitions-data.service'; +import { PartitionsFiltersService } from '../services/partitions-filters.service'; import { PartitionRaw } from '../types'; @Component({ @@ -24,6 +26,7 @@ export class PartitionsTableComponent extends AbstractTaskByStatusTableComponent implements OnInit { readonly tableDataService = inject(PartitionsDataService); + readonly filtersService = inject(PartitionsFiltersService); table: TableTasksByStatus = 'partitions'; @@ -36,7 +39,11 @@ export class PartitionsTableComponent extends AbstractTaskByStatusTableComponent return value.id === entry.id; } - trackBy(index: number, items: ArmonikData) { - return items.raw.id; + trackBy(index: number, item: ArmonikData | Group) { + if ((item as ArmonikData).raw !== undefined) { + return (item as ArmonikData).raw.id; + } else { + return (item as Group).name(); + } } } \ No newline at end of file diff --git a/src/app/results/components/table.component.ts b/src/app/results/components/table.component.ts index 46fec411e..9dc27f8b1 100644 --- a/src/app/results/components/table.component.ts +++ b/src/app/results/components/table.component.ts @@ -2,9 +2,11 @@ import { ResultRawEnumField } from '@aneoconsultingfr/armonik.api.angular'; import { Component, OnInit, inject } from '@angular/core'; import { AbstractTableComponent } from '@app/types/components/table'; import { ArmonikData } from '@app/types/data'; +import { Group } from '@app/types/groups'; import { TableComponent } from '@components/table/table.component'; import { NotificationService } from '@services/notification.service'; import ResultsDataService from '../services/results-data.service'; +import { ResultsFiltersService } from '../services/results-filters.service'; import { ResultsStatusesService } from '../services/results-statuses.service'; import { ResultRaw } from '../types'; @@ -22,6 +24,7 @@ import { ResultRaw } from '../types'; }) export class ResultsTableComponent extends AbstractTableComponent implements OnInit { readonly tableDataService = inject(ResultsDataService); + readonly filtersService = inject(ResultsFiltersService); readonly statusesService = inject(ResultsStatusesService); ngOnInit(): void { @@ -32,7 +35,11 @@ export class ResultsTableComponent extends AbstractTableComponent): string | number { - return item.raw.resultId; + trackBy(index: number, item: ArmonikData | Group): string | number { + if ((item as ArmonikData).raw !== undefined) { + return (item as ArmonikData).raw.resultId; + } else { + return (item as Group).name(); + } } } \ No newline at end of file diff --git a/src/app/sessions/components/table.component.ts b/src/app/sessions/components/table.component.ts index eaee5f645..a7338848b 100644 --- a/src/app/sessions/components/table.component.ts +++ b/src/app/sessions/components/table.component.ts @@ -6,11 +6,13 @@ import { Router, RouterModule } from '@angular/router'; import { TaskOptions } from '@app/tasks/types'; import { AbstractTaskByStatusTableComponent } from '@app/types/components/table'; import { ArmonikData, SessionData } from '@app/types/data'; +import { Group } from '@app/types/groups'; import { ActionTable } from '@app/types/table'; import { TableComponent } from '@components/table/table.component'; import { TableTasksByStatus, TasksByStatusService } from '@services/tasks-by-status.service'; import { Subject } from 'rxjs'; import { SessionsDataService } from '../services/sessions-data.service'; +import { SessionsFiltersService } from '../services/sessions-filters.service'; import { SessionsStatusesService } from '../services/sessions-statuses.service'; import { SessionRaw } from '../types'; @@ -31,6 +33,7 @@ import { SessionRaw } from '../types'; export class SessionsTableComponent extends AbstractTaskByStatusTableComponent implements OnInit { readonly statusesService = inject(SessionsStatusesService); + readonly filtersService = inject(SessionsFiltersService); readonly router = inject(Router); readonly copyService = inject(Clipboard); @@ -159,8 +162,11 @@ export class SessionsTableComponent extends AbstractTaskByStatusTableComponent) { - return index; + trackBy(index: number, item: ArmonikData | Group) { + if ((item as ArmonikData).raw !== undefined) { + return (item as ArmonikData).raw.sessionId; + } else { + return (item as Group).name(); + } } } diff --git a/src/app/tasks/components/table.component.ts b/src/app/tasks/components/table.component.ts index 0af17419a..46ed5564a 100644 --- a/src/app/tasks/components/table.component.ts +++ b/src/app/tasks/components/table.component.ts @@ -5,10 +5,12 @@ import { Router} from '@angular/router'; import { AbstractTableComponent } from '@app/types/components/table'; import { Scope } from '@app/types/config'; import { ArmonikData, TaskData } from '@app/types/data'; +import { Group } from '@app/types/groups'; import { ActionTable } from '@app/types/table'; import { TableComponent } from '@components/table/table.component'; import { Subject } from 'rxjs'; import TasksDataService from '../services/tasks-data.service'; +import { TasksFiltersService } from '../services/tasks-filters.service'; import { TasksStatusesService } from '../services/tasks-statuses.service'; import { TaskOptions, TaskSummary } from '../types'; @@ -50,6 +52,7 @@ export class TasksTableComponent extends AbstractTableComponent(); readonly tableDataService = inject(TasksDataService); + readonly filtersService = inject(TasksFiltersService); readonly router = inject(Router); readonly clipboard = inject(Clipboard); readonly tasksStatusesService = inject(TasksStatusesService); @@ -171,7 +174,11 @@ export class TasksTableComponent extends AbstractTableComponent) { - return item.raw.id; + trackBy(index: number, item: ArmonikData | Group) { + if ((item as ArmonikData).raw !== undefined) { + return (item as ArmonikData).raw.id; + } else { + return (item as Group).name(); + } } } \ No newline at end of file diff --git a/src/app/types/components/index.ts b/src/app/types/components/index.ts index 4316ca74f..31dcd673c 100644 --- a/src/app/types/components/index.ts +++ b/src/app/types/components/index.ts @@ -199,13 +199,12 @@ export abstract class TableHandler { + dialogRef.afterClosed().subscribe((result) => { if (result) { this.tableDataService.manageGroupDialogResult(result); + this.filtersService.saveGroups(this.tableDataService.groupsConditions); } }); - - subscription.unsubscribe(); } protected createDashboardLine(): TableLine { diff --git a/src/app/types/components/table.ts b/src/app/types/components/table.ts index 9895a81f5..885f0df75 100644 --- a/src/app/types/components/table.ts +++ b/src/app/types/components/table.ts @@ -11,6 +11,7 @@ import { TableColumn } from '../column.type'; import { ArmonikData, ColumnKey, DataRaw } from '../data'; import { FiltersEnums, FiltersOptionsEnums, FiltersOr } from '../filters'; import { ListOptions } from '../options'; +import { DataFilterService } from '../services/data-filter.service'; import { AbstractTableDataService } from '../services/table-data.service'; export interface SelectableTable { @@ -48,6 +49,7 @@ export abstract class AbstractTableComponent; readonly dialog = inject(MatDialog); private readonly viewContainerRef = inject(ViewContainerRef); + abstract readonly filtersService: DataFilterService; protected initTableDataService() { this.data = this.tableDataService.data; @@ -77,13 +79,12 @@ export abstract class AbstractTableComponent { + dialogRef.afterClosed().subscribe((result) => { if (result) { this.tableDataService.manageGroupDialogResult(result); + this.filtersService.saveGroups(this.tableDataService.groupsConditions); } }); - - subscription.unsubscribe(); } abstract isDataRawEqual(value: T, entry: T): boolean; diff --git a/src/app/types/services/table-data.service.ts b/src/app/types/services/table-data.service.ts index 3275b078c..b88f323f8 100644 --- a/src/app/types/services/table-data.service.ts +++ b/src/app/types/services/table-data.service.ts @@ -191,9 +191,13 @@ export abstract class AbstractTableDataService group.name() === groupName); - if (index !== -1) { - this.groups.splice(index, 1); + const conditionsIndex = this.groupsConditions.findIndex((group) => group.name === groupName); + if (conditionsIndex !== -1) { + this.groupsConditions.splice(conditionsIndex, 1); + } + const groupIndex = this.groups.findIndex((group) => group.name() === groupName); + if (groupIndex !== -1) { + this.groups.splice(groupIndex, 1); } } From 0640cc036f12f39a571432914deae1aa248b13f5 Mon Sep 17 00:00:00 2001 From: Faustin Date: Wed, 12 Feb 2025 11:50:13 +0100 Subject: [PATCH 18/47] added groups to tables --- src/app/applications/components/table.component.html | 10 +++++++--- .../table/group/group-row/group.component.html | 10 ++++++---- src/app/partitions/components/table.component.html | 8 ++++++-- src/app/results/components/table.component.html | 10 +++++++--- src/app/tasks/components/table.component.html | 10 +++++++--- 5 files changed, 33 insertions(+), 15 deletions(-) diff --git a/src/app/applications/components/table.component.html b/src/app/applications/components/table.component.html index f0db90e2d..25765d810 100644 --- a/src/app/applications/components/table.component.html +++ b/src/app/applications/components/table.component.html @@ -1,3 +1,7 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/app/components/table/group/group-row/group.component.html b/src/app/components/table/group/group-row/group.component.html index 1497476b4..1aaeafb4b 100644 --- a/src/app/components/table/group/group-row/group.component.html +++ b/src/app/components/table/group/group-row/group.component.html @@ -9,10 +9,12 @@ {{ group.total }}

-
-

Tasks statuses:

- -
+ @if (statusesGroups) { +
+

Tasks statuses:

+ +
+ }
}
+ @if (group.emptyCondition) { + + +

No conditions

+
+ } diff --git a/src/app/components/table/group/group-row/group.component.ts b/src/app/components/table/group/group-row/group.component.ts index a69a4c5db..315e597c0 100644 --- a/src/app/components/table/group/group-row/group.component.ts +++ b/src/app/components/table/group/group-row/group.component.ts @@ -2,6 +2,7 @@ import { AsyncPipe } from '@angular/common'; import { Component, EventEmitter, Input, Output, inject } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; +import { MatChipsModule } from '@angular/material/chips'; import { MatIconModule } from '@angular/material/icon'; import { MatPaginatorModule, PageEvent } from '@angular/material/paginator'; import { MatTableModule } from '@angular/material/table'; @@ -33,6 +34,7 @@ import { GroupTasksByStatusComponent } from '../grouped-tasks-by-status/group-ta TableActionsComponent, GroupTasksByStatusComponent, AsyncPipe, + MatChipsModule, ], providers: [ IconsService diff --git a/src/app/types/groups.ts b/src/app/types/groups.ts index b0feeac4a..044cb7c65 100644 --- a/src/app/types/groups.ts +++ b/src/app/types/groups.ts @@ -14,6 +14,7 @@ export type Group = { opened: boolean; total: number; page: number; + emptyCondition: boolean; refresh$: Subject, data: Observable[]> } \ No newline at end of file diff --git a/src/app/types/services/table-data.service.ts b/src/app/types/services/table-data.service.ts index b88f323f8..280cff199 100644 --- a/src/app/types/services/table-data.service.ts +++ b/src/app/types/services/table-data.service.ts @@ -121,6 +121,7 @@ export abstract class AbstractTableDataService { @@ -131,7 +132,10 @@ export abstract class AbstractTableDataService condition.name === group.name()); if (groupConditions) { - return this.grpcService.list$(options, groupConditions.conditions); + if (this.isNotEmptyCondition(groupConditions)) { + return this.grpcService.list$(options, groupConditions.conditions); + } + group.emptyCondition = true; } return of(undefined); }), @@ -139,8 +143,10 @@ export abstract class AbstractTableDataService { if (data) { @@ -178,7 +184,7 @@ export abstract class AbstractTableDataService this.setGroup(g.name)); } - addGroup(groupCondition: GroupConditions) { + private addGroup(groupCondition: GroupConditions) { this.groupsConditions.push(groupCondition); this.setGroup(groupCondition.name); } @@ -190,7 +196,7 @@ export abstract class AbstractTableDataService group.name === groupName); if (conditionsIndex !== -1) { this.groupsConditions.splice(conditionsIndex, 1); @@ -201,6 +207,14 @@ export abstract class AbstractTableDataService): boolean { + return groupCondition.conditions.map((condition) => + condition + .map((filter) => filter.for !== null && filter.field !== null && filter.operator !== null && filter.value !== null) + .reduce((acc, current) => acc || current, false) + ).reduce((acc, current) => acc || current, false); + } + /** * Display a success message to the user. */ From 0c410be1bcd95ec7d8c4e4cf4eabc26c08978ff2 Mon Sep 17 00:00:00 2001 From: Faustin Date: Thu, 13 Feb 2025 11:15:28 +0100 Subject: [PATCH 22/47] fix: filters and multiple groups are now filtering as wanted. --- src/app/applications/index.component.ts | 2 ++ .../lines/applications-line.component.ts | 2 ++ .../lines/partitions-line.component.ts | 2 ++ .../lines/results-line.component.ts | 2 ++ .../lines/sessions-line.component.ts | 2 ++ .../components/lines/tasks-line.component.ts | 2 ++ src/app/partitions/index.component.ts | 2 ++ src/app/results/index.component.ts | 2 ++ .../services/sessions-data.service.ts | 3 --- src/app/tasks/index.component.ts | 2 ++ src/app/types/services/table-data.service.ts | 27 ++++++++++++++----- 11 files changed, 39 insertions(+), 9 deletions(-) diff --git a/src/app/applications/index.component.ts b/src/app/applications/index.component.ts index ca33c66a1..c86556e1d 100644 --- a/src/app/applications/index.component.ts +++ b/src/app/applications/index.component.ts @@ -19,6 +19,7 @@ import { CacheService } from '@services/cache.service'; import { FiltersService } from '@services/filters.service'; import { GrpcSortFieldService } from '@services/grpc-sort-field.service'; import { IconsService } from '@services/icons.service'; +import { InvertFilterService } from '@services/invert-filter.service'; import { NotificationService } from '@services/notification.service'; import { QueryParamsService } from '@services/query-params.service'; import { ShareUrlService } from '@services/share-url.service'; @@ -63,6 +64,7 @@ import { ApplicationRaw } from './types'; NotificationService, FiltersService, GrpcSortFieldService, + InvertFilterService, ], imports: [ PageHeaderComponent, diff --git a/src/app/dashboard/components/lines/applications-line.component.ts b/src/app/dashboard/components/lines/applications-line.component.ts index e4de80d3a..755dcd211 100644 --- a/src/app/dashboard/components/lines/applications-line.component.ts +++ b/src/app/dashboard/components/lines/applications-line.component.ts @@ -18,6 +18,7 @@ import { AutoRefreshService } from '@services/auto-refresh.service'; import { DefaultConfigService } from '@services/default-config.service'; import { FiltersService } from '@services/filters.service'; import { GrpcSortFieldService } from '@services/grpc-sort-field.service'; +import { InvertFilterService } from '@services/invert-filter.service'; import { NotificationService } from '@services/notification.service'; import { ShareUrlService } from '@services/share-url.service'; @@ -41,6 +42,7 @@ import { ShareUrlService } from '@services/share-url.service'; FiltersService, ApplicationsDataService, GrpcSortFieldService, + InvertFilterService, ], imports: [ FiltersToolbarComponent, diff --git a/src/app/dashboard/components/lines/partitions-line.component.ts b/src/app/dashboard/components/lines/partitions-line.component.ts index 8d4f8cc35..bcd355a17 100644 --- a/src/app/dashboard/components/lines/partitions-line.component.ts +++ b/src/app/dashboard/components/lines/partitions-line.component.ts @@ -15,6 +15,7 @@ import { DataFilterService } from '@app/types/services/data-filter.service'; import { FiltersToolbarComponent } from '@components/filters/filters-toolbar.component'; import { TableDashboardActionsToolbarComponent } from '@components/table-dashboard-actions-toolbar.component'; import { GrpcSortFieldService } from '@services/grpc-sort-field.service'; +import { InvertFilterService } from '@services/invert-filter.service'; import { NotificationService } from '@services/notification.service'; @Component({ @@ -33,6 +34,7 @@ import { NotificationService } from '@services/notification.service'; PartitionsGrpcService, PartitionsDataService, GrpcSortFieldService, + InvertFilterService, ], imports: [ MatToolbarModule, diff --git a/src/app/dashboard/components/lines/results-line.component.ts b/src/app/dashboard/components/lines/results-line.component.ts index e0d793d28..8b1bd0c19 100644 --- a/src/app/dashboard/components/lines/results-line.component.ts +++ b/src/app/dashboard/components/lines/results-line.component.ts @@ -17,6 +17,7 @@ import { FiltersToolbarComponent } from '@components/filters/filters-toolbar.com import { TableDashboardActionsToolbarComponent } from '@components/table-dashboard-actions-toolbar.component'; import { FiltersService } from '@services/filters.service'; import { GrpcSortFieldService } from '@services/grpc-sort-field.service'; +import { InvertFilterService } from '@services/invert-filter.service'; import { NotificationService } from '@services/notification.service'; @Component({ @@ -37,6 +38,7 @@ import { NotificationService } from '@services/notification.service'; GrpcSortFieldService, FiltersService, NotificationService, + InvertFilterService, ], imports: [ MatIconModule, diff --git a/src/app/dashboard/components/lines/sessions-line.component.ts b/src/app/dashboard/components/lines/sessions-line.component.ts index 055436ae0..782d78594 100644 --- a/src/app/dashboard/components/lines/sessions-line.component.ts +++ b/src/app/dashboard/components/lines/sessions-line.component.ts @@ -21,6 +21,7 @@ import { FiltersToolbarComponent } from '@components/filters/filters-toolbar.com import { TableDashboardActionsToolbarComponent } from '@components/table-dashboard-actions-toolbar.component'; import { FiltersService } from '@services/filters.service'; import { GrpcSortFieldService } from '@services/grpc-sort-field.service'; +import { InvertFilterService } from '@services/invert-filter.service'; import { NotificationService } from '@services/notification.service'; @Component({ @@ -44,6 +45,7 @@ import { NotificationService } from '@services/notification.service'; GrpcSortFieldService, FiltersService, TasksGrpcService, + InvertFilterService, ], imports: [ MatToolbarModule, diff --git a/src/app/dashboard/components/lines/tasks-line.component.ts b/src/app/dashboard/components/lines/tasks-line.component.ts index 5435eb0af..4df80e951 100644 --- a/src/app/dashboard/components/lines/tasks-line.component.ts +++ b/src/app/dashboard/components/lines/tasks-line.component.ts @@ -19,6 +19,7 @@ import { FiltersToolbarComponent } from '@components/filters/filters-toolbar.com import { TableDashboardActionsToolbarComponent } from '@components/table-dashboard-actions-toolbar.component'; import { FiltersService } from '@services/filters.service'; import { GrpcSortFieldService } from '@services/grpc-sort-field.service'; +import { InvertFilterService } from '@services/invert-filter.service'; import { NotificationService } from '@services/notification.service'; @Component({ @@ -38,6 +39,7 @@ import { NotificationService } from '@services/notification.service'; GrpcSortFieldService, TasksDataService, FiltersService, + InvertFilterService, ], imports: [ MatToolbarModule, diff --git a/src/app/partitions/index.component.ts b/src/app/partitions/index.component.ts index 6b617c490..d4d39a34c 100644 --- a/src/app/partitions/index.component.ts +++ b/src/app/partitions/index.component.ts @@ -18,6 +18,7 @@ import { TableIndexActionsToolbarComponent } from '@components/table-index-actio import { AutoRefreshService } from '@services/auto-refresh.service'; import { FiltersService } from '@services/filters.service'; import { GrpcSortFieldService } from '@services/grpc-sort-field.service'; +import { InvertFilterService } from '@services/invert-filter.service'; import { NotificationService } from '@services/notification.service'; import { QueryParamsService } from '@services/query-params.service'; import { ShareUrlService } from '@services/share-url.service'; @@ -62,6 +63,7 @@ import { PartitionRaw } from './types'; PartitionsDataService, GrpcSortFieldService, PartitionsGrpcService, + InvertFilterService, ], imports: [ PageHeaderComponent, diff --git a/src/app/results/index.component.ts b/src/app/results/index.component.ts index ebb4b5368..854f198f7 100644 --- a/src/app/results/index.component.ts +++ b/src/app/results/index.component.ts @@ -17,6 +17,7 @@ import { TableIndexActionsToolbarComponent } from '@components/table-index-actio import { AutoRefreshService } from '@services/auto-refresh.service'; import { FiltersService } from '@services/filters.service'; import { GrpcSortFieldService } from '@services/grpc-sort-field.service'; +import { InvertFilterService } from '@services/invert-filter.service'; import { NotificationService } from '@services/notification.service'; import { QueryParamsService } from '@services/query-params.service'; import { ShareUrlService } from '@services/share-url.service'; @@ -61,6 +62,7 @@ import { ResultRaw } from './types'; ResultsGrpcService, GrpcSortFieldService, FiltersService, + InvertFilterService, ], imports: [ PageHeaderComponent, diff --git a/src/app/sessions/services/sessions-data.service.ts b/src/app/sessions/services/sessions-data.service.ts index 1d9a3d65c..cb6e23084 100644 --- a/src/app/sessions/services/sessions-data.service.ts +++ b/src/app/sessions/services/sessions-data.service.ts @@ -8,7 +8,6 @@ import { Filter, FiltersOr } from '@app/types/filters'; import { ListOptions } from '@app/types/options'; import { AbstractTableDataService } from '@app/types/services/table-data.service'; import { Duration, Timestamp } from '@ngx-grpc/well-known-types'; -import { InvertFilterService } from '@services/invert-filter.service'; import { Subject, map, mergeAll } from 'rxjs'; import { SessionsGrpcService } from './sessions-grpc.service'; import { SessionRaw } from '../types'; @@ -16,7 +15,6 @@ import { SessionRaw } from '../types'; @Injectable() export class SessionsDataService extends AbstractTableDataService implements OnDestroy { readonly grpcService = inject(SessionsGrpcService); - readonly invertFiltersService: InvertFilterService = inject(InvertFilterService); scope: Scope = 'sessions'; @@ -60,7 +58,6 @@ export class SessionsDataService extends AbstractTableDataService { const filtersOr = super.preparefilters(); - this.groupsConditions.forEach((groupConditions) => (filtersOr.push(...this.invertFiltersService.invert(groupConditions.conditions)))); if(this.isDurationDisplayed && this.options.sort.active === 'duration') { const date = new Date(); date.setDate(date.getDate() - 3); diff --git a/src/app/tasks/index.component.ts b/src/app/tasks/index.component.ts index b69d76253..4860b4381 100644 --- a/src/app/tasks/index.component.ts +++ b/src/app/tasks/index.component.ts @@ -17,6 +17,7 @@ import { TableIndexActionsToolbarComponent } from '@components/table-index-actio import { AutoRefreshService } from '@services/auto-refresh.service'; import { FiltersService } from '@services/filters.service'; import { GrpcSortFieldService } from '@services/grpc-sort-field.service'; +import { InvertFilterService } from '@services/invert-filter.service'; import { NotificationService } from '@services/notification.service'; import { QueryParamsService } from '@services/query-params.service'; import { ShareUrlService } from '@services/share-url.service'; @@ -71,6 +72,7 @@ import { TaskOptions, TaskSummary, TaskSummaryFilter } from './types'; DashboardStorageService, GrpcSortFieldService, TasksDataService, + InvertFilterService, ], }) export class IndexComponent extends TableHandlerCustomValues implements OnInit, AfterViewInit, OnDestroy { diff --git a/src/app/types/services/table-data.service.ts b/src/app/types/services/table-data.service.ts index 280cff199..e0f436547 100644 --- a/src/app/types/services/table-data.service.ts +++ b/src/app/types/services/table-data.service.ts @@ -7,6 +7,7 @@ import { ManageGroupsTableDialogResult } from '@components/table/group/manage-gr import { GrpcStatusEvent } from '@ngx-grpc/common'; import { CacheService } from '@services/cache.service'; import { FiltersService } from '@services/filters.service'; +import { InvertFilterService } from '@services/invert-filter.service'; import { NotificationService } from '@services/notification.service'; import { Subject, Subscription, catchError, map, merge, of, switchMap } from 'rxjs'; import { GrpcTableService } from './grpcService'; @@ -22,6 +23,7 @@ export abstract class AbstractTableDataService = inject(InvertFilterService); readonly refresh$ = new Subject(); @@ -99,10 +101,23 @@ export abstract class AbstractTableDataService { - return structuredClone(this.filters); + let filtersOr = structuredClone(this.filters); + this.groupsConditions.forEach((groupConditions) => { + const inverted = this.invertFiltersService.invert(groupConditions.conditions); + if (!this.isNotEmptyFilter(filtersOr)) { + filtersOr.push(...inverted); + } else { + const result: FiltersOr = []; + filtersOr.forEach((filterAnd) => { + inverted.map((invertedAnd) => [...invertedAnd, ...filterAnd]).forEach((r) => result.push(r)); + }); + filtersOr = result; + } + }); + return filtersOr; } /** @@ -132,7 +147,7 @@ export abstract class AbstractTableDataService condition.name === group.name()); if (groupConditions) { - if (this.isNotEmptyCondition(groupConditions)) { + if (this.isNotEmptyFilter(groupConditions.conditions)) { return this.grpcService.list$(options, groupConditions.conditions); } group.emptyCondition = true; @@ -207,9 +222,9 @@ export abstract class AbstractTableDataService): boolean { - return groupCondition.conditions.map((condition) => - condition + private isNotEmptyFilter(filters: FiltersOr): boolean { + return filters.map((filterAnd) => + filterAnd .map((filter) => filter.for !== null && filter.field !== null && filter.operator !== null && filter.value !== null) .reduce((acc, current) => acc || current, false) ).reduce((acc, current) => acc || current, false); From d729f388ed5c9a1e31742356f6da860b9228f2cb Mon Sep 17 00:00:00 2001 From: Faustin Date: Thu, 13 Feb 2025 11:18:59 +0100 Subject: [PATCH 23/47] chore: address sonarcloud issues --- src/app/components/table/group/group-row/group.component.html | 2 +- .../grouped-tasks-by-status/group-tasks-by-status.component.css | 0 .../grouped-tasks-by-status/group-tasks-by-status.component.ts | 1 - 3 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 src/app/components/table/group/grouped-tasks-by-status/group-tasks-by-status.component.css diff --git a/src/app/components/table/group/group-row/group.component.html b/src/app/components/table/group/group-row/group.component.html index f03d002f6..4af0fe412 100644 --- a/src/app/components/table/group/group-row/group.component.html +++ b/src/app/components/table/group/group-row/group.component.html @@ -38,7 +38,7 @@
@for (column of displayedColumns; track $index) { - +
@if (column.type !== 'actions') { diff --git a/src/app/components/table/group/grouped-tasks-by-status/group-tasks-by-status.component.css b/src/app/components/table/group/grouped-tasks-by-status/group-tasks-by-status.component.css deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/app/components/table/group/grouped-tasks-by-status/group-tasks-by-status.component.ts b/src/app/components/table/group/grouped-tasks-by-status/group-tasks-by-status.component.ts index 2b71c8039..f705b1fc6 100644 --- a/src/app/components/table/group/grouped-tasks-by-status/group-tasks-by-status.component.ts +++ b/src/app/components/table/group/grouped-tasks-by-status/group-tasks-by-status.component.ts @@ -8,7 +8,6 @@ import { Subject } from 'rxjs'; @Component({ selector: 'app-group-tasks-by-status', templateUrl: 'group-tasks-by-status.component.html', - styleUrl: 'group-tasks-by-status.component.css', standalone: true, imports: [ CountTasksByStatusComponent, From 6fe9d3e3abb61084e5dbc9f6675e9b2c7e810303 Mon Sep 17 00:00:00 2001 From: Faustin Date: Thu, 13 Feb 2025 11:36:50 +0100 Subject: [PATCH 24/47] tests: group tasks by status component --- .../group-tasks-by-status.component.spec.ts | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/src/app/components/table/group/grouped-tasks-by-status/group-tasks-by-status.component.spec.ts b/src/app/components/table/group/grouped-tasks-by-status/group-tasks-by-status.component.spec.ts index e69de29bb..2adf46207 100644 --- a/src/app/components/table/group/grouped-tasks-by-status/group-tasks-by-status.component.spec.ts +++ b/src/app/components/table/group/grouped-tasks-by-status/group-tasks-by-status.component.spec.ts @@ -0,0 +1,82 @@ +import { FilterStringOperator, TaskStatus, TaskSummaryEnumField } from '@aneoconsultingfr/armonik.api.angular'; +import { TasksStatusesGroup } from '@app/dashboard/types'; +import { SessionRaw } from '@app/sessions/types'; +import { TaskOptions, TaskSummaryFilters } from '@app/tasks/types'; +import { SessionData } from '@app/types/data'; +import { GroupTasksByStatusComponent } from './group-tasks-by-status.component'; + +describe('GroupTasksByStatusComponent', () => { + const component = new GroupTasksByStatusComponent(); + + const data: SessionData[] = [ + { + raw: {} as SessionRaw, + filters: [[ + { + field: TaskSummaryEnumField.TASK_SUMMARY_ENUM_FIELD_SESSION_ID, + for: 'root', + operator: FilterStringOperator.FILTER_STRING_OPERATOR_EQUAL, + value: 'session-1' + } + ]], + queryTasksParams: { + '0-root-1-0': 'session-1' + }, + resultsQueryParams: {}, + }, + { + raw: {} as SessionRaw, + filters: [[ + { + field: TaskSummaryEnumField.TASK_SUMMARY_ENUM_FIELD_SESSION_ID, + for: 'root', + operator: FilterStringOperator.FILTER_STRING_OPERATOR_EQUAL, + value: 'session-2' + } + ]], + queryTasksParams: { + '0-root-1-0': 'session-2' + }, + resultsQueryParams: {}, + }, + ]; + + const statusesGroups: TasksStatusesGroup[] = [ + { + name: 'Validated', + statuses: [TaskStatus.TASK_STATUS_COMPLETED, TaskStatus.TASK_STATUS_PROCESSED], + color: 'green', + }, + { + name: 'Error', + statuses: [TaskStatus.TASK_STATUS_ERROR, TaskStatus.TASK_STATUS_RETRIED], + color: 'red', + } + ]; + + beforeEach(() => { + component.groupData = data; + component.statusesGroups = statusesGroups; + }); + + describe('initialisation', () => { + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should set filters', () => { + expect(component.filters).toEqual(data.reduce((acc: TaskSummaryFilters, current) => [...acc, ...current.filters], [])); + }); + + it('should set queryParams', () => { + expect(component.queryParams).toEqual({ + '0-root-1-0': 'session-1', + '1-root-1-0': 'session-2' + }); + }); + + it('should set queryParamsLength', () => { + expect(component.queryParamsLength).toEqual(2); + }); + }); +}); \ No newline at end of file From f39084f2fca0a257346991e1c955b0add1ad68d9 Mon Sep 17 00:00:00 2001 From: Faustin Date: Thu, 13 Feb 2025 11:57:36 +0100 Subject: [PATCH 25/47] tests: group component --- .../group/group-row/group.component.spec.ts | 151 ++++++++++++++++++ .../table/group/group-row/group.component.ts | 3 +- 2 files changed, 152 insertions(+), 2 deletions(-) diff --git a/src/app/components/table/group/group-row/group.component.spec.ts b/src/app/components/table/group/group-row/group.component.spec.ts index e69de29bb..cd0ceeb8e 100644 --- a/src/app/components/table/group/group-row/group.component.spec.ts +++ b/src/app/components/table/group/group-row/group.component.spec.ts @@ -0,0 +1,151 @@ +import { FilterStringOperator, SessionStatus, TaskSummaryEnumField } from '@aneoconsultingfr/armonik.api.angular'; +import { signal } from '@angular/core'; +import { TestBed } from '@angular/core/testing'; +import { SessionRaw } from '@app/sessions/types'; +import { TaskOptions } from '@app/tasks/types'; +import { TableColumn } from '@app/types/column.type'; +import { SessionData } from '@app/types/data'; +import { Group } from '@app/types/groups'; +import { IconsService } from '@services/icons.service'; +import { Subject, of } from 'rxjs'; +import { TableGroupComponent } from './group.component'; + +describe('TableGroupComponent', () => { + let component: TableGroupComponent; + + const data: SessionData[] = [ + { + raw: {} as SessionRaw, + filters: [[ + { + field: TaskSummaryEnumField.TASK_SUMMARY_ENUM_FIELD_SESSION_ID, + for: 'root', + operator: FilterStringOperator.FILTER_STRING_OPERATOR_EQUAL, + value: 'session-1' + } + ]], + queryTasksParams: { + '0-root-1-0': 'session-1' + }, + resultsQueryParams: {}, + }, + { + raw: {} as SessionRaw, + filters: [[ + { + field: TaskSummaryEnumField.TASK_SUMMARY_ENUM_FIELD_SESSION_ID, + for: 'root', + operator: FilterStringOperator.FILTER_STRING_OPERATOR_EQUAL, + value: 'session-2' + } + ]], + queryTasksParams: { + '0-root-1-0': 'session-2' + }, + resultsQueryParams: {}, + }, + ]; + + const group: Group = { + name: signal('Goup 1'), + page: 0, + opened: false, + total: 2, + refresh$: new Subject(), + emptyCondition: false, + data: of(data), + }; + + const columns: TableColumn[] = [ + { + key: 'sessionId', + name: 'Session Id', + sortable: true, + link: '/sessions', + type: 'link' + }, + { + key: 'actions', + name: 'Actions', + sortable: false, + }, + ]; + + beforeEach(() => { + component = TestBed.configureTestingModule({ + providers: [ + TableGroupComponent, + IconsService, + ] + }).inject(TableGroupComponent); + component.group = group; + component.columns = columns; + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('initialisation', () => { + it('should set the group', () => { + expect(component.group).toBe(group); + }); + + it('should set columnsKeys', () => { + expect(component.columnsKeys).toEqual(columns.map((col) => col.key)); + }); + + it('should set displayedColumns', () => { + expect(component.displayedColumns).toEqual(columns); + }); + }); + + it('should get icons', () => { + expect(component.getIcon('heart')).toEqual('favorite'); + }); + + it('should switch the view of the group', () => { + component.switchView(); + expect(component.group.opened).toBeTruthy(); + }); + + describe('pageChange', () => { + let pageSpy: jest.SpyInstance; + const newPage = 1; + + beforeEach(() => { + pageSpy = jest.spyOn(component.page, 'emit'); + component.pageChange({ + length: 100, + pageIndex: newPage, + pageSize: 100, + previousPageIndex: 0 + }); + }); + + it('should emit on page change', () => { + expect(pageSpy).toHaveBeenCalled(); + }); + + it('should update group page', () => { + expect(group.page).toEqual(newPage); + }); + }); + + describe('groupSettingsEmit', () => { + let groupSettingsSpy: jest.SpyInstance; + + beforeEach(() => { + groupSettingsSpy = jest.spyOn(component.groupSettings, 'emit'); + component.groupSettingsEmit(); + }); + + it('should emit the group name', () => { + expect(groupSettingsSpy).toHaveBeenCalledWith(group.name()); + }); + }); + + it('should track the index of data by default', () => { + expect(component.trackBy(1, data[0])).toEqual(1); + }); +}); \ No newline at end of file diff --git a/src/app/components/table/group/group-row/group.component.ts b/src/app/components/table/group/group-row/group.component.ts index 315e597c0..1db26d79f 100644 --- a/src/app/components/table/group/group-row/group.component.ts +++ b/src/app/components/table/group/group-row/group.component.ts @@ -61,14 +61,13 @@ export class TableGroupComponent[]; displayedColumns: TableColumn[]; + settingsRotate = false; @Output() page = new EventEmitter(); @Output() groupSettings = new EventEmitter(); private readonly iconsService = inject(IconsService); - settingsRotate = false; - getIcon(name: string) { return this.iconsService.getIcon(name); } From 1c8a12717a2f7dc55dc5dbbe296fcb2889bd69e6 Mon Sep 17 00:00:00 2001 From: Faustin Date: Thu, 13 Feb 2025 16:21:40 +0100 Subject: [PATCH 26/47] chore: tests and docs on manage groups dialog --- .../manage-groups-dialog.component.html | 2 +- .../manage-groups-dialog.component.spec.ts | 248 ++++++++++++++++++ .../manage-groups-dialog.component.ts | 115 +++++--- 3 files changed, 333 insertions(+), 32 deletions(-) diff --git a/src/app/components/table/group/manage-groups-dialog/manage-groups-dialog.component.html b/src/app/components/table/group/manage-groups-dialog/manage-groups-dialog.component.html index fd12c2b61..74d09a9e3 100644 --- a/src/app/components/table/group/manage-groups-dialog/manage-groups-dialog.component.html +++ b/src/app/components/table/group/manage-groups-dialog/manage-groups-dialog.component.html @@ -25,7 +25,7 @@
@for (filters of selectedGroup.conditions; track $index) { - + }
diff --git a/src/app/components/table-actions-toolbar.component.ts b/src/app/components/table-actions-toolbar.component.ts index 213cfd82d..82147868a 100644 --- a/src/app/components/table-actions-toolbar.component.ts +++ b/src/app/components/table-actions-toolbar.component.ts @@ -1,4 +1,5 @@ import { Component, EventEmitter, Input, Output, inject } from '@angular/core'; +import { MatBadgeModule } from '@angular/material/badge'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { MatMenuModule } from '@angular/material/menu'; @@ -13,6 +14,7 @@ import { AutoRefreshButtonComponent } from './auto-refresh-button.component'; import { ColumnsButtonComponent } from './columns-button.component'; import { SpinnerComponent } from './spinner.component'; + @Component({ selector: 'app-table-actions-toolbar', templateUrl: './table-actions-toolbar.component.html', @@ -31,6 +33,7 @@ import { SpinnerComponent } from './spinner.component'; MatMenuModule, MatIconModule, MatTooltipModule, + MatBadgeModule ] }) export class TableActionsToolbarComponent { @@ -43,6 +46,7 @@ export class TableActionsToolbarComponent[] = []; @Input({ required: true }) availableColumns: ColumnKey[] = []; @Input({ required: true }) lockColumns = false; + @Input({ required: false }) groupsLength: number = 0; @Output() refresh: EventEmitter = new EventEmitter(); @Output() intervalValueChange: EventEmitter = new EventEmitter(); diff --git a/src/app/components/table-dashboard-actions-toolbar.component.html b/src/app/components/table-dashboard-actions-toolbar.component.html index d60b39e01..bf53d79f7 100644 --- a/src/app/components/table-dashboard-actions-toolbar.component.html +++ b/src/app/components/table-dashboard-actions-toolbar.component.html @@ -6,6 +6,7 @@ [displayedColumns]="displayedColumns" [availableColumns]="availableColumns" [lockColumns]="lockColumns" +[groupsLength]="groupsLength" (refresh)="onRefresh()" (intervalValueChange)="onIntervalValueChange($event)" (displayedColumnsChange)="onColumnsChange($event)" diff --git a/src/app/components/table-dashboard-actions-toolbar.component.ts b/src/app/components/table-dashboard-actions-toolbar.component.ts index 67e966b1e..703d0b94d 100644 --- a/src/app/components/table-dashboard-actions-toolbar.component.ts +++ b/src/app/components/table-dashboard-actions-toolbar.component.ts @@ -26,6 +26,7 @@ export class TableDashboardActionsToolbarComponent[]; @Input({ required: true }) availableColumns: ColumnKey[]; @Input({ required: true }) lockColumns = false; + @Input({ required: false }) groupsLength: number; @Output() refresh: EventEmitter = new EventEmitter(); @Output() intervalValueChange: EventEmitter = new EventEmitter(); diff --git a/src/app/components/table-index-actions-toolbar.component.html b/src/app/components/table-index-actions-toolbar.component.html index a7ec5cd95..7e6821ee6 100644 --- a/src/app/components/table-index-actions-toolbar.component.html +++ b/src/app/components/table-index-actions-toolbar.component.html @@ -6,6 +6,7 @@ [displayedColumns]="displayedColumns" [availableColumns]="availableColumns" [lockColumns]="lockColumns" +[groupsLength]="groupsLength" (refresh)="onRefresh()" (intervalValueChange)="onIntervalValueChange($event)" (displayedColumnsChange)="onColumnsChange($event)" diff --git a/src/app/components/table-index-actions-toolbar.component.ts b/src/app/components/table-index-actions-toolbar.component.ts index 6da3e9a69..ca0ca12aa 100644 --- a/src/app/components/table-index-actions-toolbar.component.ts +++ b/src/app/components/table-index-actions-toolbar.component.ts @@ -27,6 +27,7 @@ export class TableIndexActionsToolbarComponent[]; @Input({ required: true }) availableColumns: ColumnKey[]; @Input({ required: true }) lockColumns = false; + @Input({ required: false }) groupsLength: number; @Output() refresh: EventEmitter = new EventEmitter(); @Output() intervalValueChange: EventEmitter = new EventEmitter(); diff --git a/src/app/partitions/index.component.html b/src/app/partitions/index.component.html index fc1baebeb..b6b63bde9 100644 --- a/src/app/partitions/index.component.html +++ b/src/app/partitions/index.component.html @@ -13,6 +13,7 @@ [displayedColumns]="displayedColumnsKeys" [availableColumns]="availableColumns" [lockColumns]="lockColumns" + [groupsLength]="tableDataService.groupsConditions.length" (refresh)="refresh()" (intervalValueChange)="onIntervalValueChange($event)" (displayedColumnsChange)="onColumnsChange($event)" diff --git a/src/app/results/index.component.html b/src/app/results/index.component.html index a03a0be0f..39724c9de 100644 --- a/src/app/results/index.component.html +++ b/src/app/results/index.component.html @@ -13,6 +13,7 @@ [displayedColumns]="displayedColumnsKeys" [availableColumns]="availableColumns" [lockColumns]="lockColumns" + [groupsLength]="tableDataService.groupsConditions.length" (refresh)="refresh()" (intervalValueChange)="onIntervalValueChange($event)" (displayedColumnsChange)="onColumnsChange($event)" diff --git a/src/app/sessions/index.component.html b/src/app/sessions/index.component.html index 3da2d3791..2ebc00385 100644 --- a/src/app/sessions/index.component.html +++ b/src/app/sessions/index.component.html @@ -13,6 +13,7 @@ [displayedColumns]="displayedColumnsKeys" [availableColumns]="availableColumns" [lockColumns]="lockColumns" + [groupsLength]="tableDataService.groupsConditions.length" (refresh)="refresh()" (intervalValueChange)="onIntervalValueChange($event)" (displayedColumnsChange)="onColumnsChange($event)" diff --git a/src/app/tasks/index.component.html b/src/app/tasks/index.component.html index 9f8b4e7fa..c8605f5fe 100644 --- a/src/app/tasks/index.component.html +++ b/src/app/tasks/index.component.html @@ -13,6 +13,7 @@ [displayedColumns]="displayedColumnsKeys" [availableColumns]="availableColumns" [lockColumns]="lockColumns" + [groupsLength]="tableDataService.groupsConditions.length" (refresh)="refresh()" (intervalValueChange)="onIntervalValueChange($event)" (displayedColumnsChange)="onColumnsChange($event)" From 58a1e87b717454ef73ec207569a89c247ba0f3b3 Mon Sep 17 00:00:00 2001 From: faustin Date: Thu, 19 Jun 2025 16:22:06 +0200 Subject: [PATCH 47/47] fix: groups now overflow --- src/app/components/table/group/group-row/group.component.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/app/components/table/group/group-row/group.component.css b/src/app/components/table/group/group-row/group.component.css index 1df703f2c..adc1d6779 100644 --- a/src/app/components/table/group/group-row/group.component.css +++ b/src/app/components/table/group/group-row/group.component.css @@ -55,4 +55,8 @@ mat-chip p, mat-chip mat-icon { p { margin: 0; +} + +.table-container { + overflow: auto; } \ No newline at end of file