From 6c06a3c4394c6bfc67eb76c6e3c89c232cbc6fb9 Mon Sep 17 00:00:00 2001 From: Faust1 Date: Wed, 20 Dec 2023 10:45:30 +0100 Subject: [PATCH 1/8] feat: add duration filtering --- package.json | 2 +- pnpm-lock.yaml | 8 ++++---- src/app/services/filters.service.ts | 12 +++++++++++- src/app/tasks/services/tasks-filters.service.ts | 5 +++++ src/app/types/filter-definition.ts | 8 +++++++- src/app/types/filters.ts | 10 ++++++++-- 6 files changed, 36 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 6882bdf07..e9a3e910d 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ }, "private": true, "dependencies": { - "@aneoconsultingfr/armonik.api.angular": "^3.13.1", + "@aneoconsultingfr/armonik.api.angular": "^3.14.0", "@angular-material-components/datetime-picker": "^16.0.1", "@angular/animations": "^16.2.8", "@angular/cdk": "16.2.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 42757e196..6987afca8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,8 +6,8 @@ settings: dependencies: '@aneoconsultingfr/armonik.api.angular': - specifier: ^3.13.1 - version: 3.13.1(@angular/common@16.2.8)(@angular/core@16.2.8)(@ngx-grpc/common@3.1.2)(@ngx-grpc/core@3.1.2)(@ngx-grpc/well-known-types@3.1.2)(google-protobuf@3.21.2)(rxjs@7.8.1) + specifier: ^3.14.0 + version: 3.14.0(@angular/common@16.2.8)(@angular/core@16.2.8)(@ngx-grpc/common@3.1.2)(@ngx-grpc/core@3.1.2)(@ngx-grpc/well-known-types@3.1.2)(google-protobuf@3.21.2)(rxjs@7.8.1) '@angular-material-components/datetime-picker': specifier: ^16.0.1 version: 16.0.1(@angular/cdk@16.2.7)(@angular/common@16.2.8)(@angular/core@16.2.8)(@angular/forms@16.2.8)(@angular/material@16.2.7)(@angular/platform-browser@16.2.8) @@ -176,8 +176,8 @@ packages: '@jridgewell/trace-mapping': 0.3.18 dev: true - /@aneoconsultingfr/armonik.api.angular@3.13.1(@angular/common@16.2.8)(@angular/core@16.2.8)(@ngx-grpc/common@3.1.2)(@ngx-grpc/core@3.1.2)(@ngx-grpc/well-known-types@3.1.2)(google-protobuf@3.21.2)(rxjs@7.8.1): - resolution: {integrity: sha512-kSvzZ64UrDcQcSMWmCK8WD+YsANvMyR3QocuGxTtgcwaMj8IEPjRcdTE6Gulv6qg5TooucS2BcBXRWmoWuhaEA==} + /@aneoconsultingfr/armonik.api.angular@3.14.0(@angular/common@16.2.8)(@angular/core@16.2.8)(@ngx-grpc/common@3.1.2)(@ngx-grpc/core@3.1.2)(@ngx-grpc/well-known-types@3.1.2)(google-protobuf@3.21.2)(rxjs@7.8.1): + resolution: {integrity: sha512-g11BJy2xebfnn3jjM927NNhcjJtklxEZhhbmjwzobE1D6GA/0swEq709MYNQFSzx3JQr16HjIuBpyLoEeTgu3Q==} peerDependencies: '@angular/common': ^16.2.1 '@angular/core': ^16.2.1 diff --git a/src/app/services/filters.service.ts b/src/app/services/filters.service.ts index 6643a43a1..59434d067 100644 --- a/src/app/services/filters.service.ts +++ b/src/app/services/filters.service.ts @@ -1,4 +1,4 @@ -import { FilterArrayOperator, FilterBooleanOperator, FilterDateOperator, FilterNumberOperator, FilterStatusOperator, FilterStringOperator } from '@aneoconsultingfr/armonik.api.angular'; +import { FilterArrayOperator, FilterBooleanOperator, FilterDateOperator, FilterDurationOperator, FilterNumberOperator, FilterStatusOperator, FilterStringOperator } from '@aneoconsultingfr/armonik.api.angular'; import { Injectable } from '@angular/core'; import { FilterOperators, FilterType } from '@app/types/filters'; @@ -45,6 +45,15 @@ export class FiltersService { [FilterStatusOperator.FILTER_STATUS_OPERATOR_NOT_EQUAL]: $localize`Not Equal`, }; + readonly filterDurationOperators: Record = { + [FilterDurationOperator.FILTER_DURATION_OPERATOR_EQUAL]: $localize`Equal`, + [FilterDurationOperator.FILTER_DURATION_OPERATOR_NOT_EQUAL]: $localize`Not Equal`, + [FilterDurationOperator.FILTER_DURATION_OPERATOR_SHORTER_THAN]: $localize`Shorter Than`, + [FilterDurationOperator.FILTER_DURATION_OPERATOR_SHORTER_THAN_OR_EQUAL]: $localize`Shorter or Equal`, + [FilterDurationOperator.FILTER_DURATION_OPERATOR_LONGER_THAN]: $localize`Longer Than`, + [FilterDurationOperator.FILTER_DURATION_OPERATOR_LONGER_THAN_OR_EQUAL]: $localize`Longer or Equal` + }; + readonly filterOperators: Record> = { 'string': this.filterStringOperators, 'number': this.filterNumberOperators, @@ -52,6 +61,7 @@ export class FiltersService { 'array': this.filterArrayOperators, 'status': this.filterStatusOperators, 'boolean': this.filterBooleanOperators, + 'duration': this.filterDurationOperators, }; findOperators(type: FilterType) { diff --git a/src/app/tasks/services/tasks-filters.service.ts b/src/app/tasks/services/tasks-filters.service.ts index ac12a3f35..e2149f171 100644 --- a/src/app/tasks/services/tasks-filters.service.ts +++ b/src/app/tasks/services/tasks-filters.service.ts @@ -145,6 +145,11 @@ export class TasksFiltersService { field: TaskOptionEnumField.TASK_OPTION_ENUM_FIELD_MAX_RETRIES, type: 'number' }, + { + for: 'options', + field: TaskOptionEnumField.TASK_OPTION_ENUM_FIELD_MAX_DURATION, + type: 'duration' + } ]; readonly #defaultFilters: TaskSummaryFiltersOr = this.#defaultConfigService.defaultTasks.filters; diff --git a/src/app/types/filter-definition.ts b/src/app/types/filter-definition.ts index 80ac90928..2cdd56d9f 100644 --- a/src/app/types/filter-definition.ts +++ b/src/app/types/filter-definition.ts @@ -73,10 +73,16 @@ type FilterDefinitionTaskOptionNumber = { type: 'number'; }; +type FilterDefinitionTaskOptionDuration = { + for: 'options'; + field: T; + type: 'duration' +}; + type FilterDefinitionRoot = FilterDefinitionRootString | FilterDefinitionRootNumber | FilterDefinitionRootArray | FilterDefinitionRootStatus | FilterDefinitionRootDate; -export type FilterDefinitionTaskOption = FilterDefinitionTaskOptionString | FilterDefinitionTaskOptionNumber; +export type FilterDefinitionTaskOption = FilterDefinitionTaskOptionString | FilterDefinitionTaskOptionNumber | FilterDefinitionTaskOptionDuration; export type FilterDefinition = FilterDefinitionRoot | FilterDefinitionTaskOption; diff --git a/src/app/types/filters.ts b/src/app/types/filters.ts index d49a9fdff..7a2026ce5 100644 --- a/src/app/types/filters.ts +++ b/src/app/types/filters.ts @@ -3,7 +3,7 @@ import { FilterFor } from './filter-definition'; export type MaybeNull = T | null; -export type FilterType = 'string' | 'number' | 'date' | 'array' | 'status' | 'boolean'; +export type FilterType = 'string' | 'number' | 'date' | 'array' | 'status' | 'boolean' | 'duration'; export type FilterValueOptions = { key: string | number, value: string }[]; export type FilterOperators = FilterStringOperator | FilterNumberOperator | FilterDateOperator | FilterArrayOperator | FilterStatusOperator | FilterBooleanOperator; @@ -61,6 +61,7 @@ export type Filter = { export type FilterInputValueString = MaybeNull; export type FilterInputValueNumber = MaybeNull; export type FilterInputValueDate = MaybeNull; +export type FilterInputValueDuration = MaybeNull; // Input for a filter input. export interface FilterInputString { @@ -80,7 +81,12 @@ export interface FilterInputStatus { value: MaybeNull; statuses: FilterValueOptions; } -export type FilterInput = FilterInputString | FilterInputNumber | FilterInputDate | FilterInputStatus; + +export interface FilterInputDuration { + type: 'duration'; + value: FilterInputValueDuration +} +export type FilterInput = FilterInputString | FilterInputNumber | FilterInputDate | FilterInputStatus | FilterInputDuration; export type FilterInputValue = FilterInput['value']; export type FilterInputType = FilterInput['type']; From 6026bd2f4fa2d4a90b4538d870fb8139174328e6 Mon Sep 17 00:00:00 2001 From: Faust1 Date: Wed, 20 Dec 2023 15:44:47 +0100 Subject: [PATCH 2/8] working duration filter --- package.json | 2 +- pnpm-lock.yaml | 8 ++--- .../filters/filters-chips.component.ts | 20 ++++++++++++ .../filters-dialog-filter-field.component.ts | 9 +++++- .../filters/filters-dialog-input.component.ts | 31 +++++++++++++++++++ .../services/results-filters.service.ts | 1 + .../results/services/results-grpc.service.ts | 1 + .../results/services/results-index.service.ts | 1 + src/app/tasks/services/tasks-grpc.service.ts | 13 +++++++- src/app/types/filters.ts | 8 ++++- 10 files changed, 86 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index e9a3e910d..45eb965ad 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ }, "private": true, "dependencies": { - "@aneoconsultingfr/armonik.api.angular": "^3.14.0", + "@aneoconsultingfr/armonik.api.angular": "3.15.0-edge.32.e899200", "@angular-material-components/datetime-picker": "^16.0.1", "@angular/animations": "^16.2.8", "@angular/cdk": "16.2.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6987afca8..291651eb2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,8 +6,8 @@ settings: dependencies: '@aneoconsultingfr/armonik.api.angular': - specifier: ^3.14.0 - version: 3.14.0(@angular/common@16.2.8)(@angular/core@16.2.8)(@ngx-grpc/common@3.1.2)(@ngx-grpc/core@3.1.2)(@ngx-grpc/well-known-types@3.1.2)(google-protobuf@3.21.2)(rxjs@7.8.1) + specifier: 3.15.0-edge.32.e899200 + version: 3.15.0-edge.32.e899200(@angular/common@16.2.8)(@angular/core@16.2.8)(@ngx-grpc/common@3.1.2)(@ngx-grpc/core@3.1.2)(@ngx-grpc/well-known-types@3.1.2)(google-protobuf@3.21.2)(rxjs@7.8.1) '@angular-material-components/datetime-picker': specifier: ^16.0.1 version: 16.0.1(@angular/cdk@16.2.7)(@angular/common@16.2.8)(@angular/core@16.2.8)(@angular/forms@16.2.8)(@angular/material@16.2.7)(@angular/platform-browser@16.2.8) @@ -176,8 +176,8 @@ packages: '@jridgewell/trace-mapping': 0.3.18 dev: true - /@aneoconsultingfr/armonik.api.angular@3.14.0(@angular/common@16.2.8)(@angular/core@16.2.8)(@ngx-grpc/common@3.1.2)(@ngx-grpc/core@3.1.2)(@ngx-grpc/well-known-types@3.1.2)(google-protobuf@3.21.2)(rxjs@7.8.1): - resolution: {integrity: sha512-g11BJy2xebfnn3jjM927NNhcjJtklxEZhhbmjwzobE1D6GA/0swEq709MYNQFSzx3JQr16HjIuBpyLoEeTgu3Q==} + /@aneoconsultingfr/armonik.api.angular@3.15.0-edge.32.e899200(@angular/common@16.2.8)(@angular/core@16.2.8)(@ngx-grpc/common@3.1.2)(@ngx-grpc/core@3.1.2)(@ngx-grpc/well-known-types@3.1.2)(google-protobuf@3.21.2)(rxjs@7.8.1): + resolution: {integrity: sha512-8Qm7yLDafUwZ5Ko3Gs5THoQoMz1XvUY1XzFeDk27EmitModdaY9yLXq2nk3pDTQ6g49oYnXfA7TVIYtj+aUplQ==} peerDependencies: '@angular/common': ^16.2.1 '@angular/core': ^16.2.1 diff --git a/src/app/components/filters/filters-chips.component.ts b/src/app/components/filters/filters-chips.component.ts index 12e02c2e6..00bc663d3 100644 --- a/src/app/components/filters/filters-chips.component.ts +++ b/src/app/components/filters/filters-chips.component.ts @@ -73,10 +73,30 @@ export class FiltersChipsComponent 0) { + resultString += `${hours}h `; + } + if (minutes > 0) { + resultString += `${minutes}m `; + } + if (seconds > 0) { + resultString += `${seconds}s`; + } + return resultString; + } + trackByFilter(_: number, filter: Filter): string { return filter.field?.toString() ?? ''; } diff --git a/src/app/components/filters/filters-dialog-filter-field.component.ts b/src/app/components/filters/filters-dialog-filter-field.component.ts index 580b7b486..856950141 100644 --- a/src/app/components/filters/filters-dialog-filter-field.component.ts +++ b/src/app/components/filters/filters-dialog-filter-field.component.ts @@ -5,7 +5,7 @@ import { MatFormFieldModule } from '@angular/material/form-field'; import { MatSelectModule } from '@angular/material/select'; import { DATA_FILTERS_SERVICE } from '@app/tokens/filters.token'; import { FilterDefinition, FilterFor } from '@app/types/filter-definition'; -import { Filter, FilterInput, FilterInputOutput, FilterInputType, FilterInputValueString, FilterValueOptions } from '@app/types/filters'; +import { Filter, FilterInput, FilterInputOutput, FilterInputType, FilterInputValueDuration, FilterInputValueString, FilterValueOptions } from '@app/types/filters'; import { FiltersService } from '@services/filters.service'; import { FiltersDialogInputComponent } from './filters-dialog-input.component'; @@ -97,6 +97,8 @@ export class FiltersDialogFilterFieldComponent{{ option.value }} + + + : + : + + `, styles: [` mat-form-field { @@ -67,6 +73,7 @@ export class FiltersDialogInputComponent { // Créer des types en fonction du type de champ @Output() valueChange: EventEmitter = new EventEmitter(); actualDate = new Date(); + duration: {[key: number]: string} = {}; onStringChange(event: Event): void { this.valueChange.emit({ @@ -98,6 +105,17 @@ export class FiltersDialogInputComponent { }); } + onDurationChange(event: Event, index: number) { + this.duration[index] = (event.target as HTMLInputElement).value; + const durationSeconds = Number(this.duration[0] ?? this.getDurationInputValue(0) ?? 0) * 3600 + + Number(this.duration[1] ?? this.getDurationInputValue(1) ?? 0) * 60 + + Number(this.duration[2] ?? this.getDurationInputValue(2) ?? 0); + this.valueChange.emit({ + type: 'duration', + value: durationSeconds + }); + } + getInputType(): FilterInputType { switch (this.input.type) { case 'string': @@ -115,6 +133,19 @@ export class FiltersDialogInputComponent { } } + getDurationInputValue(index: number): number | undefined { + switch (index) { + case 0: + return Math.floor(Number(this.input.value)/3600) ?? undefined; + case 1: + return Math.floor((Number(this.input.value)%3600)/60) ?? undefined; + case 2: + return Math.floor(((Number(this.input.value))%3600)%60) ?? undefined; + default: + return undefined; + } + } + trackBySelect(_: number, item: { value: string }): string { return item.value; } diff --git a/src/app/results/services/results-filters.service.ts b/src/app/results/services/results-filters.service.ts index d7fb816d0..488f42538 100644 --- a/src/app/results/services/results-filters.service.ts +++ b/src/app/results/services/results-filters.service.ts @@ -23,6 +23,7 @@ export class ResultsFiltersService { [ResultRawEnumField.RESULT_RAW_ENUM_FIELD_SESSION_ID]: $localize`Session ID`, [ResultRawEnumField.RESULT_RAW_ENUM_FIELD_STATUS]: $localize`Status`, [ResultRawEnumField.RESULT_RAW_ENUM_FIELD_UNSPECIFIED]: $localize`Unspecified`, + [ResultRawEnumField.RESULT_RAW_ENUM_FIELD_SIZE]: $localize`Size`, }; readonly #filtersDefinitions: ResultsFiltersDefinition[] = [ diff --git a/src/app/results/services/results-grpc.service.ts b/src/app/results/services/results-grpc.service.ts index d49b2fb26..789a6d638 100644 --- a/src/app/results/services/results-grpc.service.ts +++ b/src/app/results/services/results-grpc.service.ts @@ -27,6 +27,7 @@ export class ResultsGrpcService { 'ownerTaskId': ResultRawEnumField.RESULT_RAW_ENUM_FIELD_OWNER_TASK_ID, 'resultId': ResultRawEnumField.RESULT_RAW_ENUM_FIELD_RESULT_ID, 'completedAt': ResultRawEnumField.RESULT_RAW_ENUM_FIELD_COMPLETED_AT, + 'size': ResultRawEnumField.RESULT_RAW_ENUM_FIELD_SIZE, }; diff --git a/src/app/results/services/results-index.service.ts b/src/app/results/services/results-index.service.ts index c50602504..0e8a51083 100644 --- a/src/app/results/services/results-index.service.ts +++ b/src/app/results/services/results-index.service.ts @@ -24,6 +24,7 @@ export class ResultsIndexService { actions: $localize`Actions`, completedAt: $localize`Completed at`, resultId: $localize`Result ID`, + size: $localize`Size` }; readonly defaultOptions: ResultRawListOptions = this.#defaultConfigService.defaultResults.options; diff --git a/src/app/tasks/services/tasks-grpc.service.ts b/src/app/tasks/services/tasks-grpc.service.ts index 49cdc0b31..adab6490b 100644 --- a/src/app/tasks/services/tasks-grpc.service.ts +++ b/src/app/tasks/services/tasks-grpc.service.ts @@ -1,4 +1,4 @@ -import { SortDirection as ArmoniKSortDirection, CancelTasksRequest, CancelTasksResponse, CountTasksByStatusRequest, CountTasksByStatusResponse, FilterDateOperator, FilterNumberOperator, FilterStringOperator, GetTaskRequest, GetTaskResponse, ListTasksRequest, ListTasksResponse, TaskFilterField, TaskOptionEnumField, TaskSummaryEnumField, TasksClient } from '@aneoconsultingfr/armonik.api.angular'; +import { SortDirection as ArmoniKSortDirection, CancelTasksRequest, CancelTasksResponse, CountTasksByStatusRequest, CountTasksByStatusResponse, FilterDateOperator, FilterDurationOperator, FilterNumberOperator, FilterStringOperator, GetTaskRequest, GetTaskResponse, ListTasksRequest, ListTasksResponse, TaskFilterField, TaskOptionEnumField, TaskSummaryEnumField, TasksClient } from '@aneoconsultingfr/armonik.api.angular'; import { Injectable, inject } from '@angular/core'; import { SortDirection } from '@angular/material/sort'; import { Observable } from 'rxjs'; @@ -146,6 +146,17 @@ export class TasksGrpcService { operator: filter.operator ?? FilterDateOperator.FILTER_DATE_OPERATOR_EQUAL } } satisfies TaskFilterField.AsObject; + case 'duration': + return { + field: filterField, + filterDuration: { + value: { + nanos: 0, + seconds: filter.value?.toString() ?? '' + }, + operator: filter.operator ?? FilterDurationOperator.FILTER_DURATION_OPERATOR_EQUAL + } + } satisfies TaskFilterField.AsObject; default: throw new Error(`Type ${type} not supported`); } diff --git a/src/app/types/filters.ts b/src/app/types/filters.ts index 7a2026ce5..c03837786 100644 --- a/src/app/types/filters.ts +++ b/src/app/types/filters.ts @@ -104,4 +104,10 @@ export interface FilterInputOutputDate { type: 'date'; value: MaybeNull; } -export type FilterInputOutput = FilterInputOutputString | FilterInputOutputNumber | FilterInputOutputDate; + +export interface FilterInputOutputDuration { + type: 'duration'; + value: MaybeNull +} + +export type FilterInputOutput = FilterInputOutputString | FilterInputOutputNumber | FilterInputOutputDate | FilterInputOutputDuration; From 2a7c7d60d29c3ef9961034c2cd8d11d346dae8ef Mon Sep 17 00:00:00 2001 From: Faust1 Date: Wed, 20 Dec 2023 16:57:41 +0100 Subject: [PATCH 3/8] added tests and modifications --- .../filters/filters-chips.component.spec.ts | 17 +++- ...ters-dialog-filter-field.component.spec.ts | 86 ++++++++++++++++++- .../filters-dialog-input.component.spec.ts | 30 +++++++ .../filters/filters-dialog-input.component.ts | 32 ++++--- .../services/tasks-filters.service.spec.ts | 5 ++ 5 files changed, 154 insertions(+), 16 deletions(-) diff --git a/src/app/components/filters/filters-chips.component.spec.ts b/src/app/components/filters/filters-chips.component.spec.ts index 8584c6345..999d15a9b 100644 --- a/src/app/components/filters/filters-chips.component.spec.ts +++ b/src/app/components/filters/filters-chips.component.spec.ts @@ -49,7 +49,12 @@ describe('FiltersChipsComponent', () => { field: 5, type: 'unknownType', for: 'root' - } as unknown as FilterDefinition) + } as unknown as FilterDefinition), + { + field: 1, + type: 'duration', + for: 'options' + } ]; beforeEach(async () => { @@ -150,4 +155,14 @@ describe('FiltersChipsComponent', () => { expect(component.trackByFilter(0, filter)).toEqual(''); }); + + it('should show a duration appropriately', () => { + const filter: Filter = { + field: 1, + for: 'options', + operator: 1, + value: 94350 + }; + expect(component.content(filter)).toEqual('other Not Equal 26h 12m 30s'); + }); }); \ No newline at end of file diff --git a/src/app/components/filters/filters-dialog-filter-field.component.spec.ts b/src/app/components/filters/filters-dialog-filter-field.component.spec.ts index 940123629..1ba94f2c6 100644 --- a/src/app/components/filters/filters-dialog-filter-field.component.spec.ts +++ b/src/app/components/filters/filters-dialog-filter-field.component.spec.ts @@ -45,7 +45,17 @@ describe('FiltersDialogFilterFieldComponent', () => { field: 5, type: 'unknownType', for: 'root' - } as unknown as FilterDefinition) + } as unknown as FilterDefinition), + { + field: 6, + type: 'duration', + for: 'options' + }, + { + field: 7, + type: 'date', + for: 'root' + } ]; beforeEach(async () => { @@ -132,6 +142,24 @@ describe('FiltersDialogFilterFieldComponent', () => { component.onInputChange(inputEvent); expect(component.filter.value).toBeNull(); }); + + it('should change the filter value to date if one is passed', () => { + const inputEvent = { + type: 'date', + value: 95603 + } as unknown as FilterInputOutput; + component.onInputChange(inputEvent); + expect(component.filter.value).toEqual(95603); + }); + + it('should change the filter value to a duration if one is passed', () => { + const inputEvent = { + type: 'duration', + value: 94350 + } as unknown as FilterInputOutput; + component.onInputChange(inputEvent); + expect(component.filter.value).toEqual(94350); + }); }); describe('findInput', () => { @@ -274,6 +302,62 @@ describe('FiltersDialogFilterFieldComponent', () => { 'Unknown type unknownType' ); }); + + describe('input filter of type date', () => { + it('should return a date', () => { + const dateFilter: Filter = { + for: 'root', + field: 7, + operator: 1, + value: 1703085190 + }; + expect(component.findInput(dateFilter)).toEqual({ + type: 'date', + value: new Date(1703085190000) + }); + }); + + it('should return null if there is no date', () => { + const dateFilter: Filter = { + for: 'root', + field: 7, + operator: 1, + value: null + }; + expect(component.findInput(dateFilter)).toEqual({ + type: 'date', + value: null + }); + }); + }); + + describe('input filter of type duration', () => { + it('should return a filterinput with a duration in seconds', () => { + const durationFilter: Filter = { + field: 6, + for: 'options', + operator: 1, + value: 94350 + }; + expect(component.findInput(durationFilter)).toEqual({ + type: 'duration', + value: 94350 + }); + }); + }); + + it('should return a null filterInput if the duration is not existing', () => { + const durationFilter: Filter = { + field: 6, + for: 'options', + operator: 1, + value: null + }; + expect(component.findInput(durationFilter)).toEqual({ + type: 'duration', + value: null + }); + }); }); describe('findType', () => { diff --git a/src/app/components/filters/filters-dialog-input.component.spec.ts b/src/app/components/filters/filters-dialog-input.component.spec.ts index 9a538169c..a3acd90f3 100644 --- a/src/app/components/filters/filters-dialog-input.component.spec.ts +++ b/src/app/components/filters/filters-dialog-input.component.spec.ts @@ -94,4 +94,34 @@ describe('FiltersDialogInputComponent', () => { }; expect(component.trackBySelect(0, item)).toBe(item.value); }); + + it('should emit a duration in second', () => { + const inputEvent = { + target: { + value: '35' + } + } as unknown as Event; + component.onDurationChange(inputEvent, 0); + expect(valueChangeSpy).toHaveBeenCalledWith({type: 'duration', value: 126000}); + (inputEvent.target as HTMLInputElement).value = '39'; + component.onDurationChange(inputEvent, 1); + expect(valueChangeSpy).toHaveBeenLastCalledWith({type: 'duration', value: 128340}); + component.onDurationChange(inputEvent, 2); + expect(valueChangeSpy).toHaveBeenLastCalledWith({type: 'duration', value: 128379}); + }); + + describe('getDurationInputValue', () => { + beforeEach(() => { + component.input.value = 94350; + }); + it('should get the hours from a duration in seconds', () => { + expect(component.getDurationInputValue('hours')).toEqual(26); + }); + it('should get the minutes from a duration in seconds', () => { + expect(component.getDurationInputValue('minutes')).toEqual(12); + }); + it('should get the seconds from a duration in seconds', () => { + expect(component.getDurationInputValue('seconds')).toEqual(30); + }); + }); }); \ No newline at end of file diff --git a/src/app/components/filters/filters-dialog-input.component.ts b/src/app/components/filters/filters-dialog-input.component.ts index 6b3958ff2..a28a3f32d 100644 --- a/src/app/components/filters/filters-dialog-input.component.ts +++ b/src/app/components/filters/filters-dialog-input.component.ts @@ -44,9 +44,9 @@ import { FilterInput, FilterInputOutput, FilterInputType } from '@app/types/filt - : - : - + : + : + `, styles: [` @@ -107,9 +107,13 @@ export class FiltersDialogInputComponent { onDurationChange(event: Event, index: number) { this.duration[index] = (event.target as HTMLInputElement).value; - const durationSeconds = Number(this.duration[0] ?? this.getDurationInputValue(0) ?? 0) * 3600 - + Number(this.duration[1] ?? this.getDurationInputValue(1) ?? 0) * 60 - + Number(this.duration[2] ?? this.getDurationInputValue(2) ?? 0); + const getHours = !isNaN(Number(this.duration[0])) ? Number(this.duration[0]) : this.getDurationInputValue('hours'); + const getMinutes = !isNaN(Number(this.duration[1])) ? Number(this.duration[1]) : this.getDurationInputValue('minutes'); + const getSeconds = !isNaN(Number(this.duration[2])) ? Number(this.duration[2]) : this.getDurationInputValue('seconds'); + + const durationSeconds = (getHours ?? 0) * 3600 + + (getMinutes ?? 0) * 60 + + (getSeconds ?? 0); this.valueChange.emit({ type: 'duration', value: durationSeconds @@ -133,14 +137,14 @@ export class FiltersDialogInputComponent { } } - getDurationInputValue(index: number): number | undefined { - switch (index) { - case 0: - return Math.floor(Number(this.input.value)/3600) ?? undefined; - case 1: - return Math.floor((Number(this.input.value)%3600)/60) ?? undefined; - case 2: - return Math.floor(((Number(this.input.value))%3600)%60) ?? undefined; + getDurationInputValue(searchItem: string): number | undefined { + switch (searchItem) { + case 'hours': + return !isNaN(Number(this.input.value)) ? Math.floor(Number(this.input.value)/3600) : undefined; + case 'minutes': + return !isNaN(Number(this.input.value)) ? Math.floor((Number(this.input.value)%3600)/60) : undefined; + case 'seconds': + return !isNaN(Number(this.input.value)) ? Math.floor(((Number(this.input.value))%3600)%60) : undefined; default: return undefined; } diff --git a/src/app/tasks/services/tasks-filters.service.spec.ts b/src/app/tasks/services/tasks-filters.service.spec.ts index 335e7ac0b..d7fca8b6d 100644 --- a/src/app/tasks/services/tasks-filters.service.spec.ts +++ b/src/app/tasks/services/tasks-filters.service.spec.ts @@ -128,6 +128,11 @@ describe('TasksFilterService', () => { field: TaskOptionEnumField.TASK_OPTION_ENUM_FIELD_MAX_RETRIES, type: 'number' }, + { + for: 'options', + field: TaskOptionEnumField.TASK_OPTION_ENUM_FIELD_MAX_DURATION, + type: 'duration' + } ]; const filterAnd: FiltersAnd = [{ From e3dc638634d79343a00a404896314b02ca16a145 Mon Sep 17 00:00:00 2001 From: Faust1 Date: Thu, 21 Dec 2023 09:10:01 +0100 Subject: [PATCH 4/8] new tests for filter.service --- src/app/services/filter.service.spec.ts | 11 ++++++++++- src/app/types/filters.ts | 4 ++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/app/services/filter.service.spec.ts b/src/app/services/filter.service.spec.ts index 74fabbbaa..4d7b62cc1 100644 --- a/src/app/services/filter.service.spec.ts +++ b/src/app/services/filter.service.spec.ts @@ -1,4 +1,4 @@ -import { FilterArrayOperator, FilterBooleanOperator, FilterDateOperator, FilterNumberOperator, FilterStatusOperator, FilterStringOperator } from '@aneoconsultingfr/armonik.api.angular'; +import { FilterArrayOperator, FilterBooleanOperator, FilterDateOperator, FilterDurationOperator, FilterNumberOperator, FilterStatusOperator, FilterStringOperator } from '@aneoconsultingfr/armonik.api.angular'; import { FiltersService } from './filters.service'; describe('FiltersService', () => { @@ -28,6 +28,10 @@ describe('FiltersService', () => { it('should return the filterBooleanOperators when "boolean" is provided', () => { expect(service.findOperators('boolean')).toBe(service.filterBooleanOperators); }); + + it('should return the filterDurationOperators when "duration" is provided', () => { + expect(service.findOperators('duration')).toBe(service.filterDurationOperators); + }); }); describe('createQueryParamsKey', () => { @@ -70,5 +74,10 @@ describe('FiltersService', () => { expect(service.createQueryParamsKey(1, 'my_string', FilterBooleanOperator.FILTER_BOOLEAN_OPERATOR_IS, 1)) .toEqual('1-my_string-1-0'); }); + + it('should return the correct string if a duration filter operator is provied', () => { + expect(service.createQueryParamsKey(1, 'my_string', FilterDurationOperator.FILTER_DURATION_OPERATOR_EQUAL, 7)) + .toEqual('1-my_string_7-0'); + }); }); }); \ No newline at end of file diff --git a/src/app/types/filters.ts b/src/app/types/filters.ts index c03837786..32652daee 100644 --- a/src/app/types/filters.ts +++ b/src/app/types/filters.ts @@ -1,4 +1,4 @@ -import { FilterArrayOperator, FilterBooleanOperator, FilterDateOperator, FilterNumberOperator, FilterStatusOperator, FilterStringOperator } from '@aneoconsultingfr/armonik.api.angular'; +import { FilterArrayOperator, FilterBooleanOperator, FilterDateOperator, FilterDurationOperator, FilterNumberOperator, FilterStatusOperator, FilterStringOperator } from '@aneoconsultingfr/armonik.api.angular'; import { FilterFor } from './filter-definition'; export type MaybeNull = T | null; @@ -6,7 +6,7 @@ export type MaybeNull = T | null; export type FilterType = 'string' | 'number' | 'date' | 'array' | 'status' | 'boolean' | 'duration'; export type FilterValueOptions = { key: string | number, value: string }[]; -export type FilterOperators = FilterStringOperator | FilterNumberOperator | FilterDateOperator | FilterArrayOperator | FilterStatusOperator | FilterBooleanOperator; +export type FilterOperators = FilterStringOperator | FilterNumberOperator | FilterDateOperator | FilterArrayOperator | FilterStatusOperator | FilterBooleanOperator | FilterDurationOperator; /** * Used to define the filter FOR (a group of AND). From 05abf27768afa7883e2521fea1f7e1600f759e7f Mon Sep 17 00:00:00 2001 From: Faust1 Date: Thu, 21 Dec 2023 09:12:12 +0100 Subject: [PATCH 5/8] Removed unrelated files modifications --- src/app/results/services/results-filters.service.ts | 1 - src/app/results/services/results-grpc.service.ts | 1 - src/app/results/services/results-index.service.ts | 1 - .../tasks/services/tasks-filters.service.spec.ts | 5 ----- src/app/tasks/services/tasks-filters.service.ts | 5 ----- src/app/tasks/services/tasks-grpc.service.ts | 13 +------------ 6 files changed, 1 insertion(+), 25 deletions(-) diff --git a/src/app/results/services/results-filters.service.ts b/src/app/results/services/results-filters.service.ts index 488f42538..d7fb816d0 100644 --- a/src/app/results/services/results-filters.service.ts +++ b/src/app/results/services/results-filters.service.ts @@ -23,7 +23,6 @@ export class ResultsFiltersService { [ResultRawEnumField.RESULT_RAW_ENUM_FIELD_SESSION_ID]: $localize`Session ID`, [ResultRawEnumField.RESULT_RAW_ENUM_FIELD_STATUS]: $localize`Status`, [ResultRawEnumField.RESULT_RAW_ENUM_FIELD_UNSPECIFIED]: $localize`Unspecified`, - [ResultRawEnumField.RESULT_RAW_ENUM_FIELD_SIZE]: $localize`Size`, }; readonly #filtersDefinitions: ResultsFiltersDefinition[] = [ diff --git a/src/app/results/services/results-grpc.service.ts b/src/app/results/services/results-grpc.service.ts index 789a6d638..d49b2fb26 100644 --- a/src/app/results/services/results-grpc.service.ts +++ b/src/app/results/services/results-grpc.service.ts @@ -27,7 +27,6 @@ export class ResultsGrpcService { 'ownerTaskId': ResultRawEnumField.RESULT_RAW_ENUM_FIELD_OWNER_TASK_ID, 'resultId': ResultRawEnumField.RESULT_RAW_ENUM_FIELD_RESULT_ID, 'completedAt': ResultRawEnumField.RESULT_RAW_ENUM_FIELD_COMPLETED_AT, - 'size': ResultRawEnumField.RESULT_RAW_ENUM_FIELD_SIZE, }; diff --git a/src/app/results/services/results-index.service.ts b/src/app/results/services/results-index.service.ts index 0e8a51083..c50602504 100644 --- a/src/app/results/services/results-index.service.ts +++ b/src/app/results/services/results-index.service.ts @@ -24,7 +24,6 @@ export class ResultsIndexService { actions: $localize`Actions`, completedAt: $localize`Completed at`, resultId: $localize`Result ID`, - size: $localize`Size` }; readonly defaultOptions: ResultRawListOptions = this.#defaultConfigService.defaultResults.options; diff --git a/src/app/tasks/services/tasks-filters.service.spec.ts b/src/app/tasks/services/tasks-filters.service.spec.ts index d7fca8b6d..335e7ac0b 100644 --- a/src/app/tasks/services/tasks-filters.service.spec.ts +++ b/src/app/tasks/services/tasks-filters.service.spec.ts @@ -128,11 +128,6 @@ describe('TasksFilterService', () => { field: TaskOptionEnumField.TASK_OPTION_ENUM_FIELD_MAX_RETRIES, type: 'number' }, - { - for: 'options', - field: TaskOptionEnumField.TASK_OPTION_ENUM_FIELD_MAX_DURATION, - type: 'duration' - } ]; const filterAnd: FiltersAnd = [{ diff --git a/src/app/tasks/services/tasks-filters.service.ts b/src/app/tasks/services/tasks-filters.service.ts index e2149f171..a90d0111c 100644 --- a/src/app/tasks/services/tasks-filters.service.ts +++ b/src/app/tasks/services/tasks-filters.service.ts @@ -144,11 +144,6 @@ export class TasksFiltersService { for: 'options', field: TaskOptionEnumField.TASK_OPTION_ENUM_FIELD_MAX_RETRIES, type: 'number' - }, - { - for: 'options', - field: TaskOptionEnumField.TASK_OPTION_ENUM_FIELD_MAX_DURATION, - type: 'duration' } ]; diff --git a/src/app/tasks/services/tasks-grpc.service.ts b/src/app/tasks/services/tasks-grpc.service.ts index adab6490b..49cdc0b31 100644 --- a/src/app/tasks/services/tasks-grpc.service.ts +++ b/src/app/tasks/services/tasks-grpc.service.ts @@ -1,4 +1,4 @@ -import { SortDirection as ArmoniKSortDirection, CancelTasksRequest, CancelTasksResponse, CountTasksByStatusRequest, CountTasksByStatusResponse, FilterDateOperator, FilterDurationOperator, FilterNumberOperator, FilterStringOperator, GetTaskRequest, GetTaskResponse, ListTasksRequest, ListTasksResponse, TaskFilterField, TaskOptionEnumField, TaskSummaryEnumField, TasksClient } from '@aneoconsultingfr/armonik.api.angular'; +import { SortDirection as ArmoniKSortDirection, CancelTasksRequest, CancelTasksResponse, CountTasksByStatusRequest, CountTasksByStatusResponse, FilterDateOperator, FilterNumberOperator, FilterStringOperator, GetTaskRequest, GetTaskResponse, ListTasksRequest, ListTasksResponse, TaskFilterField, TaskOptionEnumField, TaskSummaryEnumField, TasksClient } from '@aneoconsultingfr/armonik.api.angular'; import { Injectable, inject } from '@angular/core'; import { SortDirection } from '@angular/material/sort'; import { Observable } from 'rxjs'; @@ -146,17 +146,6 @@ export class TasksGrpcService { operator: filter.operator ?? FilterDateOperator.FILTER_DATE_OPERATOR_EQUAL } } satisfies TaskFilterField.AsObject; - case 'duration': - return { - field: filterField, - filterDuration: { - value: { - nanos: 0, - seconds: filter.value?.toString() ?? '' - }, - operator: filter.operator ?? FilterDurationOperator.FILTER_DURATION_OPERATOR_EQUAL - } - } satisfies TaskFilterField.AsObject; default: throw new Error(`Type ${type} not supported`); } From 92eba5db60fabba667beed52ae9f2eae8a9eb95a Mon Sep 17 00:00:00 2001 From: Faust1 Date: Thu, 4 Jan 2024 11:22:08 +0100 Subject: [PATCH 6/8] remove pnpm lock modifications --- pnpm-lock.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 42757e196..63239490e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,8 +6,8 @@ settings: dependencies: '@aneoconsultingfr/armonik.api.angular': - specifier: ^3.13.1 - version: 3.13.1(@angular/common@16.2.8)(@angular/core@16.2.8)(@ngx-grpc/common@3.1.2)(@ngx-grpc/core@3.1.2)(@ngx-grpc/well-known-types@3.1.2)(google-protobuf@3.21.2)(rxjs@7.8.1) + specifier: ^3.15.1 + version: 3.15.1(@angular/common@16.2.8)(@angular/core@16.2.8)(@ngx-grpc/common@3.1.2)(@ngx-grpc/core@3.1.2)(@ngx-grpc/well-known-types@3.1.2)(google-protobuf@3.21.2)(rxjs@7.8.1) '@angular-material-components/datetime-picker': specifier: ^16.0.1 version: 16.0.1(@angular/cdk@16.2.7)(@angular/common@16.2.8)(@angular/core@16.2.8)(@angular/forms@16.2.8)(@angular/material@16.2.7)(@angular/platform-browser@16.2.8) @@ -176,8 +176,8 @@ packages: '@jridgewell/trace-mapping': 0.3.18 dev: true - /@aneoconsultingfr/armonik.api.angular@3.13.1(@angular/common@16.2.8)(@angular/core@16.2.8)(@ngx-grpc/common@3.1.2)(@ngx-grpc/core@3.1.2)(@ngx-grpc/well-known-types@3.1.2)(google-protobuf@3.21.2)(rxjs@7.8.1): - resolution: {integrity: sha512-kSvzZ64UrDcQcSMWmCK8WD+YsANvMyR3QocuGxTtgcwaMj8IEPjRcdTE6Gulv6qg5TooucS2BcBXRWmoWuhaEA==} + /@aneoconsultingfr/armonik.api.angular@3.15.1(@angular/common@16.2.8)(@angular/core@16.2.8)(@ngx-grpc/common@3.1.2)(@ngx-grpc/core@3.1.2)(@ngx-grpc/well-known-types@3.1.2)(google-protobuf@3.21.2)(rxjs@7.8.1): + resolution: {integrity: sha512-UnSDwxX8YLfiSxUmtDgpP1gAjkdk67n9F30dBZLkZhVqKp7jOpSLIuUPrTrXgE1PP4yD6A24hlwbBHKn9iqBQQ==} peerDependencies: '@angular/common': ^16.2.1 '@angular/core': ^16.2.1 From b112af835635633ff662c8ba3d21a64300ee8694 Mon Sep 17 00:00:00 2001 From: Faust1 Date: Thu, 4 Jan 2024 11:25:47 +0100 Subject: [PATCH 7/8] updated tests --- src/app/services/filter.service.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/services/filter.service.spec.ts b/src/app/services/filter.service.spec.ts index 4d7b62cc1..fa6c18798 100644 --- a/src/app/services/filter.service.spec.ts +++ b/src/app/services/filter.service.spec.ts @@ -77,7 +77,7 @@ describe('FiltersService', () => { it('should return the correct string if a duration filter operator is provied', () => { expect(service.createQueryParamsKey(1, 'my_string', FilterDurationOperator.FILTER_DURATION_OPERATOR_EQUAL, 7)) - .toEqual('1-my_string_7-0'); + .toEqual('1-my_string-7-0'); }); }); }); \ No newline at end of file From 06ba216f0273c8c0e07c7daae614af5aeb4c46a2 Mon Sep 17 00:00:00 2001 From: Faust1 Date: Thu, 4 Jan 2024 11:52:15 +0100 Subject: [PATCH 8/8] chore: added specified not sortable columns to tasks table --- src/app/tasks/services/tasks-index.service.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/app/tasks/services/tasks-index.service.ts b/src/app/tasks/services/tasks-index.service.ts index 8f5f49c49..51ea6cb21 100644 --- a/src/app/tasks/services/tasks-index.service.ts +++ b/src/app/tasks/services/tasks-index.service.ts @@ -101,7 +101,11 @@ export class TasksIndexService { } isNotSortableColumn(column: TaskSummaryColumnKey): boolean { - return this.isActionsColumn(column) || this.isObjectColumn(column) || this.isSelectColumn(column); + return this.isActionsColumn(column) || this.isObjectColumn(column) || this.isSelectColumn(column) || this.isSpecifiedNotSortableColumn(column); + } + + isSpecifiedNotSortableColumn(column: TaskSummaryColumnKey): boolean { + return column === 'countDataDependencies' || column === 'countParentTaskIds' || column === 'countExpectedOutputIds' || column === 'countRetryOfIds' || column === 'statusMessage'; } /**