feat(console): events (#5092)

Shows the list of events on instance level
This commit is contained in:
Max Peintner
2023-02-01 08:54:00 +00:00
committed by GitHub
parent 5ca7e83f0a
commit e9d5d1dcaf
35 changed files with 1790 additions and 13 deletions
@@ -63,7 +63,11 @@ export class ChangesComponent implements OnInit, OnDestroy {
private _data: BehaviorSubject<any> = new BehaviorSubject([]);
loading: Observable<boolean> = this._loading.asObservable();
public data!: Observable<MappedChange[]>;
public data: Observable<MappedChange[]> = this._data.asObservable().pipe(
scan((acc, val) => {
return false ? val.concat(acc) : acc.concat(val);
}),
);
public changes!: ListChanges;
private destroyed$: Subject<void> = new Subject();
constructor(private mgmtUserService: ManagementService, private authUserService: GrpcAuthService) {}
@@ -106,13 +110,6 @@ export class ChangesComponent implements OnInit, OnDestroy {
}
this.mapAndUpdate(first);
// Create the observable array for consumption in components
this.data = this._data.asObservable().pipe(
scan((acc, val) => {
return false ? val.concat(acc) : acc.concat(val);
}),
);
}
public more(): void {
@@ -0,0 +1,79 @@
<h2 class="title" mat-dialog-title>{{ 'IAM.EVENTS.DIALOG.TITLE' | translate }}</h2>
<div mat-dialog-content>
<div class="event-data-column" *ngIf="event && (event | toobject) as event">
<div class="data-row" *ngIf="event && event.type && event.type.localized && event.type.localized.localizedMessage">
<span class="label cnsl-secondary-text">{{ 'IAM.EVENTS.TYPE' | translate }}</span>
<span>{{ event.type.localized.localizedMessage }}</span>
</div>
<div class="data-row" *ngIf="event && event.creationDate">
<span class="label cnsl-secondary-text">{{ 'IAM.EVENTS.CREATIONDATE' | translate }}</span>
<span>{{ event.creationDate | timestampToDate | localizedDate : 'EEE dd. MMM, HH:mm' }}</span>
</div>
<div class="data-row" *ngIf="event && event.aggregate && event.aggregate.id">
<span class="label cnsl-secondary-text">{{ 'IAM.EVENTS.AGGREGATEID' | translate }}</span>
<span>{{ event.aggregate.id }}</span>
</div>
<div class="data-row" *ngIf="event && event.aggregate && event.aggregate.resourceOwner">
<span class="label cnsl-secondary-text">{{ 'IAM.EVENTS.RESOURCEOWNER' | translate }}</span>
<span>{{ event.aggregate.resourceOwner }}</span>
</div>
<div
class="data-row"
*ngIf="
event &&
event.aggregate &&
event.aggregate.type &&
event.aggregate.type.localized &&
event.aggregate.type.localized.localizedMessage
"
>
<span class="label cnsl-secondary-text">{{ 'IAM.EVENTS.AGGREGATETYPE' | translate }}</span>
<span class="state aggregate-type">{{ event.aggregate.type.localized.localizedMessage }}</span>
</div>
<div class="data-row" *ngIf="event && event.editor?.userId">
<span class="label cnsl-secondary-text">{{ 'IAM.EVENTS.EDITORID' | translate }}</span>
<span>{{ event.editor?.userId }}</span>
</div>
<div class="data-row" *ngIf="event && event.editor?.displayName">
<span class="label cnsl-secondary-text">{{ 'IAM.EVENTS.EDITOR' | translate }}</span>
<span>{{ event.editor?.displayName }}</span>
</div>
<div class="data-row" *ngIf="event && event.sequence">
<span class="label cnsl-secondary-text">{{ 'IAM.EVENTS.SEQUENCE' | translate }}</span>
<span>{{ event.sequence }}</span>
</div>
</div>
<div class="code" *ngIf="opened$ | async">
<ngx-codemirror
*ngIf="event"
[ngModel]="event | topayload | json"
[options]="{
height: 'auto',
readOnly: true,
lineNumbers: true,
lineWrapping: true,
indentWithTabs: false,
tabSize: 2,
theme: 'material',
mode: {
name: 'javascript',
json: true,
statementIndent: 2
}
}"
></ngx-codemirror>
</div>
</div>
<div mat-dialog-actions class="action">
<button mat-stroked-button (click)="closeDialog()">
{{ 'ACTIONS.CANCEL' | translate }}
</button>
</div>
@@ -0,0 +1,45 @@
.title {
margin-bottom: 2rem;
display: block;
}
.event-data-column {
display: grid;
grid-template-columns: 1fr 1fr;
grid-column-gap: 2rem;
grid-row-gap: 1rem;
margin-bottom: 2rem;
.data-row {
display: flex;
flex-direction: column;
overflow: hidden;
.label {
font-size: 12px;
margin-bottom: 2px;
}
.aggregate-type {
align-self: flex-start;
}
span {
text-overflow: ellipsis;
text-overflow: ellipsis;
}
}
}
.code {
width: 100%;
}
.action {
display: flex;
justify-content: flex-end;
.ok-button {
margin-left: 0.5rem;
}
}
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DisplayJsonDialogComponent } from './display-json-dialog.component';
describe('DisplayJsonDialogComponent', () => {
let component: DisplayJsonDialogComponent;
let fixture: ComponentFixture<DisplayJsonDialogComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [DisplayJsonDialogComponent],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(DisplayJsonDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,28 @@
import { Component, Inject } from '@angular/core';
import {
MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA,
MatLegacyDialogRef as MatDialogRef,
} from '@angular/material/legacy-dialog';
import { mapTo } from 'rxjs';
import { Event } from 'src/app/proto/generated/zitadel/event_pb';
@Component({
selector: 'cnsl-display-json-dialog',
templateUrl: './display-json-dialog.component.html',
styleUrls: ['./display-json-dialog.component.scss'],
})
export class DisplayJsonDialogComponent {
public event?: Event;
public payload: any = '';
public opened$ = this.dialogRef.afterOpened().pipe(mapTo(true));
constructor(public dialogRef: MatDialogRef<DisplayJsonDialogComponent>, @Inject(MAT_DIALOG_DATA) public data: any) {
this.event = data.event;
if ((data.event as Event) && data.event.payload) {
}
}
public closeDialog(): void {
this.dialogRef.close(false);
}
}
@@ -0,0 +1,31 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
import { MatIconModule } from '@angular/material/icon';
import { TranslateModule } from '@ngx-translate/core';
import { LocalizedDatePipeModule } from 'src/app/pipes/localized-date-pipe/localized-date-pipe.module';
import { DisplayJsonDialogComponent } from './display-json-dialog.component';
import { CodemirrorModule } from '@ctrl/ngx-codemirror';
import { FormsModule } from '@angular/forms';
import { TimestampToDatePipeModule } from 'src/app/pipes/timestamp-to-date-pipe/timestamp-to-date-pipe.module';
import { ToPayloadPipeModule } from 'src/app/pipes/to-payload/to-payload.module';
import { ToObjectPipeModule } from 'src/app/pipes/to-object/to-object.module';
@NgModule({
declarations: [DisplayJsonDialogComponent],
imports: [
CommonModule,
FormsModule,
TranslateModule,
MatButtonModule,
MatIconModule,
CodemirrorModule,
TimestampToDatePipeModule,
ToObjectPipeModule,
ToPayloadPipeModule,
LocalizedDatePipeModule,
],
exports: [DisplayJsonDialogComponent],
})
export class DisplayJsonDialogModule {}
@@ -0,0 +1,195 @@
<div class="filter-button-wrapper">
<button
mat-stroked-button
cdkOverlayOrigin
(click)="showFilter = !showFilter"
class="cnsl-action-button"
#triggereventfilter="cdkOverlayOrigin"
>
<i class="las la-filter no-margin"></i>
<span>{{ 'ACTIONS.FILTER' | translate }}</span>
<span *ngIf="queryCount" class="filter-count">{{ queryCount }}</span>
<cnsl-action-keys [doNotUseContrast]="true" [type]="ActionKeysType.FILTER" (actionTriggered)="showFilter = !showFilter">
</cnsl-action-keys>
</button>
<ng-template
cdkConnectedOverlay
[cdkConnectedOverlayHasBackdrop]="true"
[flexibleDimensions]="true"
[lockPosition]="true"
[cdkConnectedOverlayOffsetY]="10"
[cdkConnectedOverlayPositions]="positions"
[cdkConnectedOverlayOrigin]="triggereventfilter"
[cdkConnectedOverlayOpen]="showFilter"
cdkConnectedOverlayBackdropClass="transparent-backdrop"
(backdropClick)="showFilter = false"
(detach)="showFilter = false"
>
<div class="filter-events-wrapper">
<div class="filter-events-top">
<button mat-stroked-button type="button" (click)="reset()">{{ 'ACTIONS.RESET' | translate }}</button>
<span class="filter-middle">{{ 'FILTER.TITLE' | translate }}</span>
<button mat-raised-button color="primary" type="button" (click)="finish()">
{{ 'ACTIONS.FINISH' | translate }}
</button>
</div>
<form *ngIf="form" [formGroup]="form" (ngSubmit)="emitChange()">
<div *ngIf="isLoading" class="sp_wrapper"><mat-spinner diameter="20"></mat-spinner></div>
<div class="filter-events-section">
<div class="checkbox-wrapper">
<mat-checkbox id="userFilterSet" name="userFilterSet" class="cb" formControlName="userFilterSet"
>{{ 'IAM.EVENTS.FILTERS.USER.CHECKBOX' | translate }}
</mat-checkbox>
</div>
<div class="filter-events-sub" *ngIf="userFilterSet?.value">
<cnsl-form-field class="filter-input-value">
<cnsl-label>{{ 'IAM.EVENTS.FILTERS.USER.IDLABEL' | translate }}</cnsl-label>
<input cnslInput id="editorUserId" name="editorUserId" formControlName="editorUserId" />
</cnsl-form-field>
</div>
</div>
<div class="filter-events-section">
<div class="checkbox-wrapper">
<mat-checkbox id="aggregateFilterSet" name="aggregateFilterSet" class="cb" formControlName="aggregateFilterSet"
>{{ 'IAM.EVENTS.FILTERS.AGGREGATE.CHECKBOX' | translate }}
</mat-checkbox>
</div>
<div class="filter-events-sub" *ngIf="aggregateFilterSet?.value">
<cnsl-form-field class="aggregate-type-select">
<cnsl-label>{{ 'IAM.EVENTS.FILTERS.AGGREGATE.TYPELABEL' | translate }}</cnsl-label>
<mat-select
id="aggregateTypesList"
name="aggregateTypesList"
formControlName="aggregateTypesList"
[multiple]="true"
>
<mat-select-trigger>
<span *ngIf="aggregateTypesList?.value && aggregateTypesList?.value.length">{{
aggregateTypesList?.value[0]?.localized?.localizedMessage || ''
}}</span>
<small *ngIf="(aggregateTypesList?.value?.length || 0) > 1" class="cnsl-secondary-text">
(+{{ (aggregateTypesList?.value?.length || 0) - 1 }}
{{ aggregateTypesList?.value?.length === 2 ? 'other' : 'others' }})
</small>
</mat-select-trigger>
<mat-option *ngFor="let aggregate of aggregateTypes" [value]="aggregate">
<span *ngIf="aggregate.localized && aggregate.localized.localizedMessage">
{{ aggregate.localized.localizedMessage }}
</span>
</mat-option>
</mat-select>
</cnsl-form-field>
<cnsl-form-field class="filter-input-value">
<cnsl-label>{{ 'IAM.EVENTS.FILTERS.AGGREGATE.IDLABEL' | translate }}</cnsl-label>
<input id="aggregateId" formControlName="aggregateId" cnslInput name="aggregateId" />
</cnsl-form-field>
</div>
</div>
<div class="filter-events-section">
<div class="checkbox-wrapper">
<mat-checkbox
id="eventTypesFilterSet"
name="eventTypesFilterSet"
class="cb"
formControlName="eventTypesFilterSet"
>{{ 'IAM.EVENTS.FILTERS.TYPE.CHECKBOX' | translate }}
</mat-checkbox>
</div>
<div class="filter-events-sub" *ngIf="eventTypesFilterSet?.value">
<cnsl-form-field class="event-types-select">
<cnsl-label>{{ 'IAM.EVENTS.FILTERS.TYPE.TYPELABEL' | translate }}</cnsl-label>
<mat-select id="eventTypesList" name="eventTypesList" formControlName="eventTypesList" [multiple]="true">
<mat-select-trigger>
<span *ngIf="eventTypesList?.value && eventTypesList?.value.length">{{
eventTypesList?.value[0]?.localized?.localizedMessage || ''
}}</span>
<small *ngIf="(eventTypesList?.value?.length || 0) > 1" class="cnsl-secondary-text">
(+{{ (eventTypesList?.value?.length || 0) - 1 }}
{{ eventTypesList?.value?.length === 2 ? 'other' : 'others' }})
</small>
</mat-select-trigger>
<mat-option *ngFor="let eventType of eventTypes" [value]="eventType">
<ng-container *ngIf="eventType.localized && eventType.localized.localizedMessage">
{{ eventType.localized.localizedMessage }}
</ng-container>
</mat-option>
</mat-select>
</cnsl-form-field>
</div>
</div>
<div class="filter-events-section">
<div class="checkbox-wrapper">
<mat-checkbox
id="resourceOwnerFilterSet"
name="resourceOwnerFilterSet"
class="cb"
formControlName="resourceOwnerFilterSet"
>{{ 'IAM.EVENTS.FILTERS.RESOURCEOWNER.CHECKBOX' | translate }}
</mat-checkbox>
</div>
<div class="filter-events-sub" *ngIf="resourceOwnerFilterSet?.value">
<cnsl-form-field class="filter-input-value">
<cnsl-label>{{ 'IAM.EVENTS.FILTERS.RESOURCEOWNER.LABEL' | translate }}</cnsl-label>
<input cnslInput id="resourceOwner" name="resourceOwner" formControlName="resourceOwner" />
</cnsl-form-field>
</div>
</div>
<div class="filter-events-section">
<div class="checkbox-wrapper">
<mat-checkbox id="sequenceFilterSet" name="sequenceFilterSet" class="cb" formControlName="sequenceFilterSet"
>{{ 'IAM.EVENTS.FILTERS.SEQUENCE.CHECKBOX' | translate }}
</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>
</div>
</div>
</form>
</div>
</ng-template>
</div>
@@ -0,0 +1,93 @@
@use '@angular/material' as mat;
@mixin filter-events-theme($theme) {
$primary: map-get($theme, primary);
$primary-color: mat.get-color-from-palette($primary, 500);
$lighter-primary-color: mat.get-color-from-palette($primary, 300);
$darker-primary-color: mat.get-color-from-palette($primary, 700);
$background: map-get($theme, background);
$foreground: map-get($theme, foreground);
$secondary-text: map-get($foreground, secondary-text);
$is-dark-theme: map-get($theme, is-dark);
$link-hover-color: if($is-dark-theme, mat.get-color-from-palette($primary, 200), $primary-color);
$link-color: if($is-dark-theme, $lighter-primary-color, $primary-color);
.filter-events-wrapper {
border-radius: 0.5rem;
z-index: 200;
border: 1px solid #ffffff30;
display: flex;
flex-direction: column;
padding: 0.5rem 0;
min-width: 320px;
max-width: 360px;
padding-bottom: 0.5rem;
position: relative;
color: map-get($foreground, text);
background: map-get($background, cards);
.sp_wrapper {
display: flex;
align-items: center;
justify-content: center;
padding: 0.5rem 0;
}
.filter-events-top {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 0.5rem 0.5rem 0.5rem;
border-bottom: 2px solid if($is-dark-theme, #ffffff15, #00000015);
.filter-middle {
margin: 0 1rem;
}
}
}
.filter-events-section {
padding: 0 0.5rem;
.checkbox-wrapper {
height: 40px;
display: flex;
align-items: center;
}
.filter-events-sub {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 0 0.5rem;
background-color: if($is-dark-theme, #00000020, #00000008);
margin: 0 -0.5rem;
.aggregate-type-select .mat-select {
height: 36px;
padding: 7px 10px;
}
.aggregate-type-select {
min-width: 100px;
margin-right: 0.5rem;
}
.event-types-select {
width: 100%;
}
.filter-input-value {
flex: 1;
input {
height: 36px;
font-size: 15px;
}
}
}
}
}
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FilterEventsComponent } from './filter-events.component';
describe('FilterOrgComponent', () => {
let component: FilterEventsComponent;
let fixture: ComponentFixture<FilterEventsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [FilterEventsComponent],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(FilterEventsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,330 @@
import { ConnectedPosition, ConnectionPositionPair } from '@angular/cdk/overlay';
import { Component, EventEmitter, OnInit, Output } from '@angular/core';
import { AbstractControl, FormControl, FormGroup } from '@angular/forms';
import { MatLegacyOptionSelectionChange } from '@angular/material/legacy-core';
import { MatLegacySelectChange } from '@angular/material/legacy-select';
import { ActivatedRoute, Router } from '@angular/router';
import { Timestamp } from 'google-protobuf/google/protobuf/timestamp_pb';
import { take } from 'rxjs';
import { ListAggregateTypesRequest, ListEventsRequest } from 'src/app/proto/generated/zitadel/admin_pb';
import { AggregateType, EventType } from 'src/app/proto/generated/zitadel/event_pb';
import { AdminService } from 'src/app/services/admin.service';
import { ToastService } from 'src/app/services/toast.service';
import { ActionKeysType } from '../action-keys/action-keys.component';
export enum UserTarget {
SELF = 'self',
EXTERNAL = 'external',
}
function dateToTs(date: Date): Timestamp {
const ts = new Timestamp();
const milliseconds = date.getTime();
const seconds = Math.abs(milliseconds / 1000);
const nanos = (milliseconds - seconds * 1000) * 1000 * 1000;
ts.setSeconds(seconds);
ts.setNanos(nanos);
return ts;
}
@Component({
selector: 'cnsl-filter-events',
templateUrl: './filter-events.component.html',
styleUrls: ['./filter-events.component.scss'],
})
export class FilterEventsComponent implements OnInit {
public showFilter: boolean = false;
public ActionKeysType: any = ActionKeysType;
public positions: ConnectedPosition[] = [
new ConnectionPositionPair({ originX: 'start', originY: 'bottom' }, { overlayX: 'start', overlayY: 'top' }, 0, 10),
new ConnectionPositionPair({ originX: 'end', originY: 'bottom' }, { overlayX: 'end', overlayY: 'top' }, 0, 10),
];
private request: ListEventsRequest = new ListEventsRequest();
public aggregateTypes: AggregateType.AsObject[] = [];
public eventTypes: Array<EventType.AsObject> = [];
public isLoading: boolean = false;
@Output() public requestChanged: EventEmitter<ListEventsRequest> = new EventEmitter();
public form: FormGroup = new FormGroup({
resourceOwnerFilterSet: new FormControl(false),
resourceOwner: new FormControl(''),
sequenceFilterSet: new FormControl(false),
sequence: new FormControl(''),
isAsc: new FormControl<boolean>(false),
creationDateFilterSet: new FormControl(false),
creationDate: new FormControl<Date>(new Date()),
userFilterSet: new FormControl(false),
editorUserId: new FormControl(''),
aggregateFilterSet: new FormControl(false),
aggregateId: new FormControl(''),
aggregateTypesList: new FormControl<AggregateType.AsObject[]>([]),
eventTypesFilterSet: new FormControl(false),
eventTypesList: new FormControl<EventType.AsObject[]>([]),
});
constructor(
private adminService: AdminService,
private toast: ToastService,
private route: ActivatedRoute,
private router: Router,
) {}
public ngOnInit(): void {
this.loadAvailableTypes().then(() => {
this.route.queryParams.pipe(take(1)).subscribe((params) => {
this.loadAvailableTypes().then(() => {
const { filter } = params;
if (filter) {
const stringifiedFilters = filter as string;
const filters = JSON.parse(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);
this.request.setCreationDate(ts);
this.creationDate?.setValue(date);
this.creationDateFilterSet?.setValue(true);
}
if (filters.aggregateTypesList && filters.aggregateTypesList.length) {
const values = this.aggregateTypes.filter((agg) => filters.aggregateTypesList.includes(agg.type));
this.request.setAggregateTypesList(filters.aggregateTypesList);
this.aggregateTypesList?.setValue(values);
this.aggregateFilterSet?.setValue(true);
}
if (filters.editorUserId) {
this.request.setEditorUserId(filters.editorUserId);
this.editorUserId?.setValue(filters.editorUserId);
this.userFilterSet?.setValue(true);
}
if (filters.resourceOwner) {
this.request.setResourceOwner(filters.resourceOwner);
this.resourceOwner?.setValue(filters.resourceOwner);
this.resourceOwnerFilterSet?.setValue(true);
}
if (filters.sequence) {
this.request.setSequence(filters.sequence);
this.sequence?.setValue(filters.sequence);
this.sequenceFilterSet?.setValue(true);
}
if (filters.isAsc) {
this.request.setAsc(filters.isAsc);
this.isAsc?.setValue(filters.isAsc);
}
if (filters.eventTypesList && filters.eventTypesList.length) {
const values = this.eventTypes.filter((ev) => filters.eventTypesList.includes(ev.type));
this.request.setEventTypesList(filters.eventTypesList);
this.eventTypesList?.setValue(values);
this.eventTypesFilterSet?.setValue(true);
}
this.emitChange();
}
});
});
});
}
private loadAvailableTypes(): Promise<void> {
this.isLoading = true;
const aT = this.getAggregateTypes();
const eT = this.getEventTypes();
return Promise.all([aT, eT])
.then(() => {
this.isLoading = false;
})
.catch(() => {
this.isLoading = false;
});
}
public reset(): void {
this.form.reset();
this.emitChange();
}
public finish(): void {
this.showFilter = false;
this.emitChange();
}
private getAggregateTypes(): Promise<void> {
const req = new ListAggregateTypesRequest();
return this.adminService
.listAggregateTypes(req)
.then((list) => {
this.aggregateTypes = list.aggregateTypesList ?? [];
})
.catch((error) => {
this.toast.showError(error);
});
}
private getEventTypes(): Promise<void> {
const req = new ListAggregateTypesRequest();
return this.adminService
.listEventTypes(req)
.then((list) => {
this.eventTypes = list.eventTypesList ?? [];
})
.catch((error) => {
this.toast.showError(error);
});
}
public emitChange(): void {
const formValues = this.form.value;
const constructRequest = new ListEventsRequest();
let filterObject: any = {};
if (formValues.userFilterSet && formValues.editorUserId) {
constructRequest.setEditorUserId(formValues.editorUserId);
filterObject.editorUserId = formValues.editorUserId;
}
if (formValues.aggregateFilterSet && formValues.aggregateTypesList && formValues.aggregateTypesList.length) {
constructRequest.setAggregateTypesList(
formValues.aggregateTypesList.map((aggType: AggregateType.AsObject) => aggType.type),
);
filterObject.aggregateTypesList = formValues.aggregateTypesList.map((aggType: AggregateType.AsObject) => aggType.type);
}
if (formValues.aggregateFilterSet && formValues.aggregateId) {
constructRequest.setAggregateId(formValues.aggregateId);
filterObject.aggregateId = formValues.aggregateId;
}
if (formValues.eventTypesFilterSet && formValues.eventTypesList && formValues.eventTypesList.length) {
constructRequest.setEventTypesList(formValues.eventTypesList.map((eventType: EventType.AsObject) => eventType.type));
filterObject.eventTypesList = formValues.eventTypesList.map((eventType: EventType.AsObject) => eventType.type);
}
if (formValues.resourceOwnerFilterSet && formValues.resourceOwner) {
constructRequest.setResourceOwner(formValues.resourceOwner);
filterObject.resourceOwner = formValues.resourceOwner;
}
if (formValues.sequenceFilterSet && formValues.sequence) {
constructRequest.setSequence(formValues.sequence);
filterObject.sequence = formValues.sequence;
}
if (formValues.isAsc) {
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();
}
this.requestChanged.emit(constructRequest);
if (Object.keys(filterObject).length) {
this.router.navigate([], {
relativeTo: this.route,
queryParams: {
['filter']: JSON.stringify(filterObject),
},
replaceUrl: true,
queryParamsHandling: 'merge',
skipLocationChange: false,
});
} else {
this.router.navigate([], {
relativeTo: this.route,
replaceUrl: true,
skipLocationChange: false,
});
}
}
public get userFilterSet(): AbstractControl | null {
return this.form.get('userFilterSet');
}
public get aggregateFilterSet(): AbstractControl | null {
return this.form.get('aggregateFilterSet');
}
public get eventTypesFilterSet(): AbstractControl | null {
return this.form.get('eventTypesFilterSet');
}
public get sequence(): AbstractControl | null {
return this.form.get('sequence');
}
public get isAsc(): AbstractControl | null {
return this.form.get('isAsc');
}
public get sequenceFilterSet(): AbstractControl | null {
return this.form.get('sequenceFilterSet');
}
public get creationDate(): AbstractControl | null {
return this.form.get('creationDate');
}
public get creationDateFilterSet(): AbstractControl | null {
return this.form.get('creationDateFilterSet');
}
public get resourceOwnerFilterSet(): AbstractControl | null {
return this.form.get('resourceOwnerFilterSet');
}
public get resourceOwner(): AbstractControl | null {
return this.form.get('resourceOwner');
}
public get editorUserId(): AbstractControl | null {
return this.form.get('editorUserId');
}
public get aggregateId(): AbstractControl | null {
return this.form.get('aggregateId');
}
public get aggregateTypesList(): AbstractControl | null {
return this.form.get('aggregateTypesList');
}
public get eventTypesList(): AbstractControl | null {
return this.form.get('eventTypesList');
}
public get queryCount(): number {
let count = 0;
if (this.userFilterSet?.value && this.editorUserId?.value) {
++count;
}
if (this.creationDateFilterSet?.value && this.creationDate?.value) {
++count;
}
if (this.aggregateFilterSet?.value && this.aggregateId?.value) {
++count;
}
if (this.sequenceFilterSet?.value && this.sequence?.value) {
++count;
}
if (this.resourceOwnerFilterSet?.value && this.resourceOwner?.value) {
++count;
}
if (this.aggregateFilterSet?.value && this.aggregateTypesList?.value && this.aggregateTypesList.value.length) {
++count;
}
if (this.eventTypesFilterSet?.value && this.eventTypesList?.value && this.eventTypesList.value.length) {
++count;
}
return count;
}
}
@@ -0,0 +1,34 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
import { MatLegacySelectModule as MatSelectModule } from '@angular/material/legacy-select';
import { TranslateModule } from '@ngx-translate/core';
import { InputModule } from '../input/input.module';
import { FilterEventsComponent } from './filter-events.component';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatLegacyProgressSpinnerModule } from '@angular/material/legacy-progress-spinner';
import { MatLegacyCheckboxModule } from '@angular/material/legacy-checkbox';
import { ActionKeysModule } from '../action-keys/action-keys.module';
import { OverlayModule } from '@angular/cdk/overlay';
import { MatDatepickerModule } from '@angular/material/datepicker';
@NgModule({
declarations: [FilterEventsComponent],
imports: [
CommonModule,
FormsModule,
MatButtonModule,
InputModule,
ReactiveFormsModule,
OverlayModule,
MatDatepickerModule,
MatLegacyProgressSpinnerModule,
TranslateModule,
MatLegacyCheckboxModule,
MatSelectModule,
ActionKeysModule,
],
exports: [FilterEventsComponent],
})
export class FilterEventsModule {}
@@ -80,6 +80,7 @@ export class HeaderComponent implements OnDestroy {
'/instance',
'/settings',
'/views',
'/events',
'/orgs',
'/settings',
'/failed-events',
@@ -37,6 +37,17 @@
</div>
</a>
<a
class="nav-item"
[routerLinkActiveOptions]="{ exact: false }"
[routerLinkActive]="['active']"
[routerLink]="['/events']"
>
<div class="c_label">
<span> {{ 'MENU.EVENTS' | translate }} </span>
</div>
</a>
<a
class="nav-item"
[routerLinkActiveOptions]="{ exact: false }"
@@ -26,4 +26,10 @@
{{ 'PAGINATOR.NEXT' | translate }}
</button>
</div>
<div class="row" *ngIf="showMoreButton">
<button (click)="moreRequested.emit()" mat-stroked-button [disabled]="disableShowMore">
{{ 'PAGINATOR.MORE' | translate }}
</button>
</div>
</div>
@@ -1,4 +1,5 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { EventManager } from '@angular/platform-browser';
import { Timestamp } from 'src/app/proto/generated/google/protobuf/timestamp_pb';
export interface PageEvent {
@@ -20,6 +21,9 @@ export class PaginatorComponent {
@Input() public pageIndex: number = 0;
@Input() public pageSizeOptions: Array<number> = [10, 25, 50];
@Input() public hidePagination: boolean = false;
@Input() public showMoreButton: boolean = false;
@Input() public disableShowMore: boolean | null = false;
@Output() public moreRequested: EventEmitter<void> = new EventEmitter();
@Output() public page: EventEmitter<PageEvent> = new EventEmitter();
constructor() {}