Skip to content

Commit 759da7a

Browse files
authored
feat(metrics): Support multiple OTEL tables (#610)
1 parent 4514f2c commit 759da7a

File tree

5 files changed

+144
-208
lines changed

5 files changed

+144
-208
lines changed

.changeset/metal-doors-burn.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@hyperdx/common-utils": minor
3+
"@hyperdx/api": minor
4+
"@hyperdx/app": minor
5+
---
6+
7+
Support multiple OTEL metric types in source configuration setup.

packages/api/src/models/source.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { SourceKind, TSource } from '@hyperdx/common-utils/dist/types';
1+
import {
2+
MetricsDataType,
3+
SourceKind,
4+
TSource,
5+
} from '@hyperdx/common-utils/dist/types';
26
import mongoose, { Schema } from 'mongoose';
37

48
type ObjectId = mongoose.Types.ObjectId;
@@ -59,11 +63,11 @@ export const Source = mongoose.model<ISource>(
5963
statusCodeExpression: String,
6064
statusMessageExpression: String,
6165

62-
metricDiscriminator: String,
63-
metricNameExpression: String,
64-
metricUnitExpression: String,
65-
flagsExpression: String,
66-
valueExpression: String,
66+
metricTables: {
67+
[MetricsDataType.Gauge]: String,
68+
[MetricsDataType.Histogram]: String,
69+
[MetricsDataType.Sum]: String,
70+
} as any,
6771
},
6872
{
6973
toJSON: { virtuals: true },

packages/app/src/components/SourceForm.tsx

Lines changed: 56 additions & 167 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ import {
66
UseFormSetValue,
77
UseFormWatch,
88
} from 'react-hook-form';
9-
import { SourceKind, TSource } from '@hyperdx/common-utils/dist/types';
9+
import {
10+
MetricsDataType,
11+
SourceKind,
12+
TSource,
13+
} from '@hyperdx/common-utils/dist/types';
1014
import {
1115
Anchor,
1216
Box,
@@ -32,6 +36,7 @@ import { IS_METRICS_ENABLED, IS_SESSIONS_ENABLED } from '@/config';
3236
import { useConnections } from '@/connection';
3337
import {
3438
inferTableSourceConfig,
39+
isValidMetricTable,
3540
useCreateSource,
3641
useDeleteSource,
3742
useSource,
@@ -641,10 +646,43 @@ export function MetricTableModelForm({
641646
setValue: UseFormSetValue<TSource>;
642647
}) {
643648
const databaseName = watch(`from.databaseName`, DEFAULT_DATABASE);
644-
const tableName = watch(`from.tableName`);
645649
const connectionId = watch(`connection`);
646650

647-
const [showOptionalFields, setShowOptionalFields] = useState(false);
651+
useEffect(() => {
652+
setValue('timestampValueExpression', 'TimeUnix');
653+
const { unsubscribe } = watch(async (value, { name, type }) => {
654+
try {
655+
if (name && type === 'change') {
656+
const [prefix, suffix] = name.split('.');
657+
if (prefix === 'metricTables') {
658+
const tableName =
659+
value?.metricTables?.[suffix as keyof typeof value.metricTables];
660+
const metricType = suffix as MetricsDataType;
661+
const isValid = await isValidMetricTable({
662+
databaseName,
663+
tableName,
664+
connectionId,
665+
metricType,
666+
});
667+
if (!isValid) {
668+
notifications.show({
669+
color: 'red',
670+
message: `${tableName} is not a valid OTEL ${metricType} schema.`,
671+
});
672+
}
673+
}
674+
}
675+
} catch (e) {
676+
console.error(e);
677+
notifications.show({
678+
color: 'red',
679+
message: e.message,
680+
});
681+
}
682+
});
683+
684+
return () => unsubscribe();
685+
}, [setValue, watch, databaseName, connectionId]);
648686

649687
return (
650688
<>
@@ -659,169 +697,20 @@ export function MetricTableModelForm({
659697
name={`from.databaseName`}
660698
/>
661699
</FormRow>
662-
<Divider />
663-
<FormRow label={'Metric Type'}>
664-
<Select
665-
data={[{ value: 'gauge', label: 'Gauge' }]}
666-
defaultValue="gauge"
667-
placeholder="Select metric type"
668-
allowDeselect={false}
669-
/>
670-
</FormRow>
671-
<FormRow label={'Table'}>
672-
<DBTableSelectControlled
673-
connectionId={connectionId}
674-
database={databaseName}
675-
control={control}
676-
name={`from.tableName`}
677-
rules={{ required: 'Table is required' }}
678-
/>
679-
</FormRow>
680-
<FormRow label={'Timestamp Column'}>
681-
<SQLInlineEditorControlled
682-
connectionId={connectionId}
683-
database={databaseName}
684-
table={tableName}
685-
control={control}
686-
name="timestampValueExpression"
687-
placeholder="TimeUnix"
688-
disableKeywordAutocomplete
689-
/>
690-
</FormRow>
691-
<FormRow
692-
label={'Default Select'}
693-
helpText="Default columns selected in search results (this can be customized per search later)"
694-
>
695-
<SQLInlineEditorControlled
696-
database={databaseName}
697-
table={tableName}
698-
control={control}
699-
name="defaultTableSelectExpression"
700-
placeholder="TimeUnix, MetricName, Value, ServiceName, Attributes"
701-
connectionId={connectionId}
702-
/>
703-
</FormRow>
704-
<FormRow
705-
label={'Metric Name Column'}
706-
helpText="Column containing the name of the metric being measured"
707-
>
708-
<SQLInlineEditorControlled
709-
connectionId={connectionId}
710-
database={databaseName}
711-
table={tableName}
712-
control={control}
713-
name="metricNameExpression"
714-
placeholder="MetricName"
715-
/>
716-
</FormRow>
717-
<FormRow label={'Gauge Value Column'}>
718-
<SQLInlineEditorControlled
719-
connectionId={connectionId}
720-
database={databaseName}
721-
table={tableName}
722-
control={control}
723-
name="valueExpression"
724-
placeholder="Value"
725-
/>
726-
</FormRow>
727-
<Box>
728-
{!showOptionalFields && (
729-
<Anchor
730-
underline="always"
731-
onClick={() => setShowOptionalFields(true)}
732-
size="xs"
733-
c="gray.4"
734-
>
735-
<Text me="sm" span>
736-
<i className="bi bi-gear" />
737-
</Text>
738-
Configure Optional Fields
739-
</Anchor>
740-
)}
741-
{showOptionalFields && (
742-
<Button
743-
onClick={() => setShowOptionalFields(false)}
744-
size="xs"
745-
variant="subtle"
746-
color="gray.4"
747-
>
748-
Hide Optional Fields
749-
</Button>
750-
)}
751-
</Box>
752-
</Stack>
753-
<Stack
754-
gap="sm"
755-
style={{
756-
display: showOptionalFields ? 'flex' : 'none',
757-
}}
758-
>
759-
<Divider />
760-
<FormRow
761-
label={'Service Name Column'}
762-
helpText="Column containing the service name associated with the metric"
763-
>
764-
<SQLInlineEditorControlled
765-
connectionId={connectionId}
766-
database={databaseName}
767-
table={tableName}
768-
control={control}
769-
name="serviceNameExpression"
770-
placeholder="ServiceName"
771-
/>
772-
</FormRow>
773-
<FormRow
774-
label={'Resource Attributes Column'}
775-
helpText="Column containing resource attributes/tags associated with the metric"
776-
>
777-
<SQLInlineEditorControlled
778-
connectionId={connectionId}
779-
database={databaseName}
780-
table={tableName}
781-
control={control}
782-
name="resourceAttributesExpression"
783-
placeholder="ResourceAttributes"
784-
/>
785-
</FormRow>
786-
<FormRow
787-
label={'Metric Unit Column'}
788-
helpText="Column containing the unit of measurement for the metric"
789-
>
790-
<SQLInlineEditorControlled
791-
connectionId={connectionId}
792-
database={databaseName}
793-
table={tableName}
794-
control={control}
795-
name="metricUnitExpression"
796-
placeholder="MetricUnit"
797-
/>
798-
</FormRow>
799-
<FormRow
800-
label={'Metric Flag Column'}
801-
helpText="Column containing flags or markers associated with the metric"
802-
>
803-
<SQLInlineEditorControlled
804-
connectionId={connectionId}
805-
database={databaseName}
806-
table={tableName}
807-
control={control}
808-
name="flagsExpression"
809-
placeholder="Flags"
810-
/>
811-
</FormRow>
812-
<FormRow
813-
label={'Event Attributes Expression'}
814-
helpText="Column containing additional attributes/dimensions for the metric"
815-
>
816-
<SQLInlineEditorControlled
817-
connectionId={connectionId}
818-
database={databaseName}
819-
table={tableName}
820-
control={control}
821-
name="eventAttributesExpression"
822-
placeholder="Attributes"
823-
/>
824-
</FormRow>
700+
{Object.keys(MetricsDataType).map(metricType => (
701+
<FormRow
702+
key={metricType.toLowerCase()}
703+
label={`${metricType} Table`}
704+
helpText={`Table containing ${metricType.toLowerCase()} metrics data`}
705+
>
706+
<DBTableSelectControlled
707+
connectionId={connectionId}
708+
database={databaseName}
709+
control={control}
710+
name={`metricTables.${metricType.toLowerCase()}`}
711+
/>
712+
</FormRow>
713+
))}
825714
</Stack>
826715
</>
827716
);
@@ -1076,7 +965,7 @@ export function TableSourceForm({
1076965
<Radio value={SourceKind.Log} label="Log" />
1077966
<Radio value={SourceKind.Trace} label="Trace" />
1078967
{IS_METRICS_ENABLED && (
1079-
<Radio value={SourceKind.Metric} label="Metric" />
968+
<Radio value={SourceKind.Metric} label="OTEL Metrics" />
1080969
)}
1081970
{IS_SESSIONS_ENABLED && (
1082971
<Radio value={SourceKind.Session} label="Session" />

0 commit comments

Comments
 (0)