Skip to content

Commit

Permalink
Refactor from schema to use centralized event factories
Browse files Browse the repository at this point in the history
  • Loading branch information
RXminuS committed Sep 13, 2024
1 parent f853036 commit ec7b187
Show file tree
Hide file tree
Showing 22 changed files with 1,659 additions and 52 deletions.
3 changes: 2 additions & 1 deletion biome.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@
"**/fixtures/**",
"!vscode/test/fixtures/mock-server.ts",
"vscode/.schema-cache/*.json",
"vscode/e2e/utils/vscody/resources/**/*"
"vscode/e2e/utils/vscody/resources/**/*",
"*.snap.json"
]
},
"overrides": [
Expand Down
3 changes: 2 additions & 1 deletion lib/shared/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"@types/lodash": "^4.14.195",
"@types/node-fetch": "^2.6.4",
"@types/semver": "^7.5.0",
"@types/vscode": "^1.80.0"
"@types/vscode": "^1.80.0",
"type-fest": "^4.25.0"
}
}
1 change: 1 addition & 0 deletions lib/shared/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ export {
} from './telemetry-v2/TelemetryRecorderProvider'
export type { TelemetryRecorder } from './telemetry-v2/TelemetryRecorderProvider'
export * from './telemetry-v2/singleton'
export { events as telemetryEvents } from './telemetry-v2/events'
export { testFileUri } from './test/path-helpers'
export * from './tracing'
export {
Expand Down
2 changes: 1 addition & 1 deletion lib/shared/src/telemetry-v2/TelemetryRecorderProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export class TelemetryRecorderProvider extends BaseTelemetryRecorderProvider<
],
{
...defaultEventRecordingOptions,
bufferTimeMs: 0, // disable buffering for now
bufferTimeMs: 0, // disable buffering for now. If this is enabled make sure that it can be disabled during tests.
}
)
}
Expand Down
38 changes: 38 additions & 0 deletions lib/shared/src/telemetry-v2/events/atMention.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { telemetryRecorder } from '../singleton'
import { type Unmapped, event, fallbackValue, pickDefined } from './internal'

export const events = [
event(
'cody.at-mention/selected',
({ map, maps, action, feature }) =>
(
source: Unmapped<typeof maps.source, true>,
provider: Unmapped<typeof maps.provider> | undefined | null = undefined,
providerMetadata: { id?: string } | undefined = undefined
) => {
telemetryRecorder.recordEvent(feature, action, {
metadata: pickDefined({
source: map.source(source),
provider:
provider !== undefined ? map.provider(provider ?? fallbackValue) : undefined,
}),
privateMetadata: { source, provider, providerMetadata },
billingMetadata: {
product: 'cody',
category: 'core',
},
})
},
{
source: { chat: 1 },
provider: {
[fallbackValue]: 0,
file: 1,
symbol: 2,
repo: 3,
remoteRepo: 4,
openctx: 5,
},
}
),
] as const
12 changes: 12 additions & 0 deletions lib/shared/src/telemetry-v2/events/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { events as atMentionEvents } from './atMention'

export const events = objectFromPairs([...atMentionEvents])

// Function to create an object from pairs with correct typing
type FromPairs<T extends readonly (readonly [PropertyKey, any])[]> = {
[K in T[number][0]]: Extract<T[number], readonly [K, any]>[1]
}

function objectFromPairs<T extends readonly (readonly [PropertyKey, any])[]>(pairs: T): FromPairs<T> {
return Object.fromEntries(pairs) as FromPairs<T>
}
105 changes: 105 additions & 0 deletions lib/shared/src/telemetry-v2/events/internal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import type { LiteralUnion } from 'type-fest'
declare const fallbackVariant: unique symbol
// we disguise the fallbackValue as a tagged string so that it can be exported as a type
export const fallbackValue = '__fallback__' as const
export type MapperInput = { [key: string]: number } & (
| { [fallbackValue]: number }
| { [fallbackValue]?: never }
)

export type MapperInputs = Record<string, MapperInput>

type ObjWithFallback<T extends Record<string, number>> = T & {
[fallbackValue]?: number
}
type KeyOfOmitFallback<T> = T extends ObjWithFallback<infer U> ? keyof U : never

type HasFallback<M extends MapperInput> = M extends { [fallbackValue]: infer V }
? V extends number
? true
: false
: false
export type MapperFn<M extends MapperInput> = HasFallback<M> extends true
? (v: LiteralUnion<KeyOfOmitFallback<M>, string>) => number
: <
V extends LiteralUnion<KeyOfOmitFallback<M>, string> = LiteralUnion<
KeyOfOmitFallback<M>,
string
>,
>(
v: V
) => V extends KeyOfOmitFallback<M> ? number : null

export type MapperFns<M extends MapperInputs> = {
[K in keyof M]: MapperFn<M[K]>
}
export type Unmapped<M extends MapperInput, Strict extends boolean = false> = Strict extends true
? KeyOfOmitFallback<M>
: LiteralUnion<KeyOfOmitFallback<M>, string>

type SplitSignature<S extends string, D extends string = '/'> = string extends S
? string[]
: S extends ''
? []
: S extends `${infer T}${D}${infer U}`
? [T, ...SplitSignature<U, D>]
: [S]

function splitSignature<const S extends string>(sig: S): SplitSignature<S, '/'> {
return sig.split('/') as SplitSignature<S>
}

export function event<
Signature extends `${string}/${string}`,
M extends MapperInputs,
Args extends readonly unknown[],
>(
featureAction: Signature,
builder: (ctx: {
maps: M
map: MapperFns<M>
feature: SplitSignature<Signature>[0]
action: SplitSignature<Signature>[1]
}) => (...args: Args) => void,
maps: M
) {
const [feature, action] = splitSignature(featureAction)
//TODO: Make type-safe
const map = new Proxy(maps, {
get(target, p) {
const mapping = target[p as keyof M]
return (v: any) => mapping[v] ?? mapping[fallbackValue] ?? null
},
}) as unknown as MapperFns<M>
const ctx = {
map,
maps,
featureAction,
feature,
action,
}
const out = {
ctx,
record: builder(ctx),
} as const
return [featureAction, out] as const
}

type PickDefined<T> = {
[K in keyof T]-?: T[K] extends undefined ? never : T[K] extends infer U | undefined ? U : T[K]
}

/**
* Only omit undefined keys. Null keys are not omitted so that the mappers still
* give type errors in case no default value is provided
*/
export function pickDefined<T>(obj: T): PickDefined<T> {
const result: any = {}
for (const key in obj) {
const value = obj[key]
if (value !== undefined) {
result[key] = value
}
}
return result
}
10 changes: 10 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit ec7b187

Please sign in to comment.