refactor: Update code and field types for telemetry exceptions

This commit is contained in:
Simon Larsen 2024-08-26 20:38:39 +01:00
parent 3636b160fb
commit 64725b3973
No known key found for this signature in database
GPG Key ID: 96C5DCA24769DBCA
11 changed files with 257 additions and 53 deletions

View File

@ -54,8 +54,8 @@ import TelemetryService from "./TelemetryService";
@CrudApiEndpoint(new Route("/telemetry-exception-status"))
@TableMetadata({
tableName: "TelemetryException",
singularName: "TelemetryException",
pluralName: "TelemetryExceptionsStatus",
singularName: "Exception",
pluralName: "Exceptions",
icon: IconProp.Error,
tableDescription:
"List of all Telemetry Exceptions created for the telemetry service for this OneUptime project and it's status.",

View File

@ -5,6 +5,7 @@ enum CodeType {
JSON = "json",
Markdown = "markdown",
SQL = "sql",
Text = "text",
// TODO add more mime types.
}

View File

@ -283,7 +283,8 @@ const Detail: DetailFunction = <T extends GenericObject>(
(field.fieldType === FieldType.HTML ||
field.fieldType === FieldType.CSS ||
field.fieldType === FieldType.JSON ||
field.fieldType === FieldType.JavaScript)
field.fieldType === FieldType.JavaScript ||
field.fieldType === FieldType.Code)
) {
let codeType: CodeType = CodeType.HTML;
@ -315,6 +316,10 @@ const Detail: DetailFunction = <T extends GenericObject>(
codeType = CodeType.JavaScript;
}
if (field.fieldType === FieldType.Code) {
codeType = CodeType.Text;
}
data = (
<CodeEditor
type={codeType}

View File

@ -34,6 +34,7 @@ enum FieldType {
Element = "Element",
Minutes = "Minutes",
ArrayOfText = "ArrayOfText",
Code = "Code",
}
export default FieldType;

View File

@ -0,0 +1,127 @@
import TelemetryService from "Common/Models/DatabaseModels/TelemetryService";
import { JSONObject } from "Common/Types/JSON";
import Card from "Common/UI/Components/Card/Card";
import Detail from "Common/UI/Components/Detail/Detail";
import Field from "Common/UI/Components/Detail/Field";
import FieldType from "Common/UI/Components/Types/FieldType";
import React, { FunctionComponent, ReactElement } from "react";
import TelemetryServiceElement from "../TelemetryService/TelemetryServiceElement";
export interface ComponentProps {
exceptionType?: string | undefined;
message?: string | undefined;
stackTrace?: string | undefined;
fingerprint?: string | undefined;
firstSeenAt?: Date | undefined;
lastSeenAt?: Date | undefined;
occuranceCount?: number | undefined;
attributes?: JSONObject | undefined;
telemetryService?: TelemetryService | undefined;
}
const ExceptionDetail: FunctionComponent<ComponentProps> = (
props: ComponentProps,
): ReactElement => {
const fields: Array<Field<ComponentProps>> = [];
if (props.message) {
fields.push({
key: "message",
title: "Message",
description: "The message of the exception.",
fieldType: FieldType.Text,
});
}
if (props.exceptionType) {
fields.push({
key: "exceptionType",
title: "Exception Type",
description: "The type of the exception.",
fieldType: FieldType.Text,
});
}
if (props.stackTrace) {
fields.push({
key: "stackTrace",
title: "Stack Trace",
description: "The stack trace of the exception.",
fieldType: FieldType.Code,
});
}
if (props.firstSeenAt) {
fields.push({
key: "firstSeenAt",
title: "First Seen At",
description: "The time the exception was first seen.",
fieldType: FieldType.DateTime,
});
}
if (props.lastSeenAt) {
fields.push({
key: "lastSeenAt",
title: "Last Seen At",
description: "The time the exception was last seen.",
fieldType: FieldType.DateTime,
});
}
if (props.occuranceCount) {
fields.push({
key: "occuranceCount",
title: "Occurance Count",
description: "The number of times this exception has occurred.",
fieldType: FieldType.Number,
});
}
if (props.fingerprint) {
fields.push({
key: "fingerprint",
title: "Fingerprint",
description: "SHA256 hash of this exception.",
fieldType: FieldType.Text,
});
}
if (props.attributes) {
fields.push({
key: "attributes",
title: "Attributes",
description: "Additional attributes of the exception.",
fieldType: FieldType.JSON,
});
}
if(props.telemetryService) {
fields.push({
key: "telemetryService",
title: "Telemetry Service",
description: "The service that this exception was received from.",
fieldType: FieldType.Element,
getElement: () => {
return (
<TelemetryServiceElement telemetryService={props.telemetryService!} />
);
}
});
}
return (
<Card>
<div className="-mt-4">
<Detail<ComponentProps>
item={props}
fields={fields}
/>
</div>
</Card>
);
};
export default ExceptionDetail;

View File

@ -0,0 +1,100 @@
import React, { FunctionComponent, ReactElement, useEffect } from "react";
import TelemetryException from "Common/Models/DatabaseModels/TelemetryException";
import ExceptionDetail from "./ExceptionDetail";
import ObjectID from "Common/Types/ObjectID";
import PageLoader from "Common/UI/Components/Loader/PageLoader";
import ErrorMessage from "Common/UI/Components/ErrorMessage/ErrorMessage";
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
import ModelAPI from "Common/UI/Utils/ModelAPI/ModelAPI";
import API from "Common/UI/Utils/API/API";
import ModelDelete from "Common/UI/Components/ModelDelete/ModelDelete";
import Navigation from "Common/UI/Utils/Navigation";
import RouteMap from "../../Utils/RouteMap";
import PageMap from "../../Utils/PageMap";
import Route from "Common/Types/API/Route";
export interface ComponentProps {
telemetryExceptionId: ObjectID;
}
const ExceptionExplorer: FunctionComponent<ComponentProps> = (
props: ComponentProps,
): ReactElement => {
const [telemetryException, setTelemetryException] = React.useState<
TelemetryException | undefined
>(undefined);
const [isLoading, setIsLoading] = React.useState<boolean>(false);
const [error, setError] = React.useState<string | undefined>(undefined);
const fetchItems: PromiseVoidFunction = async (): Promise<void> => {
try {
setIsLoading(true);
// get trace with this id and then get all the parentSpanId with this traceid.
const telemetryException: TelemetryException | null =
await ModelAPI.getItem<TelemetryException>({
id: props.telemetryExceptionId,
modelType: TelemetryException,
select: {
_id: true,
exceptionType: true,
message: true,
stackTrace: true,
fingerprint: true,
firstSeenAt: true,
lastSeenAt: true,
occuranceCount: true,
},
});
if (!telemetryException) {
throw new Error("Exception not found");
}
setTelemetryException(telemetryException);
} catch (err) {
setError(API.getFriendlyMessage(err));
}
setIsLoading(false);
};
useEffect(() => {
fetchItems().catch((err) => {
return setError(API.getFriendlyMessage(err));
});
}, []);
if (isLoading) {
return <PageLoader isVisible={true} />;
}
if (error) {
return <ErrorMessage error={error} />;
}
return (
<div className="space-y-4 mb-10">
{/** Resolve / Unresolve Button */}
<ExceptionDetail {...telemetryException} />
{/** Assign / Unassign Button */}
{/** Occurance Table */}
{/** Archive / Unarchive Button Button */}
<ModelDelete
modelType={TelemetryException}
modelId={props.telemetryExceptionId}
onDeleteSuccess={() => {
Navigation.navigate(RouteMap[PageMap.TELEMETRY] as Route);
}}
/>
</div>
);
};
export default ExceptionExplorer;

View File

@ -1,15 +1,17 @@
import MetricExplorer from "../../../../Components/Metrics/MetricExplorer";
import Navigation from "Common/UI/Utils/Navigation";
import ExceptionExplorer from "../../../../Components/Exceptions/ExceptionExplorer";
import PageComponentProps from "../../../PageComponentProps";
import React, { Fragment, FunctionComponent, ReactElement } from "react";
import ObjectID from "Common/Types/ObjectID";
const TelemetryMetricViewPage: FunctionComponent<
PageComponentProps
> = (): ReactElement => {
const exceptionId: string = Navigation.getLastParamAsString(0);
return (
<Fragment>
<div className="mb-10">
<MetricExplorer />
</div>
<ExceptionExplorer telemetryExceptionId={new ObjectID(exceptionId)} />
</Fragment>
);
};

View File

@ -1,22 +1,17 @@
import DashboardLogsViewer from "../../../../../../Components/Logs/LogsViewer";
import PageComponentProps from "../../../../../PageComponentProps";
import ObjectID from "Common/Types/ObjectID";
import Navigation from "Common/UI/Utils/Navigation";
import React, { Fragment, FunctionComponent, ReactElement } from "react";
import ExceptionExplorer from "../../../../../../Components/Exceptions/ExceptionExplorer";
const ServiceDelete: FunctionComponent<
PageComponentProps
> = (): ReactElement => {
const modelId: ObjectID = Navigation.getLastParamAsObjectID(1);
const modelId: ObjectID = Navigation.getLastParamAsObjectID();
return (
<Fragment>
<DashboardLogsViewer
showFilters={true}
telemetryServiceIds={[modelId]}
enableRealtime={true}
id="logs"
/>
<ExceptionExplorer telemetryExceptionId={modelId} />
</Fragment>
);
};

View File

@ -165,12 +165,6 @@ const TelemetryServicesViewDocumentation: LazyExoticComponent<
return import("../Pages/Telemetry/Services/View/Documentation");
});
const TelemetryExceptionView: LazyExoticComponent<
FunctionComponent<ComponentProps>
> = lazy(() => {
return import("../Pages/Telemetry/Exceptions/View/Index");
});
const TelemetryRoutes: FunctionComponent<ComponentProps> = (
props: ComponentProps,
): ReactElement => {
@ -201,7 +195,6 @@ const TelemetryRoutes: FunctionComponent<ComponentProps> = (
}
/>
<PageRoute
path={TelemetryRoutePath[PageMap.TELEMETRY_TRACES] || ""}
element={
@ -299,25 +292,6 @@ const TelemetryRoutes: FunctionComponent<ComponentProps> = (
/>
</PageRoute>
{/** Exception View */}
<PageRoute
path={TelemetryRoutePath[PageMap.TELEMETRY_EXCEPTIONS_ROOT] || ""}
element={<TelemetryExceptionViewLayout {...props} />}
>
<PageRoute
path={TelemetryRoutePath[PageMap.TELEMETRY_EXCEPTIONS_VIEW] || ""}
element={
<Suspense fallback={Loader}>
<TelemetryExceptionView
{...props}
pageRoute={RouteMap[PageMap.TELEMETRY_EXCEPTIONS_VIEW] as Route}
/>
</Suspense>
}
/>
</PageRoute>
{/* Metric View */}
<PageRoute
@ -349,12 +323,11 @@ const TelemetryRoutes: FunctionComponent<ComponentProps> = (
/>
</PageRoute>
{/** Exception View */}
<PageRoute
path={TelemetryRoutePath[PageMap.TELEMETRY_EXCEPTIONS_ROOT] || ""}
element={<TelemetryTraceLayout {...props} />}
element={<TelemetryExceptionViewLayout {...props} />}
>
<PageRoute
index
@ -473,7 +446,7 @@ const TelemetryRoutes: FunctionComponent<ComponentProps> = (
{...props}
pageRoute={
RouteMap[
PageMap.TELEMETRY_SERVICES_VIEW_EXCEPTIONS_UNRESOLVED
PageMap.TELEMETRY_SERVICES_VIEW_EXCEPTIONS_UNRESOLVED
] as Route
}
/>
@ -492,7 +465,7 @@ const TelemetryRoutes: FunctionComponent<ComponentProps> = (
{...props}
pageRoute={
RouteMap[
PageMap.TELEMETRY_SERVICES_VIEW_EXCEPTIONS_RESOLVED
PageMap.TELEMETRY_SERVICES_VIEW_EXCEPTIONS_RESOLVED
] as Route
}
/>
@ -510,9 +483,7 @@ const TelemetryRoutes: FunctionComponent<ComponentProps> = (
<TelemetryServiceViewException
{...props}
pageRoute={
RouteMap[
PageMap.TELEMETRY_SERVICES_VIEW_EXCEPTION
] as Route
RouteMap[PageMap.TELEMETRY_SERVICES_VIEW_EXCEPTION] as Route
}
/>
</Suspense>
@ -530,7 +501,7 @@ const TelemetryRoutes: FunctionComponent<ComponentProps> = (
{...props}
pageRoute={
RouteMap[
PageMap.TELEMETRY_SERVICES_VIEW_EXCEPTIONS_ARCHIVED
PageMap.TELEMETRY_SERVICES_VIEW_EXCEPTIONS_ARCHIVED
] as Route
}
/>
@ -646,7 +617,7 @@ const TelemetryRoutes: FunctionComponent<ComponentProps> = (
{...props}
pageRoute={
RouteMap[
PageMap.TELEMETRY_SERVICES_VIEW_DOCUMENTATION
PageMap.TELEMETRY_SERVICES_VIEW_DOCUMENTATION
] as Route
}
/>

View File

@ -75,7 +75,7 @@ export const TelemetryRoutePath: Dictionary<string> = {
[PageMap.TELEMETRY_TRACE_VIEW]: `traces/view/${RouteParams.ModelID}`, // modelID is spanId
[PageMap.TELEMETRY_EXCEPTIONS_ROOT]: `exception`,
[PageMap.TELEMETRY_EXCEPTIONS_VIEW]: `exception/view/${RouteParams.ModelID}`,
[PageMap.TELEMETRY_EXCEPTIONS_VIEW]: `exception/${RouteParams.ModelID}`,
[PageMap.TELEMETRY_LOG_ROOT]: `logs`,
@ -946,7 +946,6 @@ const RouteMap: Dictionary<Route> = {
}`,
),
[PageMap.TELEMETRY_SERVICES_VIEW_EXCEPTIONS_UNRESOLVED]: new Route(
`/dashboard/${RouteParams.ProjectID}/telemetry/${
TelemetryRoutePath[PageMap.TELEMETRY_SERVICES_VIEW_EXCEPTIONS_UNRESOLVED]
@ -1008,7 +1007,7 @@ const RouteMap: Dictionary<Route> = {
}`,
),
// view exceptions.
// view exceptions.
[PageMap.TELEMETRY_SERVICES_VIEW_EXCEPTIONS]: new Route(
`/dashboard/${RouteParams.ProjectID}/telemetry/${
TelemetryRoutePath[PageMap.TELEMETRY_SERVICES_VIEW_EXCEPTIONS]

View File

@ -56,6 +56,7 @@ export default class ExceptionUtil {
},
select: {
_id: true,
occuranceCount: true,
},
props: {
isRoot: true,
@ -72,6 +73,7 @@ export default class ExceptionUtil {
lastSeenAt: OneUptimeDate.now(),
markedAsResolvedByUserId: null,
markedAsResolvedAt: null, // unmark as resolved if it was marked as resolved
occuranceCount: (existingExceptionStatus.occuranceCount || 0) + 1,
},
props: {
isRoot: true,
@ -87,6 +89,7 @@ export default class ExceptionUtil {
newExceptionStatus.telemetryServiceId = exception.serviceId;
newExceptionStatus.lastSeenAt = OneUptimeDate.now();
newExceptionStatus.firstSeenAt = OneUptimeDate.now();
newExceptionStatus.occuranceCount = 1;
if (exception.exceptionType) {
newExceptionStatus.exceptionType = exception.exceptionType;