refactor event detail
This commit is contained in:
1 parent
1af2540a4c
commit
dc833b2eb2
17 files changed
+83
-102
No files matched your search
Vendored
+28
-20
@@ -2754,15 +2754,15 @@ export interface components {
|
||||
/** Groupid */
|
||||
groupID: string;
|
||||
/**
|
||||
* Timestamp
|
||||
* Datecreated
|
||||
* Format: date-time
|
||||
*/
|
||||
timestamp: string;
|
||||
dateCreated: string;
|
||||
/**
|
||||
* Received
|
||||
* Datereceived
|
||||
* Format: date-time
|
||||
*/
|
||||
received: string;
|
||||
dateReceived: string;
|
||||
/** Dist */
|
||||
dist?: string | null;
|
||||
/** Transaction */
|
||||
@@ -2788,16 +2788,20 @@ export interface components {
|
||||
}[];
|
||||
/** Entries */
|
||||
entries?: (components["schemas"]["BreadcrumbsEntry"] | components["schemas"]["CSPEntry"] | components["schemas"]["ExceptionEntry"] | components["schemas"]["MessageEntry"] | components["schemas"]["RequestEntry"])[];
|
||||
/** Data.Contexts */
|
||||
"data.contexts"?: {
|
||||
/** Contexts */
|
||||
contexts?: {
|
||||
[key: string]: (components["schemas"]["DeviceContext"] | components["schemas"]["OSContext"] | components["schemas"]["RuntimeContext"] | components["schemas"]["AppContext"] | components["schemas"]["BrowserContext"] | components["schemas"]["GPUContext"] | components["schemas"]["StateContext"] | components["schemas"]["CultureContext"] | components["schemas"]["CloudResourceContext"] | components["schemas"]["TraceContext"] | components["schemas"]["ReplayContext"] | components["schemas"]["ResponseContext"]) | unknown;
|
||||
} | null;
|
||||
/** Data.Extra */
|
||||
"data.extra"?: {
|
||||
/** Context */
|
||||
context?: {
|
||||
[key: string]: unknown;
|
||||
} | null;
|
||||
/** User */
|
||||
user?: unknown | null;
|
||||
/** Sdk */
|
||||
sdk?: {
|
||||
[key: string]: unknown;
|
||||
} | null;
|
||||
/** Data.User */
|
||||
"data.user"?: unknown | null;
|
||||
/** Title */
|
||||
title: string;
|
||||
};
|
||||
@@ -2866,15 +2870,15 @@ export interface components {
|
||||
/** Groupid */
|
||||
groupID: string;
|
||||
/**
|
||||
* Timestamp
|
||||
* Datecreated
|
||||
* Format: date-time
|
||||
*/
|
||||
timestamp: string;
|
||||
dateCreated: string;
|
||||
/**
|
||||
* Received
|
||||
* Datereceived
|
||||
* Format: date-time
|
||||
*/
|
||||
received: string;
|
||||
dateReceived: string;
|
||||
/** Dist */
|
||||
dist?: string | null;
|
||||
/** Transaction */
|
||||
@@ -2900,16 +2904,20 @@ export interface components {
|
||||
}[];
|
||||
/** Entries */
|
||||
entries?: (components["schemas"]["BreadcrumbsEntry"] | components["schemas"]["CSPEntry"] | components["schemas"]["ExceptionEntry"] | components["schemas"]["MessageEntry"] | components["schemas"]["RequestEntry"])[];
|
||||
/** Data.Contexts */
|
||||
"data.contexts"?: {
|
||||
/** Contexts */
|
||||
contexts?: {
|
||||
[key: string]: (components["schemas"]["DeviceContext"] | components["schemas"]["OSContext"] | components["schemas"]["RuntimeContext"] | components["schemas"]["AppContext"] | components["schemas"]["BrowserContext"] | components["schemas"]["GPUContext"] | components["schemas"]["StateContext"] | components["schemas"]["CultureContext"] | components["schemas"]["CloudResourceContext"] | components["schemas"]["TraceContext"] | components["schemas"]["ReplayContext"] | components["schemas"]["ResponseContext"]) | unknown;
|
||||
} | null;
|
||||
/** Data.Extra */
|
||||
"data.extra"?: {
|
||||
/** Context */
|
||||
context?: {
|
||||
[key: string]: unknown;
|
||||
} | null;
|
||||
/** User */
|
||||
user?: unknown | null;
|
||||
/** Sdk */
|
||||
sdk?: {
|
||||
[key: string]: unknown;
|
||||
} | null;
|
||||
/** Data.User */
|
||||
"data.user"?: unknown | null;
|
||||
/** Title */
|
||||
title: string;
|
||||
userReport: components["schemas"]["UserReportSchema"] | null;
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<section class="grid-wrapper">
|
||||
@for (
|
||||
contexts of specialContexts$ | async;
|
||||
track contexts.title + contexts.type
|
||||
) {
|
||||
@for (contexts of specialContexts(); track contexts.title + contexts.type) {
|
||||
<div class="wrapper">
|
||||
@if (contexts.icon || contexts.matIcon) {
|
||||
@if (contexts.icon) {
|
||||
|
||||
@@ -1,26 +1,16 @@
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
ChangeDetectionStrategy,
|
||||
inject,
|
||||
} from "@angular/core";
|
||||
import { Component, ChangeDetectionStrategy, inject } from "@angular/core";
|
||||
import { IssueDetailService } from "../../issue-detail.service";
|
||||
import { MatIconModule } from "@angular/material/icon";
|
||||
import { AsyncPipe } from "@angular/common";
|
||||
|
||||
@Component({
|
||||
selector: "gt-contexts",
|
||||
templateUrl: "./contexts.component.html",
|
||||
styleUrls: ["./contexts.component.scss"],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [AsyncPipe, MatIconModule],
|
||||
imports: [MatIconModule],
|
||||
})
|
||||
export class ContextsComponent implements OnInit {
|
||||
export class ContextsComponent {
|
||||
private issueDetailService = inject(IssueDetailService);
|
||||
|
||||
specialContexts$ = this.issueDetailService.specialContexts$;
|
||||
|
||||
ngOnInit() {
|
||||
this.issueDetailService.specialContexts$.subscribe();
|
||||
}
|
||||
specialContexts = this.issueDetailService.specialContexts;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
@if (eventEntryCSP$ | async; as csp) {
|
||||
@if (eventEntryCSP(); as csp) {
|
||||
<section class="mat-typography">
|
||||
<mat-divider></mat-divider>
|
||||
<div class="csp__header">
|
||||
|
||||
@@ -3,7 +3,7 @@ import { IssueDetailService } from "../../issue-detail.service";
|
||||
import { EntryDataComponent } from "../../../../shared/entry-data/entry-data.component";
|
||||
import { MatButtonToggleModule } from "@angular/material/button-toggle";
|
||||
import { MatDividerModule } from "@angular/material/divider";
|
||||
import { AsyncPipe, JsonPipe, KeyValuePipe } from "@angular/common";
|
||||
import { JsonPipe, KeyValuePipe } from "@angular/common";
|
||||
|
||||
@Component({
|
||||
selector: "gt-entry-csp",
|
||||
@@ -14,7 +14,6 @@ import { AsyncPipe, JsonPipe, KeyValuePipe } from "@angular/common";
|
||||
MatDividerModule,
|
||||
MatButtonToggleModule,
|
||||
EntryDataComponent,
|
||||
AsyncPipe,
|
||||
JsonPipe,
|
||||
KeyValuePipe,
|
||||
],
|
||||
@@ -22,5 +21,5 @@ import { AsyncPipe, JsonPipe, KeyValuePipe } from "@angular/common";
|
||||
export class EntryCSPComponent {
|
||||
private issueService = inject(IssueDetailService);
|
||||
|
||||
eventEntryCSP$ = this.issueService.eventEntryCSP$;
|
||||
eventEntryCSP = this.issueService.eventEntryCSP;
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
@if (eventEntryException$ | async; as exception) {
|
||||
@if (eventEntryException(); as exception) {
|
||||
<section class="mat-typography">
|
||||
<section class="exception">
|
||||
<div class="header-wrapper">
|
||||
|
||||
+1
-3
@@ -13,7 +13,6 @@ import { MatExpansionModule } from "@angular/material/expansion";
|
||||
import { MatChipsModule } from "@angular/material/chips";
|
||||
import { MatButtonToggleModule } from "@angular/material/button-toggle";
|
||||
import { MatButtonModule } from "@angular/material/button";
|
||||
import { AsyncPipe } from "@angular/common";
|
||||
|
||||
@Component({
|
||||
selector: "gt-entry-exception",
|
||||
@@ -28,7 +27,6 @@ import { AsyncPipe } from "@angular/common";
|
||||
FrameTitleComponent,
|
||||
FrameExpandedComponent,
|
||||
RawStacktraceComponent,
|
||||
AsyncPipe,
|
||||
],
|
||||
})
|
||||
export class EntryExceptionComponent {
|
||||
@@ -36,7 +34,7 @@ export class EntryExceptionComponent {
|
||||
|
||||
readonly eventTitle = input<string>();
|
||||
readonly eventPlatform = input<string>();
|
||||
eventEntryException$ = this.issueService.eventEntryException$;
|
||||
eventEntryException = this.issueService.eventEntryException;
|
||||
isReversed = this.issueService.isReversed;
|
||||
|
||||
checkStacktraceInterface = isStacktrace;
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
@for (value of rawStacktraceValues$ | async; track value) {
|
||||
@for (value of rawStacktraceValues(); track value) {
|
||||
<div class="wrapper">
|
||||
<p>
|
||||
@if (eventPlatform() === "java") {
|
||||
|
||||
+1
-3
@@ -6,20 +6,18 @@ import {
|
||||
} from "@angular/core";
|
||||
import { isStacktrace } from "src/app/issues/utils";
|
||||
import { IssueDetailService } from "../../../issue-detail.service";
|
||||
import { AsyncPipe } from "@angular/common";
|
||||
|
||||
@Component({
|
||||
selector: "gt-raw-stacktrace",
|
||||
templateUrl: "./raw-stacktrace.component.html",
|
||||
styleUrls: ["./raw-stacktrace.component.scss"],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [AsyncPipe],
|
||||
})
|
||||
export class RawStacktraceComponent {
|
||||
private issueService = inject(IssueDetailService);
|
||||
|
||||
readonly eventPlatform = input<string | null>();
|
||||
rawStacktraceValues$ = this.issueService.rawStacktraceValues$;
|
||||
rawStacktraceValues = this.issueService.rawStacktraceValues;
|
||||
|
||||
checkStacktraceInterface = isStacktrace;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
@if (eventEntryMessage$ | async; as message) {
|
||||
@if (eventEntryMessage(); as message) {
|
||||
<section class="mat-typography">
|
||||
<div>
|
||||
<mat-divider></mat-divider>
|
||||
|
||||
@@ -2,17 +2,17 @@ import { Component, ChangeDetectionStrategy, inject } from "@angular/core";
|
||||
import { IssueDetailService } from "../../issue-detail.service";
|
||||
import { EntryDataComponent } from "../../../../shared/entry-data/entry-data.component";
|
||||
import { MatDividerModule } from "@angular/material/divider";
|
||||
import { AsyncPipe, KeyValuePipe } from "@angular/common";
|
||||
import { KeyValuePipe } from "@angular/common";
|
||||
|
||||
@Component({
|
||||
selector: "gt-entry-message",
|
||||
templateUrl: "./entry-message.component.html",
|
||||
styleUrls: ["./entry-message.component.scss"],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [MatDividerModule, EntryDataComponent, AsyncPipe, KeyValuePipe],
|
||||
imports: [MatDividerModule, EntryDataComponent, KeyValuePipe],
|
||||
})
|
||||
export class EntryMessageComponent {
|
||||
private issueService = inject(IssueDetailService);
|
||||
|
||||
eventEntryMessage$ = this.issueService.eventEntryMessage$;
|
||||
eventEntryMessage = this.issueService.eventEntryMessage;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
@if (eventEntryRequest$ | async; as request) {
|
||||
@if (eventEntryRequest(); as request) {
|
||||
<section class="mat-typography">
|
||||
<mat-divider></mat-divider>
|
||||
<section class="location">
|
||||
|
||||
@@ -3,7 +3,7 @@ import { IssueDetailService } from "../../issue-detail.service";
|
||||
import { EntryDataComponent } from "../../../../shared/entry-data/entry-data.component";
|
||||
import { MatTooltipModule } from "@angular/material/tooltip";
|
||||
import { MatDividerModule } from "@angular/material/divider";
|
||||
import { AsyncPipe, KeyValuePipe } from "@angular/common";
|
||||
import { KeyValuePipe } from "@angular/common";
|
||||
|
||||
@Component({
|
||||
selector: "gt-entry-request",
|
||||
@@ -14,12 +14,11 @@ import { AsyncPipe, KeyValuePipe } from "@angular/common";
|
||||
MatDividerModule,
|
||||
MatTooltipModule,
|
||||
EntryDataComponent,
|
||||
AsyncPipe,
|
||||
KeyValuePipe,
|
||||
],
|
||||
})
|
||||
export class EntryRequestComponent {
|
||||
private issueService = inject(IssueDetailService);
|
||||
|
||||
eventEntryRequest$ = this.issueService.eventEntryRequest$;
|
||||
eventEntryRequest = this.issueService.eventEntryRequest;
|
||||
}
|
||||
@@ -64,8 +64,7 @@
|
||||
@for (tag of event.tags; track tag.key) {
|
||||
<li class="pseudo-chip">
|
||||
<a
|
||||
[routerLink]="['..']"
|
||||
[relativeTo]="route.parent"
|
||||
[routerLink]="['/', orgSlug(), 'issues']"
|
||||
[queryParams]="generateQuery(tag)"
|
||||
queryParamsHandling="merge"
|
||||
>
|
||||
@@ -80,17 +79,17 @@
|
||||
<gt-entry-message></gt-entry-message>
|
||||
<gt-entry-exception
|
||||
[eventTitle]="event?.title"
|
||||
[eventPlatform]="event?.platform"
|
||||
[eventPlatform]="event?.platform!"
|
||||
></gt-entry-exception>
|
||||
<gt-entry-csp></gt-entry-csp>
|
||||
<gt-entry-breadcrumbs></gt-entry-breadcrumbs>
|
||||
<gt-entry-request></gt-entry-request>
|
||||
<!-- @for (context of event.contexts | keyvalue; track context) {
|
||||
@for (context of contexts() | keyvalue; track context) {
|
||||
<section>
|
||||
<mat-divider></mat-divider>
|
||||
<h3>
|
||||
{{ context.value.type }}
|
||||
@if (context.value.type) {
|
||||
{{ context.value!.type! }}
|
||||
@if (context.value!.type!) {
|
||||
({{ context.key }})
|
||||
} @else {
|
||||
{{ context.key }}
|
||||
@@ -110,16 +109,16 @@
|
||||
}
|
||||
</dl>
|
||||
</section>
|
||||
} -->
|
||||
}
|
||||
@if (event.context) {
|
||||
<section>
|
||||
<mat-divider></mat-divider>
|
||||
<h3>Additional Data</h3>
|
||||
<dl>
|
||||
<!-- @for (context of event.context | keyvalue; track context) {
|
||||
@for (context of event.context | keyvalue; track context) {
|
||||
<gt-entry-data [key]="context.key" [value]="context.value">
|
||||
</gt-entry-data>
|
||||
} -->
|
||||
}
|
||||
</dl>
|
||||
</section>
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@ import {
|
||||
inject,
|
||||
input,
|
||||
effect,
|
||||
computed,
|
||||
} from "@angular/core";
|
||||
import { ActivatedRoute, RouterLink } from "@angular/router";
|
||||
import { RouterLink } from "@angular/router";
|
||||
import { IssueDetailService } from "../issue-detail.service";
|
||||
import { EventTag } from "src/app/issues/interfaces";
|
||||
import { EntryDataComponent } from "../../../shared/entry-data/entry-data.component";
|
||||
import { EntryRequestComponent } from "./entry-request/entry-request.component";
|
||||
import { EntryBreadcrumbsComponent } from "./entry-breadcrumbs/entry-breadcrumbs.component";
|
||||
@@ -46,9 +46,10 @@ export class EventDetailComponent {
|
||||
orgSlug = input.required<string>({ alias: "org-slug" });
|
||||
issueID = input.required<string>({ alias: "issue-id" });
|
||||
eventID = input<string | null>(null, { alias: "event-id" });
|
||||
route = inject(ActivatedRoute);
|
||||
query = input<string | null>(null);
|
||||
|
||||
event = this.issueService.event as any;
|
||||
event = this.issueService.event;
|
||||
contexts = computed(() => this.event()?.contexts as { [key: string]: any });
|
||||
initialLoadComplete = this.issueService.eventInitialLoadComplete;
|
||||
nextEvent = this.issueService.hasNextEvent;
|
||||
previousEvent = this.issueService.hasPreviousEvent;
|
||||
@@ -62,10 +63,9 @@ export class EventDetailComponent {
|
||||
}
|
||||
|
||||
/** TODO fix these types */
|
||||
generateQuery(tag: EventTag) {
|
||||
generateQuery(tag: { [key: string]: string | null }) {
|
||||
// Assume unresolved if not present; tag overrides query otherwise
|
||||
const query = this.route.snapshot.queryParams.query;
|
||||
const unresolved = query === undefined ? "is:unresolved " : "";
|
||||
const unresolved = this.query() === undefined ? "is:unresolved " : "";
|
||||
|
||||
if (tag.key === "environment") {
|
||||
return { environment: tag.value };
|
||||
|
||||
@@ -24,7 +24,6 @@ import { IssuesAPIService } from "src/app/api/issues/issues-api.service";
|
||||
import { Json } from "src/app/interface-primitives";
|
||||
import { OrganizationsService } from "src/app/api/organizations.service";
|
||||
import { StatefulService } from "src/app/shared/stateful-service/signal-state.service";
|
||||
import { toObservable } from "@angular/core/rxjs-interop";
|
||||
import { client } from "src/app/api/api";
|
||||
import { components } from "src/app/api/api-schema";
|
||||
|
||||
@@ -97,44 +96,36 @@ export class IssueDetailService extends StatefulService<IssueDetailState> {
|
||||
}
|
||||
return null;
|
||||
});
|
||||
readonly previousEventUrl$ = toObservable(this.previousEventUrl);
|
||||
readonly eventEntryException = computed(() => {
|
||||
const event = this.event();
|
||||
const isReversed = this.isReversed();
|
||||
|
||||
return event ? this.reverseFrames(event, isReversed) : undefined;
|
||||
});
|
||||
readonly eventEntryException$ = toObservable(this.eventEntryException);
|
||||
readonly _rawStacktraceValues = computed(() => {
|
||||
readonly rawStacktraceValues = computed(() => {
|
||||
const event = this.event();
|
||||
return event ? this.rawStacktraceValues(event) : undefined;
|
||||
return event ? this.getRawStacktraceValues(event) : undefined;
|
||||
});
|
||||
readonly rawStacktraceValues$ = toObservable(this._rawStacktraceValues);
|
||||
readonly eventEntryRequest = computed(() => {
|
||||
const event = this.event();
|
||||
return event ? this.entryRequestData(event) : undefined;
|
||||
return event ? this.getEntryRequestData(event) : undefined;
|
||||
});
|
||||
readonly eventEntryRequest$ = toObservable(this.eventEntryRequest);
|
||||
readonly _eventEntryCSP = computed(() => {
|
||||
readonly eventEntryCSP = computed(() => {
|
||||
const event = this.event();
|
||||
return event ? this.eventEntryCSP(event) : undefined;
|
||||
return event ? this.getEventEntryCSP(event) : undefined;
|
||||
});
|
||||
readonly eventEntryCSP$ = toObservable(this._eventEntryCSP);
|
||||
readonly _eventEntryMessage = computed(() => {
|
||||
readonly eventEntryMessage = computed(() => {
|
||||
const event = this.event();
|
||||
return event ? this.eventEntryMessage(event) : undefined;
|
||||
return event ? this.getEventEntryMessage(event) : undefined;
|
||||
});
|
||||
readonly eventEntryMessage$ = toObservable(this._eventEntryMessage);
|
||||
readonly _specialContexts = computed(() => {
|
||||
readonly specialContexts = computed(() => {
|
||||
const event = this.event();
|
||||
return event ? this.specialContexts(event) : undefined;
|
||||
return event ? this.getSpecialContexts(event) : undefined;
|
||||
});
|
||||
readonly specialContexts$ = toObservable(this._specialContexts);
|
||||
readonly breadcrumbs = computed(() => {
|
||||
const event = this.event();
|
||||
return event ? this.eventEntryBreadcrumbs(event) : undefined;
|
||||
});
|
||||
readonly breadcrumbs$ = toObservable(this.breadcrumbs);
|
||||
|
||||
#params = signal<{ issueID: string; eventID: string | null }>({
|
||||
issueID: "",
|
||||
@@ -333,7 +324,7 @@ export class IssueDetailService extends StatefulService<IssueDetailState> {
|
||||
}
|
||||
|
||||
/* Return the message entry type for an event */
|
||||
private eventEntryMessage(event: EventDetail): Message | undefined {
|
||||
private getEventEntryMessage(event: EventDetail): Message | undefined {
|
||||
const eventMessage = this.getMessageEntryData(event);
|
||||
|
||||
if (eventMessage) {
|
||||
@@ -343,7 +334,7 @@ export class IssueDetailService extends StatefulService<IssueDetailState> {
|
||||
}
|
||||
|
||||
/* Return the CSP entry type for an event */
|
||||
private eventEntryCSP(event: EventDetail): CSP | undefined {
|
||||
private getEventEntryCSP(event: EventDetail): CSP | undefined {
|
||||
const eventCSP = this.getCspEntryData(event);
|
||||
|
||||
if (eventCSP) {
|
||||
@@ -364,7 +355,9 @@ export class IssueDetailService extends StatefulService<IssueDetailState> {
|
||||
}
|
||||
|
||||
/* Return the request entry type for an event with additional fields parsed from url */
|
||||
private entryRequestData(event: EventDetail): AnnotatedRequest | undefined {
|
||||
private getEntryRequestData(
|
||||
event: EventDetail,
|
||||
): AnnotatedRequest | undefined {
|
||||
const eventRequest = this.getRequestEntryData(event);
|
||||
if (eventRequest) {
|
||||
let urlDomainName = "";
|
||||
@@ -411,7 +404,7 @@ export class IssueDetailService extends StatefulService<IssueDetailState> {
|
||||
return;
|
||||
}
|
||||
|
||||
rawStacktraceValues(event: EventDetail): Values[] | undefined {
|
||||
getRawStacktraceValues(event: EventDetail): Values[] | undefined {
|
||||
const platform = event.platform;
|
||||
const eventException = this.getExceptionEntryData(event);
|
||||
|
||||
@@ -455,14 +448,14 @@ export class IssueDetailService extends StatefulService<IssueDetailState> {
|
||||
* The order they are return in should always be user, browser, runtime
|
||||
* os, device, gpu
|
||||
*/
|
||||
specialContexts(event: EventDetail): AnnotatedContexts[] {
|
||||
const user = (event as any).user;
|
||||
const contexts = (event as any).contexts;
|
||||
getSpecialContexts(event: EventDetail): AnnotatedContexts[] {
|
||||
const user: any = event.user;
|
||||
const contexts = event.contexts;
|
||||
const contextsArray: AnnotatedContexts[] = [];
|
||||
|
||||
for (const key in contexts) {
|
||||
if (key) {
|
||||
const contextsObject = contexts[key];
|
||||
const contextsObject: any = contexts[key];
|
||||
|
||||
if (key === "browser") {
|
||||
contextsArray.unshift({
|
||||
|
||||
@@ -8,8 +8,8 @@ import { Component, ChangeDetectionStrategy, input } from "@angular/core";
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class EntryDataComponent {
|
||||
readonly key = input<string>();
|
||||
readonly value = input<unknown>();
|
||||
readonly key = input<any>();
|
||||
readonly value = input<any>();
|
||||
|
||||
get displayValue() {
|
||||
const value = this.value();
|
||||
|
||||
Reference in new issue
Block a user