1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-24 16:43:27 +00:00

[PM-32372] Added testid for table and then fixed tech debt (#19066)

This commit is contained in:
Vijay Oommen
2026-02-20 10:17:08 -06:00
committed by GitHub
parent e82669b999
commit a7c74c6f76
7 changed files with 215 additions and 191 deletions

View File

@@ -1,6 +1,6 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Directive, OnDestroy } from "@angular/core";
import { Directive, OnDestroy, signal } from "@angular/core";
import { FormControl, FormGroup } from "@angular/forms";
import { ActivatedRoute } from "@angular/router";
import { combineLatest, filter, map, Observable, Subject, switchMap, takeUntil } from "rxjs";
@@ -22,9 +22,9 @@ import { EventExportService } from "../../tools/event-export";
@Directive()
export abstract class BaseEventsComponent implements OnDestroy {
loading = true;
loaded = false;
events: EventView[];
readonly loading = signal(true);
readonly loaded = signal(false);
readonly events = signal<EventView[]>([]);
dirtyDates = true;
continuationToken: string;
canUseSM = false;
@@ -115,7 +115,7 @@ export abstract class BaseEventsComponent implements OnDestroy {
return;
}
this.loading = true;
this.loading.set(true);
const dates = this.parseDates();
if (dates == null) {
@@ -131,7 +131,7 @@ export abstract class BaseEventsComponent implements OnDestroy {
}
promise = null;
this.loading = false;
this.loading.set(false);
};
loadEvents = async (clearExisting: boolean) => {
@@ -140,7 +140,7 @@ export abstract class BaseEventsComponent implements OnDestroy {
return;
}
this.loading = true;
this.loading.set(true);
let events: EventView[] = [];
let promise: Promise<any>;
promise = this.loadAndParseEvents(
@@ -153,14 +153,16 @@ export abstract class BaseEventsComponent implements OnDestroy {
this.continuationToken = result.continuationToken;
events = result.events;
if (!clearExisting && this.events != null && this.events.length > 0) {
this.events = this.events.concat(events);
if (!clearExisting && this.events() != null && this.events().length > 0) {
this.events.update((current) => {
return [...current, ...events];
});
} else {
this.events = events;
this.events.set(events);
}
this.dirtyDates = false;
this.loading = false;
this.loading.set(false);
promise = null;
};
@@ -227,7 +229,7 @@ export abstract class BaseEventsComponent implements OnDestroy {
private async export(start: string, end: string) {
let continuationToken = this.continuationToken;
let events = [].concat(this.events);
let events = [].concat(this.events());
while (continuationToken != null) {
const result = await this.loadAndParseEvents(start, end, continuationToken);

View File

@@ -1,14 +1,16 @@
@let usePlaceHolderEvents = !organization?.useEvents;
<app-header>
<span
bitBadge
variant="primary"
slot="title-suffix"
class="tw-ml-2 tw-mt-1.5 tw-inline-flex tw-items-center"
*ngIf="usePlaceHolderEvents"
>
{{ "upgrade" | i18n }}
</span>
@if (usePlaceHolderEvents) {
<span
bitBadge
variant="primary"
slot="title-suffix"
class="tw-ml-2 tw-mt-1.5 tw-inline-flex tw-items-center"
>
{{ "upgrade" | i18n }}
</span>
}
</app-header>
<div class="tw-mb-4" [formGroup]="eventsForm">
<div class="tw-mt-4 tw-flex tw-items-center">
@@ -61,79 +63,87 @@
</form>
</div>
</div>
<bit-callout
type="info"
[title]="'upgradeEventLogTitleMessage' | i18n"
*ngIf="loaded && usePlaceHolderEvents"
>
{{ "upgradeEventLogMessage" | i18n }}
</bit-callout>
<ng-container *ngIf="!loaded">
<i
class="bwi bwi-spinner bwi-spin tw-text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
</ng-container>
<ng-container *ngIf="loaded">
@let displayedEvents = organization?.useEvents ? events : placeholderEvents;
@if (loaded() && usePlaceHolderEvents) {
<bit-callout type="info" [title]="'upgradeEventLogTitleMessage' | i18n">
{{ "upgradeEventLogMessage" | i18n }}
</bit-callout>
}
<p *ngIf="!displayedEvents || !displayedEvents.length">{{ "noEventsInList" | i18n }}</p>
<bit-table *ngIf="displayedEvents && displayedEvents.length">
<ng-container header>
<tr>
<th bitCell>{{ "timestamp" | i18n }}</th>
<th bitCell>{{ "client" | i18n }}</th>
<th bitCell>{{ "member" | i18n }}</th>
<th bitCell>{{ "event" | i18n }}</th>
</tr>
</ng-container>
<ng-template body>
<tr bitRow *ngFor="let e of displayedEvents; index as i" alignContent="top">
<td bitCell class="tw-whitespace-nowrap">
{{ i > 4 && usePlaceHolderEvents ? "******" : (e.date | date: "medium") }}
</td>
<td bitCell>
<span title="{{ e.appName }}, {{ e.ip }}">{{ e.appName }}</span>
</td>
<td bitCell>
<span title="{{ e.userEmail }}">{{ e.userName }}</span>
</td>
<td bitCell [innerHTML]="e.message"></td>
</tr>
</ng-template>
</bit-table>
<button
type="button"
bitButton
buttonType="primary"
[bitAction]="loadMoreEvents"
*ngIf="continuationToken"
>
{{ "loadMore" | i18n }}
</button>
</ng-container>
@if (!loaded()) {
<ng-container>
<bit-icon
class="bwi-lg bwi-spin tw-text-muted"
name="bwi-spinner"
aria-hidden="true"
></bit-icon>
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
</ng-container>
}
@if (loaded()) {
<ng-container>
@let displayedEvents = organization?.useEvents ? events() : placeholderEvents;
<ng-container *ngIf="loaded && usePlaceHolderEvents">
<div
class="tw-relative tw--top-72 tw-bg-background tw-bg-opacity-90 tw-pb-5 tw-flex tw-items-center tw-justify-center tw-h-[19rem]"
>
<div
class="tw-bg-background tw-max-w-xl tw-flex-col tw-justify-center tw-text-center tw-p-5 tw-px-10 tw-rounded tw-border-0 tw-border-b tw-border-secondary-300 tw-border-solid tw-mt-5"
>
<i class="bwi bwi-2x bwi-business tw-text-primary-600"></i>
@if (!displayedEvents || !displayedEvents.length) {
<p>{{ "noEventsInList" | i18n }}</p>
}
<p class="tw-font-medium tw-mt-2">
{{ "upgradeEventLogTitleMessage" | i18n }}
</p>
<p>
{{ "upgradeForFullEventsMessage" | i18n }}
</p>
<button type="button" class="tw-mt-1" bitButton buttonType="primary" (click)="changePlan()">
{{ "changeBillingPlan" | i18n }}
@if (displayedEvents && displayedEvents.length) {
<bit-table data-testid="events-table">
<ng-container header>
<tr>
<th bitCell>{{ "timestamp" | i18n }}</th>
<th bitCell>{{ "client" | i18n }}</th>
<th bitCell>{{ "member" | i18n }}</th>
<th bitCell>{{ "event" | i18n }}</th>
</tr>
</ng-container>
<ng-template body>
@for (e of displayedEvents; track i; let i = $index) {
<tr bitRow alignContent="top">
<td bitCell class="tw-whitespace-nowrap">
{{ i > 4 && usePlaceHolderEvents ? "******" : (e.date | date: "medium") }}
</td>
<td bitCell>
<span title="{{ e.appName }}, {{ e.ip }}">{{ e.appName }}</span>
</td>
<td bitCell>
<span title="{{ e.userEmail }}">{{ e.userName }}</span>
</td>
<td bitCell [innerHTML]="e.message"></td>
</tr>
}
</ng-template>
</bit-table>
}
@if (continuationToken) {
<button type="button" bitButton buttonType="primary" [bitAction]="loadMoreEvents">
{{ "loadMore" | i18n }}
</button>
}
</ng-container>
}
@if (loaded() && usePlaceHolderEvents) {
<ng-container>
<div
class="tw-relative tw--top-72 tw-bg-background tw-bg-opacity-90 tw-pb-5 tw-flex tw-items-center tw-justify-center tw-h-[19rem]"
>
<div
class="tw-bg-background tw-max-w-xl tw-flex-col tw-justify-center tw-text-center tw-p-5 tw-px-10 tw-rounded tw-border-0 tw-border-b tw-border-secondary-300 tw-border-solid tw-mt-5"
>
<i class="bwi bwi-2x bwi-business tw-text-primary-600"></i>
<p class="tw-font-medium tw-mt-2">
{{ "upgradeEventLogTitleMessage" | i18n }}
</p>
<p>
{{ "upgradeForFullEventsMessage" | i18n }}
</p>
<button type="button" class="tw-mt-1" bitButton buttonType="primary" (click)="changePlan()">
{{ "changeBillingPlan" | i18n }}
</button>
</div>
</div>
</div>
</ng-container>
</ng-container>
}

View File

@@ -1,6 +1,6 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Component, OnDestroy, OnInit } from "@angular/core";
import { Component, OnDestroy, OnInit, ChangeDetectionStrategy } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { concatMap, firstValueFrom, lastValueFrom, map, of, switchMap, takeUntil, tap } from "rxjs";
@@ -47,9 +47,8 @@ const EVENT_SYSTEM_USER_TO_TRANSLATION: Record<EventSystemUser, string> = {
[EventSystemUser.BitwardenPortal]: "system",
};
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
templateUrl: "events.component.html",
imports: [SharedModule, HeaderModule],
})
@@ -168,7 +167,7 @@ export class EventsComponent extends BaseEventsComponent implements OnInit, OnDe
}
}
await this.refreshEvents();
this.loaded = true;
this.loaded.set(true);
}
protected requestEvents(startDate: string, endDate: string, continuationToken: string) {

View File

@@ -50,56 +50,64 @@
</form>
</div>
</div>
<ng-container *ngIf="!loaded">
<i
class="bwi bwi-spinner bwi-spin tw-text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
</ng-container>
<ng-container *ngIf="loaded">
<p *ngIf="!events || !events.length">{{ "noEventsInList" | i18n }}</p>
<bit-table *ngIf="events && events.length">
<ng-container header>
<tr>
<th bitCell>{{ "timestamp" | i18n }}</th>
<th bitCell>{{ "device" | i18n }}</th>
<th bitCell>{{ "user" | i18n }}</th>
<th bitCell>{{ "event" | i18n }}</th>
</tr>
</ng-container>
<ng-template body>
<tr bitRow *ngFor="let e of events" alignContent="top">
<td bitCell class="tw-whitespace-nowrap">{{ e.date | date: "medium" }}</td>
<td bitCell>
<i
class="tw-text-muted bwi bwi-lg {{ e.appIcon }}"
title="{{ e.appName }}, {{ e.ip }}"
aria-hidden="true"
></i>
<span class="tw-sr-only">{{ e.appName }}, {{ e.ip }}</span>
</td>
<td bitCell>
<span title="{{ e.userEmail }}">{{ e.userName }}</span>
</td>
<td bitCell [innerHTML]="e.message"></td>
</tr>
</ng-template>
</bit-table>
<button
bitButton
type="button"
buttonType="primary"
[bitAction]="loadMoreEvents"
*ngIf="continuationToken"
>
<i
*ngIf="loading"
class="bwi bwi-spinner bwi-spin"
title="{{ 'loading' | i18n }}"
@if (!loaded()) {
<ng-container>
<bit-icon
class="bwi-lg bwi-spin tw-text-muted"
name="bwi-spinner"
aria-hidden="true"
></i>
<span>{{ "loadMore" | i18n }}</span>
</button>
</ng-container>
></bit-icon>
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
</ng-container>
}
@if (loaded()) {
<ng-container>
@if (!events() || !events().length) {
<p>{{ "noEventsInList" | i18n }}</p>
}
@if (events() && events().length) {
<bit-table data-testid="events-table">
<ng-container header>
<tr>
<th bitCell>{{ "timestamp" | i18n }}</th>
<th bitCell>{{ "device" | i18n }}</th>
<th bitCell>{{ "user" | i18n }}</th>
<th bitCell>{{ "event" | i18n }}</th>
</tr>
</ng-container>
<ng-template body>
@for (e of events(); track i; let i = $index) {
<tr bitRow alignContent="top">
<td bitCell class="tw-whitespace-nowrap">{{ e.date | date: "medium" }}</td>
<td bitCell>
<i
class="tw-text-muted bwi bwi-lg {{ e.appIcon }}"
title="{{ e.appName }}, {{ e.ip }}"
aria-hidden="true"
></i>
<span class="tw-sr-only">{{ e.appName }}, {{ e.ip }}</span>
</td>
<td bitCell>
<span title="{{ e.userEmail }}">{{ e.userName }}</span>
</td>
<td bitCell [innerHTML]="e.message"></td>
</tr>
}
</ng-template>
</bit-table>
}
@if (continuationToken) {
<button bitButton type="button" buttonType="primary" [bitAction]="loadMoreEvents">
@if (loading()) {
<bit-icon
class="bwi-lg bwi-spin tw-text-muted"
name="bwi-spinner"
aria-hidden="true"
></bit-icon>
}
<span>{{ "loadMore" | i18n }}</span>
</button>
}
</ng-container>
}

View File

@@ -94,7 +94,7 @@ export class EventsComponent extends BaseEventsComponent implements OnInit {
this.providerUsersUserIdMap.set(u.userId, { name: name, email: u.email });
});
await this.refreshEvents();
this.loaded = true;
this.loaded.set(true);
}
protected requestEvents(startDate: string, endDate: string, continuationToken: string) {

View File

@@ -47,41 +47,47 @@
</form>
</div>
</div>
<ng-container *ngIf="!loaded">
<i
class="bwi bwi-spinner bwi-spin tw-text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
</ng-container>
<ng-container *ngIf="loaded">
<p *ngIf="!events || !events.length">{{ "noEventsInList" | i18n }}</p>
<bit-table *ngIf="events && events.length">
<ng-container header>
<tr>
<th bitCell>{{ "timestamp" | i18n }}</th>
<th bitCell>{{ "client" | i18n }}</th>
<th bitCell>{{ "event" | i18n }}</th>
</tr>
</ng-container>
<ng-template body>
<tr bitRow *ngFor="let e of events" alignContent="top">
<td bitCell class="tw-whitespace-nowrap">{{ e.date | date: "medium" }}</td>
<td bitCell>
<span title="{{ e.appName }}, {{ e.ip }}">{{ e.appName }}</span>
</td>
<td bitCell [innerHTML]="e.message"></td>
</tr>
</ng-template>
</bit-table>
<button
type="button"
bitButton
buttonType="primary"
[bitAction]="loadMoreEvents"
*ngIf="continuationToken"
>
<span>{{ "loadMore" | i18n }}</span>
</button>
</ng-container>
@if (!loaded()) {
<ng-container>
<bit-icon
class="bwi-lg bwi-spin tw-text-muted"
name="bwi-spinner"
aria-hidden="true"
></bit-icon>
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
</ng-container>
}
@if (loaded()) {
<ng-container>
@if (!events() || !events().length) {
<p>{{ "noEventsInList" | i18n }}</p>
}
@if (events() && events().length) {
<bit-table data-testid="events-table">
<ng-container header>
<tr>
<th bitCell>{{ "timestamp" | i18n }}</th>
<th bitCell>{{ "client" | i18n }}</th>
<th bitCell>{{ "event" | i18n }}</th>
</tr>
</ng-container>
<ng-template body>
@for (e of events(); track i; let i = $index) {
<tr bitRow alignContent="top">
<td bitCell class="tw-whitespace-nowrap">{{ e.date | date: "medium" }}</td>
<td bitCell>
<span title="{{ e.appName }}, {{ e.ip }}">{{ e.appName }}</span>
</td>
<td bitCell [innerHTML]="e.message"></td>
</tr>
}
</ng-template>
</bit-table>
}
@if (continuationToken) {
<button type="button" bitButton buttonType="primary" [bitAction]="loadMoreEvents">
<span>{{ "loadMore" | i18n }}</span>
</button>
}
</ng-container>
}

View File

@@ -1,6 +1,6 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Component, OnDestroy, OnInit } from "@angular/core";
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { takeUntil } from "rxjs";
@@ -17,9 +17,8 @@ import { EventExportService } from "@bitwarden/web-vault/app/tools/event-export"
import { ServiceAccountEventLogApiService } from "./service-account-event-log-api.service";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: "sm-service-accounts-events",
templateUrl: "./service-accounts-events.component.html",
standalone: false,
@@ -69,7 +68,7 @@ export class ServiceAccountEventsComponent
async load() {
await this.refreshEvents();
this.loaded = true;
this.loaded.set(true);
}
protected requestEvents(startDate: string, endDate: string, continuationToken: string) {