mirror of
https://github.com/zitadel/zitadel.git
synced 2025-02-28 21:37:24 +00:00
feat: add time range events filter (#7005)
* feat(console): add time range events filter * deprecate creation_date, use oneof filter * use range or from * implement api * fix timestamp format * translate * styles * lint * integration tests * fix until date * rearrange sorting control * sort creation date * fix events e2e test * Update console/src/app/modules/filter-events/filter-events.component.html Co-authored-by: Max Peintner <max@caos.ch> * Update console/src/app/modules/filter-events/filter-events.component.html Co-authored-by: Max Peintner <max@caos.ch> * Update console/src/app/modules/filter-events/filter-events.component.html Co-authored-by: Max Peintner <max@caos.ch> * lint * lint * don't use utc call time --------- Co-authored-by: Max Peintner <max@caos.ch> Co-authored-by: Silvan <silvan.reusser@gmail.com>
This commit is contained in:
parent
2e505f40f9
commit
9da4abd459
@ -145,7 +145,6 @@
|
||||
</cnsl-form-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="filter-events-section">
|
||||
<div class="checkbox-wrapper">
|
||||
<mat-checkbox id="sequenceFilterSet" name="sequenceFilterSet" class="cb" formControlName="sequenceFilterSet"
|
||||
@ -153,47 +152,74 @@
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
<div class="filter-events-sub" *ngIf="sequenceFilterSet?.value">
|
||||
<cnsl-form-field class="aggregate-type-select">
|
||||
<cnsl-label>{{ 'IAM.EVENTS.FILTERS.SEQUENCE.SORT' | translate }}</cnsl-label>
|
||||
|
||||
<mat-select id="isAsc" name="isAsc" formControlName="isAsc">
|
||||
<mat-option [value]="false"> {{ 'IAM.EVENTS.FILTERS.SEQUENCE.DESC' | translate }} </mat-option>
|
||||
<mat-option [value]="true">{{ 'IAM.EVENTS.FILTERS.SEQUENCE.ASC' | translate }} </mat-option>
|
||||
</mat-select>
|
||||
</cnsl-form-field>
|
||||
|
||||
<cnsl-form-field class="filter-input-value">
|
||||
<cnsl-label>{{ 'IAM.EVENTS.FILTERS.SEQUENCE.LABEL' | translate }}</cnsl-label>
|
||||
<input cnslInput id="sequence" name="sequence" formControlName="sequence" />
|
||||
</cnsl-form-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="filter-events-section">
|
||||
<div class="checkbox-wrapper">
|
||||
<mat-checkbox
|
||||
id="creationDateFilterSet"
|
||||
name="creationDateFilterSet"
|
||||
class="cb"
|
||||
formControlName="creationDateFilterSet"
|
||||
>{{ 'IAM.EVENTS.FILTERS.CREATIONDATE.CHECKBOX' | translate }}
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
<div class="filter-events-sub" *ngIf="creationDateFilterSet?.value">
|
||||
<cnsl-form-field class="filter-input-value">
|
||||
<cnsl-label>{{ 'IAM.EVENTS.FILTERS.CREATIONDATE.LABEL' | translate }}</cnsl-label>
|
||||
<input
|
||||
cnslInput
|
||||
id="creationDate"
|
||||
name="creationDate"
|
||||
[matDatepicker]="picker"
|
||||
formControlName="creationDate"
|
||||
/>
|
||||
<mat-datepicker-toggle style="top: 0" cnslSuffix [for]="picker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #picker></mat-datepicker>
|
||||
</cnsl-form-field>
|
||||
<mat-radio-group aria-label="Select an option" class="filter-events-sub" formControlName="creationDateFilterType">
|
||||
<mat-radio-button [value]="CreationDateFilterType.FROM">
|
||||
{{ 'IAM.EVENTS.FILTERS.CREATIONDATE.RADIO_FROM' | translate }}
|
||||
</mat-radio-button>
|
||||
<mat-radio-button [value]="CreationDateFilterType.RANGE">
|
||||
{{ 'IAM.EVENTS.FILTERS.CREATIONDATE.RADIO_RANGE' | translate }}
|
||||
</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
<div class="filter-events-sub">
|
||||
<ng-container *ngIf="creationDateFilterType!.value === CreationDateFilterType.FROM">
|
||||
<mat-form-field class="datetime-input">
|
||||
<input
|
||||
class="datetime"
|
||||
matInput
|
||||
type="datetime-local"
|
||||
name="creationDateFrom"
|
||||
[value]="creationDateFrom!.value | date: 'yyyy-MM-dd HH:mm:ss'"
|
||||
(change)="creationDateFrom = $event.target"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="creationDateFilterType!.value === CreationDateFilterType.RANGE">
|
||||
<div class="datetime-range">
|
||||
<mat-form-field class="datetime-input">
|
||||
<cnsl-label
|
||||
>{{ 'IAM.EVENTS.FILTERS.CREATIONDATE.LABEL_SINCE' | translate }}
|
||||
<input
|
||||
matInput
|
||||
type="datetime-local"
|
||||
name="creationDateSince"
|
||||
[value]="creationDateSince!.value | date: 'yyyy-MM-dd HH:mm:ss'"
|
||||
(change)="creationDateSince = $event.target"
|
||||
/>
|
||||
</cnsl-label>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="datetime-input">
|
||||
<cnsl-label
|
||||
>{{ 'IAM.EVENTS.FILTERS.CREATIONDATE.LABEL_UNTIL' | translate }}
|
||||
<input
|
||||
matInput
|
||||
type="datetime-local"
|
||||
name="creationDateUntil"
|
||||
[value]="creationDateUntil!.value | date: 'yyyy-MM-dd HH:mm:ss'"
|
||||
(change)="creationDateUntil = $event.target"
|
||||
/>
|
||||
</cnsl-label>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
<div class="filter-events-section">
|
||||
<cnsl-form-field class="aggregate-type-select">
|
||||
<cnsl-label>{{ 'IAM.EVENTS.FILTERS.SORT' | translate }}</cnsl-label>
|
||||
|
||||
<mat-select id="isAsc" name="isAsc" formControlName="isAsc">
|
||||
<mat-option [value]="false"> {{ 'IAM.EVENTS.FILTERS.DESC' | translate }} </mat-option>
|
||||
<mat-option [value]="true">{{ 'IAM.EVENTS.FILTERS.ASC' | translate }} </mat-option>
|
||||
</mat-select>
|
||||
</cnsl-form-field>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
@ -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;
|
||||
|
@ -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<boolean>(false),
|
||||
creationDateFilterSet: new FormControl(false),
|
||||
creationDate: new FormControl<Date>(new Date()),
|
||||
creationDateFilterType: new FormControl(CreationDateFilterType.FROM),
|
||||
creationDateFrom: new FormControl<Date>(new Date()),
|
||||
// creationDateSince is 15 minutes in the past by default
|
||||
creationDateSince: new FormControl<Date>(new Date(new Date().getTime() - 15 * 60_000)),
|
||||
creationDateUntil: new FormControl<Date>(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<Date>, event: EventTarget | null): void {
|
||||
if (!(event instanceof HTMLInputElement)) {
|
||||
throw new Error('wrong target');
|
||||
}
|
||||
ctrl.setValue(new Date(event.value || ''));
|
||||
}
|
||||
}
|
||||
|
@ -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],
|
||||
})
|
||||
|
@ -70,7 +70,7 @@
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="sequence">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header [start]="'desc'" [disableClear]="true">
|
||||
<th mat-header-cell *matHeaderCellDef>
|
||||
{{ 'IAM.EVENTS.SEQUENCE' | translate }}
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let event">
|
||||
@ -81,10 +81,12 @@
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="creationDate">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'IAM.EVENTS.CREATIONDATE' | translate }}</th>
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header [start]="'desc'" [disableClear]="true">
|
||||
{{ 'IAM.EVENTS.CREATIONDATE' | translate }}
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let event">
|
||||
<ng-container *ngIf="event | toobject as event">
|
||||
<span>{{ event?.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm' }}</span>
|
||||
<span>{{ event?.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm:ss' }}</span>
|
||||
</ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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": "други"
|
||||
|
@ -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é"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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": "その他"
|
||||
|
@ -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": "други"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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": "другие"
|
||||
|
@ -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": "其他"
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
})
|
||||
|
@ -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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user