refactor: Update JSONFunctions and Table component to support custom classNames

This commit is contained in:
Simon Larsen
2024-08-08 16:04:14 -06:00
parent ca7c55b557
commit 0ef7ca93f8
7 changed files with 192 additions and 137 deletions

View File

@@ -15,6 +15,13 @@ export default class JSONFunctions {
return sizeToGb;
}
public static isJSONObjectDifferent(
obj1: JSONObject,
obj2: JSONObject,
): boolean {
return JSON.stringify(obj1) !== JSON.stringify(obj2);
}
public static nestJson(obj: JSONObject): JSONObject {
// obj could be in this format:

View File

@@ -619,6 +619,12 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
setIsFilterFetchLoading(false);
};
useEffect(() => {
fetchItems().catch((err: Error) => {
setError(API.getFriendlyMessage(err));
});
}, [props.query]);
const fetchAllBulkItems: PromiseVoidFunction = async (): Promise<void> => {
setError("");
setIsLoading(true);
@@ -1260,6 +1266,11 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
onFilterChanged={(filterData: FilterData<TBaseModel>) => {
onFilterChanged(filterData);
}}
className={
props.cardProps
? ""
: "rounded-lg border-2 border-gray-200 p-6 pt-0 pb-0"
}
onFilterRefreshClick={async () => {
await getFilterDropdownItems();
}}

View File

@@ -25,7 +25,7 @@ export interface ComponentProps<T extends GenericObject> {
data: Array<T>;
id: string;
columns: Columns<T>;
className?: string;
disablePagination?: undefined | boolean;
onNavigateToPage: (pageNumber: number, itemsOnPage: number) => void;
currentPageNumber: number;
@@ -200,7 +200,7 @@ const Table: TableFunction = <T extends GenericObject>(
});
return (
<div>
<div className={props.className}>
<FilterViewer
id={`${props.id}-filter`}
showFilterModal={props.showFilterModal || false}

View File

@@ -1,14 +1,13 @@
import MonitorStepTraceMonitor from "Common/Types/Monitor/MonitorStepTraceMonitor";
import TelemetryService from "Common/Models/DatabaseModels/TelemetryService";
import React, { FunctionComponent, ReactElement } from "react";
import React, { FunctionComponent, ReactElement, useEffect } from "react";
import BasicForm from "Common/UI/Components/Forms/BasicForm";
import DropdownUtil from "Common/UI/Utils/Dropdown";
import FormFieldSchemaType from "Common/UI/Components/Forms/Types/FormFieldSchemaType";
import Button, { ButtonStyleType } from "Common/UI/Components/Button/Button";
import FieldLabelElement from "Common/UI/Components/Forms/Fields/FieldLabel";
import HorizontalRule from "Common/UI/Components/HorizontalRule/HorizontalRule";
import TraceMonitorPreview from "../../../Monitor/TraceMonitor/TraceMonitorPreview";
import { SpanStatus } from "Common/Models/AnalyticsModels/Span";
import SpanUtil from "../../../../Utils/SpanUtil";
export interface ComponentProps {
monitorStepTraceMonitor: MonitorStepTraceMonitor;
@@ -23,14 +22,19 @@ const TraceMonitorStepForm: FunctionComponent<ComponentProps> = (
props: ComponentProps,
): ReactElement => {
const [monitorStepTraceMonitor, setMonitorStepTraceMonitor] =
React.useState<MonitorStepTraceMonitor>(props.monitorStepTraceMonitor);
React.useState<MonitorStepTraceMonitor | null>(null);
let showAdvancedOptionsByDefault: boolean = false;
useEffect(() => {
setMonitorStepTraceMonitor(props.monitorStepTraceMonitor);
}, [props.monitorStepTraceMonitor]);
if (
monitorStepTraceMonitor.attributes ||
monitorStepTraceMonitor.spanStatuses ||
monitorStepTraceMonitor.telemetryServiceIds
monitorStepTraceMonitor &&
(monitorStepTraceMonitor.attributes ||
monitorStepTraceMonitor.spanStatuses ||
monitorStepTraceMonitor.telemetryServiceIds)
) {
showAdvancedOptionsByDefault = true;
}
@@ -121,8 +125,7 @@ const TraceMonitorStepForm: FunctionComponent<ComponentProps> = (
field: {
spanStatuses: true,
},
dropdownOptions:
DropdownUtil.getDropdownOptionsFromEnum(SpanStatus),
dropdownOptions: SpanUtil.getSpanStatusDropdownOptions(),
fieldType: FormFieldSchemaType.MultiSelectDropdown,
title: "Filter by Span Status",
description: "Select the status of the spans you want to monitor.",
@@ -183,7 +186,7 @@ const TraceMonitorStepForm: FunctionComponent<ComponentProps> = (
<div>
<HorizontalRule />
<FieldLabelElement
title={"Traces Preview"}
title={"Spans Preview"}
description={
"Here is the preview of the Traces that will be monitored based on the filters you have set above."
}
@@ -192,7 +195,7 @@ const TraceMonitorStepForm: FunctionComponent<ComponentProps> = (
/>
<div className="mt-5 mb-5">
<TraceMonitorPreview
monitorStepTraceMonitor={monitorStepTraceMonitor}
monitorStepTraceMonitor={monitorStepTraceMonitor!}
/>
</div>
</div>

View File

@@ -5,6 +5,7 @@ import MonitorStepTraceMonitor, {
import TraceTable from "../../Traces/TraceTable";
import Query from "Common/Types/BaseDatabase/Query";
import Span from "Common/Models/AnalyticsModels/Span";
import JSONFunctions from "Common/Types/JSONFunctions";
export interface ComponentProps {
monitorStepTraceMonitor: MonitorStepTraceMonitor | undefined;
@@ -25,10 +26,14 @@ const TraceMonitorPreview: FunctionComponent<ComponentProps> = (
const [spanQuery, setSpanQuery] = React.useState<Query<Span>>(refreshQuery());
useEffect(() => {
setSpanQuery(refreshQuery());
const query: Query<Span> = refreshQuery();
if (JSONFunctions.isJSONObjectDifferent(spanQuery, query)) {
setSpanQuery(query);
}
}, [props.monitorStepTraceMonitor]);
return <TraceTable spanQuery={spanQuery} />;
return <TraceTable isMinimalTable={true} spanQuery={spanQuery} />;
};
export default TraceMonitorPreview;

View File

@@ -6,7 +6,7 @@ import { DropdownOption } from "Common/UI/Components/Dropdown/Dropdown";
import AnalyticsModelTable from "Common/UI/Components/ModelTable/AnalyticsModelTable";
import FieldType from "Common/UI/Components/Types/FieldType";
import DropdownUtil from "Common/UI/Utils/Dropdown";
import Span, { SpanKind, SpanStatus } from "Common/Models/AnalyticsModels/Span";
import Span, { SpanKind } from "Common/Models/AnalyticsModels/Span";
import React, {
Fragment,
FunctionComponent,
@@ -27,10 +27,13 @@ import ModelAPI from "Common/UI/Utils/ModelAPI/ModelAPI";
import PageLoader from "Common/UI/Components/Loader/PageLoader";
import ErrorMessage from "Common/UI/Components/ErrorMessage/ErrorMessage";
import Query from "Common/Types/BaseDatabase/Query";
import SpanUtil from "../../Utils/SpanUtil";
export interface ComponentProps {
modelId?: ObjectID | undefined;
spanQuery?: Query<Span> | undefined;
isMinimalTable?: boolean | undefined;
noItemsMessage?: string | undefined;
}
const TraceTable: FunctionComponent<ComponentProps> = (
@@ -43,6 +46,16 @@ const TraceTable: FunctionComponent<ComponentProps> = (
const [isPageLoading, setIsPageLoading] = React.useState<boolean>(true);
const [pageError, setPageError] = React.useState<string>("");
const [spanQuery, setSpanQuery] = React.useState<Query<Span> | null>(
props.spanQuery || null,
);
useEffect(() => {
if (props.spanQuery) {
setSpanQuery(props.spanQuery);
}
}, [props.spanQuery]);
const loadAttributes: PromiseVoidFunction = async (): Promise<void> => {
try {
setIsPageLoading(true);
@@ -107,137 +120,139 @@ const TraceTable: FunctionComponent<ComponentProps> = (
return (
<Fragment>
<AnalyticsModelTable<Span>
modelType={Span}
id="traces-table"
isDeleteable={false}
isEditable={false}
isCreateable={false}
singularName="Trace"
pluralName="Traces"
name="Traces"
isViewable={true}
cardProps={{
title: "Traces",
description:
"Traces are the individual spans that make up a request. They are the building blocks of a trace and represent the work done by a single service.",
}}
query={{
projectId: DashboardNavigation.getProjectId(),
serviceId: modelId ? modelId : undefined,
...props.spanQuery,
}}
showViewIdButton={true}
noItemsMessage={"No traces found for this service."}
showRefreshButton={true}
sortBy="startTime"
sortOrder={SortOrder.Descending}
onViewPage={(span: Span) => {
return Promise.resolve(
new Route(viewRoute.toString()).addRoute(span.traceId!.toString()),
);
}}
filters={[
{
field: {
traceId: true,
<div className="rounded">
<AnalyticsModelTable<Span>
disablePagination={true}
modelType={Span}
id="traces-table"
isDeleteable={false}
isEditable={false}
isCreateable={false}
singularName="Trace"
pluralName="Traces"
name="Traces"
isViewable={true}
cardProps={
props.isMinimalTable
? undefined
: {
title: "Traces",
description:
"Traces are the individual spans that make up a request. They are the building blocks of a trace and represent the work done by a single service.",
}
}
query={{
projectId: DashboardNavigation.getProjectId(),
serviceId: modelId ? modelId : undefined,
...spanQuery,
}}
showViewIdButton={true}
noItemsMessage={
props.noItemsMessage ? props.noItemsMessage : "No traces found."
}
showRefreshButton={true}
sortBy="startTime"
sortOrder={SortOrder.Descending}
onViewPage={(span: Span) => {
return Promise.resolve(
new Route(viewRoute.toString()).addRoute(
span.traceId!.toString(),
),
);
}}
filters={[
{
field: {
traceId: true,
},
type: FieldType.Text,
title: "Trace ID",
},
type: FieldType.Text,
title: "Trace ID",
},
{
field: {
statusCode: true,
{
field: {
statusCode: true,
},
type: FieldType.Dropdown,
filterDropdownOptions: SpanUtil.getSpanStatusDropdownOptions(),
title: "Span Status",
},
type: FieldType.Dropdown,
filterDropdownOptions: DropdownUtil.getDropdownOptionsFromEnum(
SpanStatus,
true,
).filter((dropdownOption: DropdownOption) => {
return (
dropdownOption.label === "Unset" ||
dropdownOption.label === "Ok" ||
dropdownOption.label === "Error"
);
}),
title: "Span Status",
},
{
field: {
name: true,
{
field: {
name: true,
},
type: FieldType.Text,
title: "Span Name",
},
type: FieldType.Text,
title: "Span Name",
},
{
field: {
kind: true,
{
field: {
kind: true,
},
type: FieldType.Text,
title: "Span Kind",
filterDropdownOptions: spanKindDropdownOptions,
},
type: FieldType.Text,
title: "Span Kind",
filterDropdownOptions: spanKindDropdownOptions,
},
{
field: {
startTime: true,
{
field: {
startTime: true,
},
type: FieldType.DateTime,
title: "Seen At",
},
type: FieldType.DateTime,
title: "Seen At",
},
{
field: {
attributes: true,
{
field: {
attributes: true,
},
type: FieldType.JSON,
title: "Attributes",
jsonKeys: attributes,
},
type: FieldType.JSON,
title: "Attributes",
jsonKeys: attributes,
},
]}
selectMoreFields={{
statusCode: true,
}}
columns={[
{
field: {
spanId: true,
]}
selectMoreFields={{
statusCode: true,
}}
columns={[
{
field: {
spanId: true,
},
title: "Span ID",
type: FieldType.Element,
getElement: (span: Span): ReactElement => {
return (
<Fragment>
<SpanStatusElement
traceId={span.traceId?.toString()}
spanStatusCode={span.statusCode!}
title={span.spanId?.toString()}
/>
</Fragment>
);
},
},
title: "Span ID",
type: FieldType.Element,
getElement: (span: Span): ReactElement => {
return (
<Fragment>
<SpanStatusElement
traceId={span.traceId?.toString()}
spanStatusCode={span.statusCode!}
title={span.spanId?.toString()}
/>
</Fragment>
);
{
field: {
traceId: true,
},
title: "Trace ID",
type: FieldType.Text,
},
},
{
field: {
traceId: true,
{
field: {
name: true,
},
title: "Span Name",
type: FieldType.Text,
},
title: "Trace ID",
type: FieldType.Text,
},
{
field: {
name: true,
{
field: {
startTime: true,
},
title: "Seen At",
type: FieldType.DateTime,
},
title: "Span Name",
type: FieldType.Text,
},
{
field: {
startTime: true,
},
title: "Seen At",
type: FieldType.DateTime,
},
]}
/>
]}
/>
</div>
</Fragment>
);
};

View File

@@ -2,6 +2,8 @@ import { Black } from "Common/Types/BrandColors";
import Color from "Common/Types/Color";
import Span, { SpanKind, SpanStatus } from "Common/Models/AnalyticsModels/Span";
import TelemetryService from "Common/Models/DatabaseModels/TelemetryService";
import { DropdownOption } from "Common/UI/Components/Dropdown/Dropdown";
import DropdownUtil from "Common/UI/Utils/Dropdown";
export enum IntervalUnit {
Nanoseconds = "ns",
@@ -16,6 +18,18 @@ export interface DivisibilityFactor {
}
export default class SpanUtil {
public static getSpanStatusDropdownOptions(): Array<DropdownOption> {
return DropdownUtil.getDropdownOptionsFromEnum(SpanStatus, true).filter(
(dropdownOption: DropdownOption) => {
return (
dropdownOption.label === "Unset" ||
dropdownOption.label === "Ok" ||
dropdownOption.label === "Error"
);
},
);
}
public static getSpanDurationAsString(data: {
divisibilityFactor: DivisibilityFactor;
spanDurationInUnixNano: number;