diff --git a/console/src/app/modules/filter-events/filter-events.component.html b/console/src/app/modules/filter-events/filter-events.component.html index 5d835c32b..ed1943b51 100644 --- a/console/src/app/modules/filter-events/filter-events.component.html +++ b/console/src/app/modules/filter-events/filter-events.component.html @@ -145,7 +145,6 @@ -
- - {{ 'IAM.EVENTS.FILTERS.SEQUENCE.SORT' | translate }} - - - {{ 'IAM.EVENTS.FILTERS.SEQUENCE.DESC' | translate }} - {{ 'IAM.EVENTS.FILTERS.SEQUENCE.ASC' | translate }} - - - {{ 'IAM.EVENTS.FILTERS.SEQUENCE.LABEL' | translate }}
-
-
- {{ 'IAM.EVENTS.FILTERS.CREATIONDATE.CHECKBOX' | translate }} - -
-
- - {{ 'IAM.EVENTS.FILTERS.CREATIONDATE.LABEL' | translate }} - - - - + + + {{ 'IAM.EVENTS.FILTERS.CREATIONDATE.RADIO_FROM' | translate }} + + + {{ 'IAM.EVENTS.FILTERS.CREATIONDATE.RADIO_RANGE' | translate }} + + +
+ + + + + + +
+ + {{ 'IAM.EVENTS.FILTERS.CREATIONDATE.LABEL_SINCE' | translate }} + + + + + {{ 'IAM.EVENTS.FILTERS.CREATIONDATE.LABEL_UNTIL' | translate }} + + + +
+
+
+ + {{ 'IAM.EVENTS.FILTERS.SORT' | translate }} + + + {{ 'IAM.EVENTS.FILTERS.DESC' | translate }} + {{ 'IAM.EVENTS.FILTERS.ASC' | translate }} + + +
diff --git a/console/src/app/modules/filter-events/filter-events.component.scss b/console/src/app/modules/filter-events/filter-events.component.scss index 8e2e9558c..3d7643501 100644 --- a/console/src/app/modules/filter-events/filter-events.component.scss +++ b/console/src/app/modules/filter-events/filter-events.component.scss @@ -19,7 +19,7 @@ display: flex; flex-direction: column; padding: 0.5rem 0; - min-width: 320px; + min-width: 360px; max-width: 360px; padding-bottom: 0.5rem; position: relative; @@ -55,11 +55,23 @@ align-items: center; } + .mdc-text-field--filled:not(.mdc-text-field--disabled) { + background-color: map-get($background, cards); + } + + .datetime-input { + width: 100%; + } + + .datetime-range { + display: inline-block; + } + .filter-events-sub { display: flex; flex-direction: row; align-items: center; - justify-content: space-between; + justify-content: space-around; padding: 0 0.5rem; background-color: if($is-dark-theme, #00000020, #00000008); margin: 0 -0.5rem; diff --git a/console/src/app/modules/filter-events/filter-events.component.ts b/console/src/app/modules/filter-events/filter-events.component.ts index 158a767ce..186436ddf 100644 --- a/console/src/app/modules/filter-events/filter-events.component.ts +++ b/console/src/app/modules/filter-events/filter-events.component.ts @@ -15,13 +15,18 @@ export enum UserTarget { EXTERNAL = 'external', } +enum CreationDateFilterType { + FROM = 'from', + RANGE = 'range', +} + function dateToTs(date: Date): Timestamp { const ts = new Timestamp(); const milliseconds = date.getTime(); - const seconds = Math.abs(milliseconds / 1000); + const seconds = milliseconds / 1000; const nanos = (milliseconds - seconds * 1000) * 1000 * 1000; - ts.setSeconds(seconds); - ts.setNanos(nanos); + ts.setSeconds(Math.round(seconds)); + ts.setNanos(Math.round(nanos)); return ts; } @@ -31,6 +36,9 @@ function dateToTs(date: Date): Timestamp { styleUrls: ['./filter-events.component.scss'], }) export class FilterEventsComponent implements OnInit { + // Make enum available in template + public CreationDateFilterType = CreationDateFilterType; + public showFilter: boolean = false; public ActionKeysType: any = ActionKeysType; @@ -53,8 +61,11 @@ export class FilterEventsComponent implements OnInit { sequenceFilterSet: new FormControl(false), sequence: new FormControl(''), isAsc: new FormControl(false), - creationDateFilterSet: new FormControl(false), - creationDate: new FormControl(new Date()), + creationDateFilterType: new FormControl(CreationDateFilterType.FROM), + creationDateFrom: new FormControl(new Date()), + // creationDateSince is 15 minutes in the past by default + creationDateSince: new FormControl(new Date(new Date().getTime() - 15 * 60_000)), + creationDateUntil: new FormControl(new Date()), userFilterSet: new FormControl(false), editorUserId: new FormControl(''), aggregateFilterSet: new FormControl(false), @@ -78,20 +89,35 @@ export class FilterEventsComponent implements OnInit { const { filter } = params; if (filter) { const stringifiedFilters = filter as string; - const filters = JSON.parse(stringifiedFilters); + const filters = JSON.parse(decodeURIComponent(stringifiedFilters)); if (filters.aggregateId) { this.request.setAggregateId(filters.aggregateId); this.aggregateId?.setValue(filters.aggregateId); this.aggregateFilterSet?.setValue(true); } - if (filters.creationDate) { - const milliseconds = filters.creationDate; - const date = new Date(milliseconds); - const ts = dateToTs(date); + if (filters.creationDateFrom) { + const millisecondsFrom = filters.creationDateFrom; + const dateFrom = new Date(millisecondsFrom); + const ts = dateToTs(dateFrom); + this.creationDateFrom?.setValue(dateFrom); + this.creationDateFilterType?.setValue(CreationDateFilterType.FROM); this.request.setCreationDate(ts); - this.creationDate?.setValue(date); - this.creationDateFilterSet?.setValue(true); + } + if (filters.creationDateSince || filters.creationDateUntil) { + const millisecondsFrom = filters.creationDateSince; + const dateSince = new Date(millisecondsFrom); + const tsSince = dateToTs(dateSince); + this.creationDateSince?.setValue(dateSince); + const millisecondsUntil = filters.creationDateUntil; + const dateUntil = new Date(millisecondsUntil); + const tsUntil = dateToTs(dateUntil); + this.creationDateUntil?.setValue(dateUntil); + const range = new ListEventsRequest.creation_date_range(); + range.setSince(tsSince); + range.setUntil(tsUntil); + this.request.setRange(range); + this.creationDateFilterType?.setValue(CreationDateFilterType.RANGE); } if (filters.aggregateTypesList && filters.aggregateTypesList.length) { const values = this.aggregateTypes.filter((agg) => filters.aggregateTypesList.includes(agg.type)); @@ -252,11 +278,28 @@ export class FilterEventsComponent implements OnInit { constructRequest.setAsc(formValues.isAsc); filterObject.isAsc = formValues.isAsc; } - if (formValues.creationDateFilterSet && formValues.creationDate) { - const date = new Date(formValues.creationDate); - const ts = dateToTs(date); - constructRequest.setCreationDate(ts); - filterObject.creationDate = date.getTime(); + if (formValues.creationDateFilterType === CreationDateFilterType.FROM) { + const dateFrom = new Date(formValues.creationDateFrom); + const tsFrom = dateToTs(dateFrom); + constructRequest.setFrom(tsFrom); + constructRequest.clearRange(); + filterObject.creationDateFrom = dateFrom.getTime(); + filterObject.creationDateSince = undefined; + filterObject.creationDateUntil = undefined; + } + if (formValues.creationDateFilterType === CreationDateFilterType.RANGE) { + const range = new ListEventsRequest.creation_date_range(); + const dateSince = new Date(formValues.creationDateSince); + const tsSince = dateToTs(dateSince); + range.setSince(tsSince); + filterObject.creationDateSince = dateSince.getTime(); + const dateUntil = new Date(formValues.creationDateUntil); + const tsUntil = dateToTs(dateUntil); + range.setUntil(tsUntil); + filterObject.creationDateUntil = dateUntil.getTime(); + constructRequest.setRange(range); + constructRequest.clearFrom(); + filterObject.creationDateFrom = undefined; } this.requestChanged.emit(constructRequest); @@ -265,7 +308,7 @@ export class FilterEventsComponent implements OnInit { this.router.navigate([], { relativeTo: this.route, queryParams: { - ['filter']: JSON.stringify(filterObject), + ['filter']: encodeURIComponent(JSON.stringify(filterObject)), }, replaceUrl: true, queryParamsHandling: 'merge', @@ -304,12 +347,32 @@ export class FilterEventsComponent implements OnInit { return this.form.get('sequenceFilterSet'); } - public get creationDate(): AbstractControl | null { - return this.form.get('creationDate'); + public get creationDateFilterType(): AbstractControl | null { + return this.form.get('creationDateFilterType'); } - public get creationDateFilterSet(): AbstractControl | null { - return this.form.get('creationDateFilterSet'); + public get creationDateFrom(): AbstractControl | null { + return this.form.get('creationDateFrom'); + } + + public set creationDateFrom(event: EventTarget | null) { + this.setDate(this.creationDateFrom!, event); + } + + public get creationDateSince(): AbstractControl | null { + return this.form.get('creationDateSince'); + } + + public set creationDateSince(event: EventTarget | null) { + this.setDate(this.creationDateSince!, event); + } + + public get creationDateUntil(): AbstractControl | null { + return this.form.get('creationDateUntil'); + } + + public set creationDateUntil(event: EventTarget | null) { + this.setDate(this.creationDateUntil!, event); } public get resourceOwnerFilterSet(): AbstractControl | null { @@ -341,9 +404,6 @@ export class FilterEventsComponent implements OnInit { if (this.userFilterSet?.value && this.editorUserId?.value) { ++count; } - if (this.creationDateFilterSet?.value && this.creationDate?.value) { - ++count; - } if (this.aggregateFilterSet?.value && this.aggregateId?.value) { ++count; } @@ -361,4 +421,11 @@ export class FilterEventsComponent implements OnInit { } return count; } + + private setDate(ctrl: AbstractControl, event: EventTarget | null): void { + if (!(event instanceof HTMLInputElement)) { + throw new Error('wrong target'); + } + ctrl.setValue(new Date(event.value || '')); + } } diff --git a/console/src/app/modules/filter-events/filter-events.module.ts b/console/src/app/modules/filter-events/filter-events.module.ts index 9aff53d7d..d4dd102ae 100644 --- a/console/src/app/modules/filter-events/filter-events.module.ts +++ b/console/src/app/modules/filter-events/filter-events.module.ts @@ -12,6 +12,8 @@ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { ActionKeysModule } from '../action-keys/action-keys.module'; import { InputModule } from '../input/input.module'; import { FilterEventsComponent } from './filter-events.component'; +import { MatInputModule } from '@angular/material/input'; +import { MatRadioModule } from '@angular/material/radio'; @NgModule({ declarations: [FilterEventsComponent], @@ -28,6 +30,8 @@ import { FilterEventsComponent } from './filter-events.component'; MatCheckboxModule, MatSelectModule, ActionKeysModule, + MatInputModule, + MatRadioModule, ], exports: [FilterEventsComponent], }) diff --git a/console/src/app/pages/events/events.component.html b/console/src/app/pages/events/events.component.html index acc8c3677..39c567b4d 100644 --- a/console/src/app/pages/events/events.component.html +++ b/console/src/app/pages/events/events.component.html @@ -70,7 +70,7 @@ - + {{ 'IAM.EVENTS.SEQUENCE' | translate }} @@ -81,10 +81,12 @@ - {{ 'IAM.EVENTS.CREATIONDATE' | translate }} + + {{ 'IAM.EVENTS.CREATIONDATE' | translate }} + - {{ event?.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm' }} + {{ event?.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm:ss' }} diff --git a/console/src/app/pages/events/events.component.ts b/console/src/app/pages/events/events.component.ts index 41f520a7a..c6dd22fc8 100644 --- a/console/src/app/pages/events/events.component.ts +++ b/console/src/app/pages/events/events.component.ts @@ -176,13 +176,13 @@ export class EventsComponent implements OnDestroy { req.setEditorUserId(filterRequest.getEditorUserId()); req.setResourceOwner(filterRequest.getResourceOwner()); req.setSequence(filterRequest.getSequence()); - req.setCreationDate(filterRequest.getCreationDate()); + req.setRange(filterRequest.getRange()); + req.setFrom(filterRequest.getFrom()); const isAsc: boolean = filterRequest.getAsc(); req.setAsc(isAsc); if (this.sortAsc !== isAsc) { - this.sort.sort({ id: 'sequence', start: isAsc ? 'asc' : 'desc', disableClear: true }); + this.sort.sort({ id: 'creationDate', start: isAsc ? 'asc' : 'desc', disableClear: true }); } - this.loadEvents(req, true); } diff --git a/console/src/assets/i18n/bg.json b/console/src/assets/i18n/bg.json index e3a1e7876..349a8496a 100644 --- a/console/src/assets/i18n/bg.json +++ b/console/src/assets/i18n/bg.json @@ -865,14 +865,16 @@ }, "SEQUENCE": { "LABEL": "Последователност", - "CHECKBOX": "Филтриране по последователност", - "SORT": "Сортиране", - "ASC": "Възходящ", - "DESC": "Спускане" + "CHECKBOX": "Филтриране по последователност" }, + "SORT": "Сортиране", + "ASC": "Възходящ", + "DESC": "Спускане", "CREATIONDATE": { - "LABEL": "Дата на създаване", - "CHECKBOX": "Филтриране по дата на създаване" + "RADIO_FROM": "От", + "RADIO_RANGE": "Обхват", + "LABEL_SINCE": "От", + "LABEL_UNTIL": "До" }, "OTHER": "друго", "OTHERS": "други" diff --git a/console/src/assets/i18n/cs.json b/console/src/assets/i18n/cs.json index ead54bb53..2da0d463e 100644 --- a/console/src/assets/i18n/cs.json +++ b/console/src/assets/i18n/cs.json @@ -872,14 +872,16 @@ }, "SEQUENCE": { "LABEL": "Sekvence", - "CHECKBOX": "Filtrovat podle Sekvence", - "SORT": "Třídění", - "ASC": "Vzestupně", - "DESC": "Sestupně" + "CHECKBOX": "Filtrovat podle Sekvence" }, + "SORT": "Třídění", + "ASC": "Vzestupně", + "DESC": "Sestupně", "CREATIONDATE": { - "LABEL": "Datum vytvoření", - "CHECKBOX": "Filtrovat podle Datumu vytvoření" + "RADIO_FROM": "Od", + "RADIO_RANGE": "Rozsah", + "LABEL_SINCE": "Od", + "LABEL_UNTIL": "Do" }, "OTHER": "jiný", "OTHERS": "jiné" diff --git a/console/src/assets/i18n/de.json b/console/src/assets/i18n/de.json index 1afe195e0..c2c28c751 100644 --- a/console/src/assets/i18n/de.json +++ b/console/src/assets/i18n/de.json @@ -871,14 +871,16 @@ }, "SEQUENCE": { "LABEL": "Sequenz", - "CHECKBOX": "Nach Sequenz filtern", - "SORT": "Sortierung", - "ASC": "aufsteigend", - "DESC": "absteigend" + "CHECKBOX": "Nach Sequenz filtern" }, + "SORT": "Sortierung", + "ASC": "Aufsteigend", + "DESC": "Absteigend", "CREATIONDATE": { - "LABEL": "Erstelldatum", - "CHECKBOX": "Nach Erstelldatum filtern" + "RADIO_FROM": "Von", + "RADIO_RANGE": "Zeitraum", + "LABEL_SINCE": "Seit", + "LABEL_UNTIL": "Bis" }, "OTHER": "weiterer", "OTHERS": "weitere" diff --git a/console/src/assets/i18n/en.json b/console/src/assets/i18n/en.json index 5b96e6e8d..38066e722 100644 --- a/console/src/assets/i18n/en.json +++ b/console/src/assets/i18n/en.json @@ -872,14 +872,16 @@ }, "SEQUENCE": { "LABEL": "Sequence", - "CHECKBOX": "Filter by Sequence", - "SORT": "Sorting", - "ASC": "Ascending", - "DESC": "Descending" + "CHECKBOX": "Filter by Sequence" }, + "SORT": "Sort", + "ASC": "Ascending", + "DESC": "Descending", "CREATIONDATE": { - "LABEL": "Creation Date", - "CHECKBOX": "Filter by Creation Date" + "RADIO_FROM": "From", + "RADIO_RANGE": "Range", + "LABEL_SINCE": "Since", + "LABEL_UNTIL": "Until" }, "OTHER": "other", "OTHERS": "others" diff --git a/console/src/assets/i18n/es.json b/console/src/assets/i18n/es.json index 7be679000..c2444c228 100644 --- a/console/src/assets/i18n/es.json +++ b/console/src/assets/i18n/es.json @@ -872,14 +872,16 @@ }, "SEQUENCE": { "LABEL": "Secuencia", - "CHECKBOX": "Filtrar por secuencia", - "SORT": "Ordenado", - "ASC": "Ascendente", - "DESC": "Descendente" + "CHECKBOX": "Filtrar por secuencia" }, + "SORT": "Ordenado", + "ASC": "Ascendente", + "DESC": "Descendente", "CREATIONDATE": { - "LABEL": "Fecha de creación", - "CHECKBOX": "Filtrar por fecha de creación" + "RADIO_FROM": "Desde", + "RADIO_RANGE": "Rango", + "LABEL_SINCE": "Desde", + "LABEL_UNTIL": "Hasta" }, "OTHER": "otro", "OTHERS": "otros" diff --git a/console/src/assets/i18n/fr.json b/console/src/assets/i18n/fr.json index 3659d9000..bcb2a9d50 100644 --- a/console/src/assets/i18n/fr.json +++ b/console/src/assets/i18n/fr.json @@ -871,14 +871,16 @@ }, "SEQUENCE": { "LABEL": "séquence", - "CHECKBOX": "Filtrer par séquence", - "SORT": "Triage", - "ASC": "Ascendant", - "DESC": "Descendant" + "CHECKBOX": "Filtrer par séquence" }, + "SORT": "Triage", + "ASC": "Ascendant", + "DESC": "Descendant", "CREATIONDATE": { - "LABEL": "Date de création", - "CHECKBOX": "Filtrer par date de création" + "RADIO_FROM": "De", + "RADIO_RANGE": "Gamme", + "LABEL_SINCE": "Depuis", + "LABEL_UNTIL": "Jusqu'à" }, "OTHER": "autre", "OTHERS": "autres" diff --git a/console/src/assets/i18n/it.json b/console/src/assets/i18n/it.json index 97c890eed..e3feb17fd 100644 --- a/console/src/assets/i18n/it.json +++ b/console/src/assets/i18n/it.json @@ -870,14 +870,16 @@ }, "SEQUENCE": { "LABEL": "Sequence", - "CHECKBOX": "Filter per sequenza", - "SORT": "", - "ASC": "Ascending", - "DESC": "Descending" + "CHECKBOX": "Filter per sequenza" }, + "SORT": "Ordina per", + "ASC": "Ascendente", + "DESC": "Discendente", "CREATIONDATE": { - "LABEL": "Creation Date", - "CHECKBOX": "Filter by Creation Date" + "RADIO_FROM": "Da", + "RADIO_RANGE": "Intervallo", + "LABEL_SINCE": "Da", + "LABEL_UNTIL": "A" }, "OTHER": "altro", "OTHERS": "altri" diff --git a/console/src/assets/i18n/ja.json b/console/src/assets/i18n/ja.json index 0977ea2be..036eef223 100644 --- a/console/src/assets/i18n/ja.json +++ b/console/src/assets/i18n/ja.json @@ -872,14 +872,16 @@ }, "SEQUENCE": { "LABEL": "シーケンス", - "CHECKBOX": "シーケンスで絞り込み", - "SORT": "ソート", - "ASC": "昇順", - "DESC": "降順" + "CHECKBOX": "シーケンスで絞り込み" }, + "SORT": "ソート", + "ASC": "昇順", + "DESC": "降順", "CREATIONDATE": { - "LABEL": "作成日", - "CHECKBOX": "作成日で絞り込み" + "RADIO_FROM": "から", + "RADIO_RANGE": "範囲", + "LABEL_SINCE": "以降", + "LABEL_UNTIL": "まで" }, "OTHER": "その他", "OTHERS": "その他" diff --git a/console/src/assets/i18n/mk.json b/console/src/assets/i18n/mk.json index 34810be3c..54d1053ae 100644 --- a/console/src/assets/i18n/mk.json +++ b/console/src/assets/i18n/mk.json @@ -872,14 +872,16 @@ }, "SEQUENCE": { "LABEL": "Секвенца", - "CHECKBOX": "Филтер според секвенцата", - "SORT": "Сортирање", - "ASC": "Растечки", - "DESC": "Опаѓачки" + "CHECKBOX": "Филтер според секвенцата" }, + "SORT": "Сортирање", + "ASC": "Растечки", + "DESC": "Опаѓачки", "CREATIONDATE": { - "LABEL": "Датум на креирање", - "CHECKBOX": "Филтер според датумот на креирање" + "RADIO_FROM": "Од", + "RADIO_RANGE": "Ранг", + "LABEL_SINCE": "Од", + "LABEL_UNTIL": "До" }, "OTHER": "друго", "OTHERS": "други" diff --git a/console/src/assets/i18n/nl.json b/console/src/assets/i18n/nl.json index cb8c5dc55..9a1614799 100644 --- a/console/src/assets/i18n/nl.json +++ b/console/src/assets/i18n/nl.json @@ -872,14 +872,16 @@ }, "SEQUENCE": { "LABEL": "Reeks", - "CHECKBOX": "Filter op Reeks", - "SORT": "Sortering", - "ASC": "Oplopend", - "DESC": "Aflopend" + "CHECKBOX": "Filter op Reeks" }, + "SORT": "Sortering", + "ASC": "Oplopend", + "DESC": "Aflopend", "CREATIONDATE": { - "LABEL": "Aanmaakdatum", - "CHECKBOX": "Filter op Aanmaakdatum" + "RADIO_FROM": "Van", + "RADIO_RANGE": "Reeks", + "LABEL_SINCE": "Sinds", + "LABEL_UNTIL": "Tot" }, "OTHER": "ander", "OTHERS": "anderen" diff --git a/console/src/assets/i18n/pl.json b/console/src/assets/i18n/pl.json index aa48a434a..4c5c20068 100644 --- a/console/src/assets/i18n/pl.json +++ b/console/src/assets/i18n/pl.json @@ -871,14 +871,16 @@ }, "SEQUENCE": { "LABEL": "Sekwencja", - "CHECKBOX": "Filtruj według sekwencji", - "SORT": "Sortowanie", - "ASC": "Rosnące", - "DESC": "Malejące" + "CHECKBOX": "Filtruj według sekwencji" }, + "SORT": "Sortowanie", + "ASC": "Rosnące", + "DESC": "Malejące", "CREATIONDATE": { - "LABEL": "Data utworzenia", - "CHECKBOX": "Filtruj według daty utworzenia" + "RADIO_FROM": "Od", + "RADIO_RANGE": "Zakres", + "LABEL_SINCE": "Od", + "LABEL_UNTIL": "Do" }, "OTHER": "inne", "OTHERS": "inni" diff --git a/console/src/assets/i18n/pt.json b/console/src/assets/i18n/pt.json index 69984728b..6535c5b95 100644 --- a/console/src/assets/i18n/pt.json +++ b/console/src/assets/i18n/pt.json @@ -872,14 +872,16 @@ }, "SEQUENCE": { "LABEL": "Sequência", - "CHECKBOX": "Filtrar por Sequência", - "SORT": "Ordenação", - "ASC": "Crescente", - "DESC": "Decrescente" + "CHECKBOX": "Filtrar por Sequência" }, + "SORT": "Ordenação", + "ASC": "Crescente", + "DESC": "Decrescente", "CREATIONDATE": { - "LABEL": "Data de Criação", - "CHECKBOX": "Filtrar por Data de Criação" + "RADIO_FROM": "Desde", + "RADIO_RANGE": "Intervalo", + "LABEL_SINCE": "Desde", + "LABEL_UNTIL": "Até" }, "OTHER": "outro", "OTHERS": "outros" diff --git a/console/src/assets/i18n/ru.json b/console/src/assets/i18n/ru.json index 02c023f5b..f147f8fb3 100644 --- a/console/src/assets/i18n/ru.json +++ b/console/src/assets/i18n/ru.json @@ -868,14 +868,16 @@ }, "SEQUENCE": { "LABEL": "Последовательность", - "CHECKBOX": "Фильтровать по последовательности", - "SORT": "Сортировка", - "ASC": "Восходящий", - "DESC": "По убыванию" + "CHECKBOX": "Фильтровать по последовательности" }, + "SORT": "Сортировка", + "ASC": "Восходящий", + "DESC": "По убыванию", "CREATIONDATE": { - "LABEL": "Дата создания", - "CHECKBOX": "Фильтровать по дате создания" + "RADIO_FROM": "От", + "RADIO_RANGE": "Диапазон", + "LABEL_SINCE": "С", + "LABEL_UNTIL": "К" }, "OTHER": "другой", "OTHERS": "другие" diff --git a/console/src/assets/i18n/zh.json b/console/src/assets/i18n/zh.json index d1eb3551d..e6cb353a8 100644 --- a/console/src/assets/i18n/zh.json +++ b/console/src/assets/i18n/zh.json @@ -871,14 +871,16 @@ }, "SEQUENCE": { "LABEL": "序列", - "CHECKBOX": "按顺序过滤", - "SORT": "分拣", - "ASC": "上升中", - "DESC": "下降" + "CHECKBOX": "按顺序过滤" }, + "SORT": "分拣", + "ASC": "上升中", + "DESC": "下降", "CREATIONDATE": { - "LABEL": "创建日期", - "CHECKBOX": "按创建日期过滤" + "RADIO_FROM": "从", + "RADIO_RANGE": "范围", + "LABEL_SINCE": "自从", + "LABEL_UNTIL": "直到" }, "OTHER": "其他", "OTHERS": "其他" diff --git a/e2e/cypress/e2e/events/events.cy.ts b/e2e/cypress/e2e/events/events.cy.ts index 65362b612..facd298b1 100644 --- a/e2e/cypress/e2e/events/events.cy.ts +++ b/e2e/cypress/e2e/events/events.cy.ts @@ -9,9 +9,8 @@ describe('events', () => { cy.get('[data-e2e="event-type-cell"]').should('have.length', 20); cy.get('[data-e2e="open-filter-button"]').click(); cy.get('[data-e2e="event-type-filter-checkbox"]').click(); - cy.get('#mat-select-value-1').click(); - cy.contains('mat-option', eventTypeEnglish).click(); - cy.get('body').click(); + cy.contains('mat-select', 'Descending').click(); + cy.contains('mat-option', 'Ascending').click(); cy.get('[data-e2e="filter-finish-button"]').click(); cy.contains('[data-e2e="event-type-cell"]', eventTypeEnglish).should('have.length.at.least', 1); }); diff --git a/internal/api/grpc/admin/event.go b/internal/api/grpc/admin/event.go index 72adfa015..576fce8af 100644 --- a/internal/api/grpc/admin/event.go +++ b/internal/api/grpc/admin/event.go @@ -2,6 +2,7 @@ package admin import ( "context" + "time" "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/eventstore" @@ -36,6 +37,25 @@ func (s *Server) ListAggregateTypes(ctx context.Context, in *admin_pb.ListAggreg } func eventRequestToFilter(ctx context.Context, req *admin_pb.ListEventsRequest) (*eventstore.SearchQueryBuilder, error) { + var fromTime, sinceTime, untilTime time.Time + // We ignore the deprecation warning here because we still need to support the deprecated field. + //nolint:staticcheck + if creationDatePb := req.GetCreationDate(); creationDatePb != nil { + fromTime = creationDatePb.AsTime() + } + if fromTimePb := req.GetFrom(); fromTimePb != nil { + fromTime = fromTimePb.AsTime() + } + if timeRange := req.GetRange(); timeRange != nil { + // If range is set, we ignore the from and the deprecated creation_date fields + fromTime = time.Time{} + if timeSincePb := timeRange.GetSince(); timeSincePb != nil { + sinceTime = timeSincePb.AsTime() + } + if timeUntilPb := timeRange.GetUntil(); timeUntilPb != nil { + untilTime = timeUntilPb.AsTime() + } + } eventTypes := make([]eventstore.EventType, len(req.EventTypes)) for i, eventType := range req.EventTypes { eventTypes[i] = eventstore.EventType(eventType) @@ -60,7 +80,9 @@ func eventRequestToFilter(ctx context.Context, req *admin_pb.ListEventsRequest) AwaitOpenTransactions(). ResourceOwner(req.ResourceOwner). EditorUser(req.EditorUserId). - SequenceGreater(req.Sequence) + SequenceGreater(req.Sequence). + CreationDateAfter(sinceTime). + CreationDateBefore(untilTime) if len(aggregateIDs) > 0 || len(aggregateTypes) > 0 || len(eventTypes) > 0 { builder.AddQuery(). @@ -72,10 +94,9 @@ func eventRequestToFilter(ctx context.Context, req *admin_pb.ListEventsRequest) if req.GetAsc() { builder.OrderAsc() - builder.CreationDateAfter(req.CreationDate.AsTime()) + builder.CreationDateAfter(fromTime) } else { - builder.CreationDateBefore(req.CreationDate.AsTime()) + builder.CreationDateBefore(fromTime) } - return builder, nil } diff --git a/internal/api/grpc/system/limits_integration_test.go b/internal/api/grpc/system/limits_integration_test.go index 2557d72c2..96ceafcdc 100644 --- a/internal/api/grpc/system/limits_integration_test.go +++ b/internal/api/grpc/system/limits_integration_test.go @@ -4,6 +4,7 @@ package system_test import ( "context" + "google.golang.org/protobuf/types/known/timestamppb" "math/rand" "sync" "testing" @@ -23,6 +24,7 @@ func TestServer_Limits_AuditLogRetention(t *testing.T) { _, instanceID, iamOwnerCtx := Tester.UseIsolatedInstance(t, CTX, SystemCTX) userID, projectID, appID, projectGrantID := seedObjects(iamOwnerCtx, t) beforeTime := time.Now() + farPast := timestamppb.New(beforeTime.Add(-10 * time.Hour).UTC()) zeroCounts := &eventCounts{} seededCount := requireEventually(t, iamOwnerCtx, userID, projectID, appID, projectGrantID, func(c assert.TestingT, counts *eventCounts) { counts.assertAll(t, c, "seeded events are > 0", assert.Greater, zeroCounts) @@ -36,10 +38,22 @@ func TestServer_Limits_AuditLogRetention(t *testing.T) { AuditLogRetention: durationpb.New(time.Now().Sub(beforeTime)), }) require.NoError(t, err) + var limitedCounts *eventCounts requireEventually(t, iamOwnerCtx, userID, projectID, appID, projectGrantID, func(c assert.TestingT, counts *eventCounts) { counts.assertAll(t, c, "limited events < added events", assert.Less, addedCount) counts.assertAll(t, c, "limited events > 0", assert.Greater, zeroCounts) + limitedCounts = counts }, "wait for limited event assertions to pass") + listedEvents, err := Tester.Client.Admin.ListEvents(iamOwnerCtx, &admin.ListEventsRequest{CreationDateFilter: &admin.ListEventsRequest_From{ + From: farPast, + }}) + require.NoError(t, err) + assert.LessOrEqual(t, len(listedEvents.GetEvents()), limitedCounts.all, "ListEvents with from query older than retention doesn't return more events") + listedEvents, err = Tester.Client.Admin.ListEvents(iamOwnerCtx, &admin.ListEventsRequest{CreationDateFilter: &admin.ListEventsRequest_Range{Range: &admin.ListEventsRequestCreationDateRange{ + Since: farPast, + }}}) + require.NoError(t, err) + assert.LessOrEqual(t, len(listedEvents.GetEvents()), limitedCounts.all, "ListEvents with since query older than retention doesn't return more events") _, err = Tester.Client.System.ResetLimits(SystemCTX, &system.ResetLimitsRequest{ InstanceId: instanceID, }) diff --git a/proto/zitadel/admin.proto b/proto/zitadel/admin.proto index dc0dc962e..e85ccea8a 100644 --- a/proto/zitadel/admin.proto +++ b/proto/zitadel/admin.proto @@ -7972,11 +7972,35 @@ message ListEventsRequest { } ]; google.protobuf.Timestamp creation_date = 9 [ + deprecated = true, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { example: "\"2019-04-01T08:45:00.000000Z\""; - description: "If asc is false, the events returned are older than creation_date. If asc is true, the events returned are younger than creation_date. If creation_date is not set the field is ignored."; + description: "Use from instead."; } ]; + message creation_date_range { + google.protobuf.Timestamp since = 1 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"2019-04-01T08:45:00.000000Z\""; + description: "The events returned are younger than the UTC since date"; + } + ]; + google.protobuf.Timestamp until = 2 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"2019-04-01T08:45:00.000000Z\""; + description: "The events returned are older than the UTC until date."; + } + ]; + } + oneof creation_date_filter { + creation_date_range range = 10; + google.protobuf.Timestamp from = 11 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"2019-04-01T08:45:00.000000Z\""; + description: "If asc is false, the events returned are older than the UTC from date. If asc is true, the events returned are younger than from."; + } + ]; + } } message ListEventsResponse {