-
Notifications
You must be signed in to change notification settings - Fork 311
refactor: unify source types and remove ambiguity #1194
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
E2E Test Results✅ All tests passed • 21 passed • 3 skipped • 151s
|
export function createSource(team: string, source: Omit<ISource, 'id'>) { | ||
return Source.create({ ...source, team }); | ||
switch (source.kind) { | ||
case SourceKind.Log: | ||
return LogSource.create({ ...source, team }); | ||
case SourceKind.Trace: | ||
return TraceSource.create({ ...source, team }); | ||
case SourceKind.Metric: | ||
return MetricSource.create({ ...source, team }); | ||
case SourceKind.Session: | ||
return SessionSource.create({ ...source, team }); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mongoose will strip out any unnecessary fields, so create source must use the new broken out discriminated models
switch (source.kind) { | ||
case SourceKind.Log: | ||
return LogSource.findOneAndUpdate({ _id: sourceId, team }, source, { | ||
new: true, | ||
}); | ||
case SourceKind.Trace: | ||
return TraceSource.findOneAndUpdate({ _id: sourceId, team }, source, { | ||
new: true, | ||
}); | ||
case SourceKind.Metric: | ||
return MetricSource.findOneAndUpdate({ _id: sourceId, team }, source, { | ||
new: true, | ||
}); | ||
case SourceKind.Session: | ||
return SessionSource.findOneAndUpdate({ _id: sourceId, team }, source, { | ||
new: true, | ||
}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same, using discriminated models
durationPrecision: Number, | ||
parentSpanIdExpression: String, | ||
spanNameExpression: String, | ||
export const LogSource = Source.discriminator< |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Broke out the mongoose models into the base model (Source
) and a discriminated model for each type
@@ -1,10 +1,10 @@ | |||
import { SourceKind, TSourceUnion } from '@hyperdx/common-utils/dist/types'; | |||
import { SourceKind, TSource } from '@hyperdx/common-utils/dist/types'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TSourceUnion is renamed TSource
export function useSource(params: { | ||
id?: string | null; | ||
connection?: string | null; | ||
}): UseQueryResult<TSource | undefined>; | ||
export function useSource<K extends TSource['kind']>(params: { | ||
id?: string | null; | ||
connection?: string | null; | ||
kind: K; | ||
}): UseQueryResult<Extract<TSource, { kind: K }> | undefined>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The type signature without specifying kind returns the TSource
type, but if you specify kind
then the type signature understands the returned source is a particular kind. Super useful if you always know you'll be using a specific kind of source
mutationFn: async ({ source }: { source: Omit<TSource, 'id'> }) => { | ||
mutationFn: async ({ source }: { source: TSourceNoId }) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TS's built in Omit
does not work well with discriminated unions, so it's better to construct with zod and type assert.
The refactoring makes me a bit nervous, as the changes affect the core of the entire app and I’m not very confident in our test coverage in this area. To minimize the risk of breaking existing behavior, I’d suggest migrating to the new sources model gradually, one source type at a time. If it’s not that easy, I’d recommend adding more unit tests for different sources. Overall, I really like these changes, which will help us catch more source type–related bugs :) |
return LogSource.create({ ...source, team }); | ||
case SourceKind.Trace: | ||
return TraceSource.create({ ...source, team }); | ||
case SourceKind.Metric: | ||
return MetricSource.create({ ...source, team }); | ||
case SourceKind.Session: | ||
return SessionSource.create({ ...source, team }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should also handle default case
export const SessionSource = Source.discriminator< | ||
Extract<TSource, { kind: SourceKind.Session }> | ||
>( | ||
SourceKind.Session, | ||
new mongoose.Schema<Extract<TSource, { kind: SourceKind.Session }>>({ | ||
traceSourceId: String, | ||
}), | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will those non-public fields (e.g., ResourceAttributes) be inserted or updated?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's talk about the requirements for Sessions, because ResourceAttributes
isn't currently listed in the schema
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm you are right. Sorry, I was thinking about the different thing. The mapping should only require a few fields
case SourceKind.Log: | ||
return s.toJSON({ getters: true }); | ||
case SourceKind.Trace: | ||
return s.toJSON({ getters: true }); | ||
case SourceKind.Metric: | ||
return s.toJSON({ getters: true }); | ||
case SourceKind.Session: | ||
return s.toJSON({ getters: true }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: this can be
case SourceKind.Log:
case SourceKind.Trace:
case SourceKind.Metric:
case SourceKind.Session:
return s.toJSON({ getters: true });
also we need to handle default case
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For some reason Typescript gets confused about the union so that does not work, it has to be broken out like this. I'll add an error for the default case
const { data: logSource, isLoading: isLogSourceLoading } = useSource({ | ||
id: source, | ||
kind: SourceKind.Log, | ||
}); | ||
const { data: traceSource, isLoading: isTraceSourceLoading } = useSource({ | ||
id: source, | ||
kind: SourceKind.Trace, | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any chance we could avoid duplicate requests here? I see the point, but the source ID should already give us the right source
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Under the hood they all are using the same query key to fetch all the sources and then just running find
over the elements of the array. They are using the same query key, so only one request should be generated
Unfortunately it is not easy. More unit tests makes sense, I'll seek guidance for which ones specifically we are interested in |
Closes HDX-2468