refactor event detail

This commit is contained in:
David Burke committed 2025-06-04 11:21:09 -04:00
1 parent 1af2540a4c
commit dc833b2eb2
17 files changed
+83 -102

No files matched your search

+28 -20
View File
@@ -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,4 +1,4 @@
@if (eventEntryException$ | async; as exception) {
@if (eventEntryException(); as exception) {
<section class="mat-typography">
<section class="exception">
<div class="header-wrapper">
@@ -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,4 +1,4 @@
@for (value of rawStacktraceValues$ | async; track value) {
@for (value of rawStacktraceValues(); track value) {
<div class="wrapper">
<p>
@if (eventPlatform() === "java") {
@@ -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();