Skip to content

Commit

Permalink
WIP: Event Metrics Log Frontend
Browse files Browse the repository at this point in the history
  • Loading branch information
pmachapman committed Jan 7, 2025
1 parent ebc4fe1 commit a82585f
Show file tree
Hide file tree
Showing 16 changed files with 617 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,18 @@ describe('SFProjectService', () => {
}));
});

describe('onlineEventMetrics', () => {
it('should invoke the command service', fakeAsync(async () => {
const env = new TestEnvironment();
const projectId = 'project01';
const pageIndex = 0;
const pageSize = 20;
await env.service.onlineEventMetrics(projectId, pageIndex, pageSize);
verify(mockedCommandService.onlineInvoke(anything(), 'eventMetrics', anything())).once();
expect().nothing();
}));
});

class TestEnvironment {
readonly httpTestingController: HttpTestingController;
readonly service: SFProjectService;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@ import { CommandService } from 'xforge-common/command.service';
import { LocationService } from 'xforge-common/location.service';
import { RealtimeQuery } from 'xforge-common/models/realtime-query';
import { ProjectService } from 'xforge-common/project.service';
import { QueryParameters } from 'xforge-common/query-parameters';
import { QueryParameters, QueryResults } from 'xforge-common/query-parameters';
import { RealtimeService } from 'xforge-common/realtime.service';
import { RetryingRequest, RetryingRequestService } from 'xforge-common/retrying-request.service';
import { TransceleratorQuestion } from '../checking/import-questions-dialog/import-questions-dialog.component';
import { EventMetric } from '../event-metrics/event-metric';
import { ShareLinkType } from '../shared/share/share-dialog.component';
import { InviteeStatus } from '../users/collaborators/collaborators.component';
import { BiblicalTermDoc } from './models/biblical-term-doc';
Expand Down Expand Up @@ -309,4 +310,8 @@ export class SFProjectService extends ProjectService<SFProject, SFProjectDoc> {
isValid
});
}

async onlineEventMetrics(projectId: string, pageIndex: number, pageSize: number): Promise<QueryResults<EventMetric>> {
return await this.onlineInvoke<QueryResults<EventMetric>>('eventMetrics', { projectId, pageIndex, pageSize });
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<ng-container *transloco="let t; read: 'event_metric_dialog'">
<mat-dialog [dir]="i18n.direction">
<h2 mat-dialog-title>
{{ t("technical_details") }}
</h2>
<mat-dialog-content>
<h3>{{ t("event_type") }}</h3>
<div>
<pre>{{ data.eventType }}</pre>
</div>
<h3>{{ t("parameters") }}</h3>
<div>
<pre>{{ data.payload | json }}</pre>
</div>
@if (data.result != null) {
<h3>{{ t("result") }}</h3>
<div>
<pre>{{ data.result }}</pre>
</div>
}
@if (data.exception != null) {
<h3>{{ t("exception") }}</h3>
<div>
<pre>{{ data.exception }}</pre>
</div>
}
</mat-dialog-content>
<mat-dialog-actions [align]="'end'">
<button mat-button id="close-btn" [mat-dialog-close]="'cancel'">{{ t("close") }}</button>
</mat-dialog-actions>
</mat-dialog>
</ng-container>
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { NgModule } from '@angular/core';
import { ComponentFixture, fakeAsync, flush, inject, TestBed, tick } from '@angular/core/testing';
import { MatDialog, MatDialogConfig, MatDialogRef } from '@angular/material/dialog';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { ChildViewContainerComponent, configureTestingModule, TestTranslocoModule } from 'xforge-common/test-utils';
import { UICommonModule } from 'xforge-common/ui-common.module';
import { EventMetricDialogComponent } from './event-metric-dialog.component';

describe('EventMetricDialogComponent', () => {
configureTestingModule(() => ({
imports: [DialogTestModule, NoopAnimationsModule, UICommonModule]
}));

let dialog: MatDialog;
let viewContainerFixture: ComponentFixture<ChildViewContainerComponent>;

it('should allow user to close', fakeAsync(() => {
const env = new TestEnvironment();
env.clickElement(env.closeButton);
flush();
expect(env.afterCloseCallback).toHaveBeenCalledWith('cancel');
}));

class TestEnvironment {
fixture: ComponentFixture<ChildViewContainerComponent>;
component: EventMetricDialogComponent;
dialogRef: MatDialogRef<EventMetricDialogComponent>;

afterCloseCallback: jasmine.Spy;

constructor() {
this.afterCloseCallback = jasmine.createSpy('afterClose callback');
const config: MatDialogConfig = { data: { name: 'project01' } };
this.dialogRef = dialog.open(EventMetricDialogComponent, config);
this.dialogRef.afterClosed().subscribe(this.afterCloseCallback);
this.component = this.dialogRef.componentInstance;
this.fixture = viewContainerFixture;
this.fixture.detectChanges();
}

get overlayContainerElement(): HTMLElement {
return this.fixture.nativeElement.parentElement.querySelector('.cdk-overlay-container');
}

get closeButton(): HTMLElement {
return this.overlayContainerElement.querySelector('#close-btn') as HTMLElement;
}

clickElement(element: HTMLElement): void {
element.click();
this.fixture.detectChanges();
tick();
}
}

beforeEach(inject([MatDialog], (d: MatDialog) => {
dialog = d;
}));

beforeEach(() => {
viewContainerFixture = TestBed.createComponent(ChildViewContainerComponent);
});
});

@NgModule({
exports: [EventMetricDialogComponent],
imports: [EventMetricDialogComponent, TestTranslocoModule, UICommonModule],
providers: [provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting()]
})
class DialogTestModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { CommonModule } from '@angular/common';
import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
import { TranslocoModule } from '@ngneat/transloco';
import { I18nService } from 'xforge-common/i18n.service';
import { UICommonModule } from 'xforge-common/ui-common.module';
import { EventMetric } from './event-metric';

@Component({
selector: 'app-event-metric-dialog',
templateUrl: './event-metric-dialog.component.html',
styleUrls: ['./event-metric-dialog.component.scss'],
standalone: true,
imports: [CommonModule, MatDialogModule, TranslocoModule, UICommonModule]
})
export class EventMetricDialogComponent {
constructor(
readonly i18n: I18nService,
@Inject(MAT_DIALOG_DATA) public data: EventMetric
) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export interface EventMetric {
eventType: string;
exception?: string;
id: string;
payload: { [key: string]: any };
projectId?: string;
result?: any;
scope: EventScope;
timeStamp: string;
userId?: string;
}

export enum EventScope {
None = 'None',
Settings = 'Settings',
Sync = 'Sync',
Drafting = 'Drafting',
Checking = 'Checking'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<ng-container *transloco="let t; read: 'event_metrics'">
@if (!isLoading) {
@if (length > 0) {
<table mat-table id="event-metrics-log-table" [dataSource]="rows">
<tr mat-header-row *matHeaderRowDef="columnsToDisplay"></tr>
<tr mat-row *matRowDef="let row; columns: columnsToDisplay"></tr>
<ng-container matColumnDef="scope">
<th mat-header-cell *matHeaderCellDef>{{ t("scope") }}</th>
<td mat-cell *matCellDef="let row">
{{ row.scope }}
</td>
</ng-container>
<ng-container matColumnDef="eventType">
<th mat-header-cell *matHeaderCellDef>{{ t("event") }}</th>
<td mat-cell *matCellDef="let row">
{{ row.eventType }}
</td>
</ng-container>
<ng-container matColumnDef="successful">
<th mat-header-cell *matHeaderCellDef>{{ t("status") }}</th>
<td mat-cell *matCellDef="let row">
{{ t(row.successful ? "successful" : "failed") }}
</td>
</ng-container>
<ng-container matColumnDef="author">
<th mat-header-cell *matHeaderCellDef>{{ t("author") }}</th>
<td mat-cell *matCellDef="let row">
<app-owner
[ownerRef]="row.userId"
[includeAvatar]="true"
[layoutStacked]="false"
[dateTime]="row.timeStamp"
></app-owner>
</td>
</ng-container>
<ng-container matColumnDef="details">
<th mat-header-cell *matHeaderCellDef>{{ t("technical_details") }}</th>
<td mat-cell *matCellDef="let row">
<button
mat-flat-button
(click)="openDetailsDialog(row.dialogData)"
color="primary"
class="button-with-text"
>
<mat-icon class="mirror-rtl">open_in_new</mat-icon> {{ t("technical_details") }}
</button>
</td>
</ng-container>
</table>
<mat-paginator
[pageIndex]="pageIndex"
[length]="length"
[pageSize]="pageSize"
[pageSizeOptions]="[5, 10, 20, 50, 100]"
(page)="updatePage($event.pageIndex, $event.pageSize)"
>
</mat-paginator>
} @else if (length === 0) {
<div class="no-event-metrics-label">{{ t("not_found") }}</div>
}
}
</ng-container>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.no-event-metrics-label {
padding-top: 14px;
}
Loading

0 comments on commit a82585f

Please sign in to comment.