Skip to content

Commit a1c22c0

Browse files
committed
fix: SpanMatcherFn doesn't break inference
1 parent 161f3d9 commit a1c22c0

File tree

4 files changed

+122
-21
lines changed

4 files changed

+122
-21
lines changed

src/v3/matchSpan.ts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,15 @@ export interface Context<
2828
/**
2929
* Function type for matching performance entries.
3030
*/
31-
export type SpanMatcherFn<
31+
export interface SpanMatcherFn<
3232
TracerScopeKeysT extends KeysOfUnion<AllPossibleScopesT>,
3333
AllPossibleScopesT,
34-
> = ((
35-
spanAndAnnotation: SpanAndAnnotation<AllPossibleScopesT>,
36-
context: Context<TracerScopeKeysT, AllPossibleScopesT>,
37-
) => boolean) &
38-
SpanMatcherTags
34+
> extends SpanMatcherTags {
35+
(
36+
spanAndAnnotation: SpanAndAnnotation<AllPossibleScopesT>,
37+
context: Context<TracerScopeKeysT, AllPossibleScopesT>,
38+
): boolean
39+
}
3940

4041
type NameMatcher<TracerScopeT> =
4142
| string
@@ -132,11 +133,14 @@ export function withAttributes<
132133
* A list of scope keys to match against the span.
133134
*/
134135
export function withMatchingScopes<
135-
TracerScopeKeysT extends KeysOfUnion<AllPossibleScopesT>,
136-
AllPossibleScopesT,
136+
const TracerScopeKeysT extends KeysOfUnion<AllPossibleScopesT>,
137+
const AllPossibleScopesT,
137138
>(
138-
keys: NoInfer<readonly TracerScopeKeysT[]> | true = true,
139-
): SpanMatcherFn<TracerScopeKeysT, AllPossibleScopesT> {
139+
keys: readonly NoInfer<TracerScopeKeysT>[] | true = true,
140+
): SpanMatcherFn<
141+
TracerScopeKeysT & KeysOfUnion<AllPossibleScopesT>,
142+
AllPossibleScopesT
143+
> {
140144
return ({ span }, { input: { scope }, definition: { scopes } }) => {
141145
if (!span.scope) return false
142146
const spanScope = span.scope as AllPossibleScopesT & object

src/v3/test/types.test.ts

Lines changed: 94 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,46 @@ interface ExampleUserScope {
1111
userId: string
1212
}
1313

14+
interface ExampleTicketEventScope {
15+
ticketId: string
16+
eventId: string
17+
}
18+
1419
interface ExampleCustomFieldScope {
1520
customFieldId: string
1621
}
1722

23+
interface ExampleScopeWithMultipleKeys {
24+
customId: string
25+
customOtherId: string
26+
}
27+
28+
interface TicketFieldScope {
29+
ticketId: string
30+
ticketFieldType: 'dropdown'
31+
}
32+
interface TicketAppScope {
33+
ticketId: string
34+
appId: string
35+
}
36+
1837
type ExampleAllPossibleScopes =
1938
| ExampleTicketScope
2039
| ExampleUserScope
2140
| ExampleCustomFieldScope
41+
| ExampleScopeWithMultipleKeys
42+
| ExampleTicketEventScope
43+
44+
// TODO:
45+
// // Helper type to ensure we get a tuple instead of an array type
46+
// type ToTuple<T extends any[]> = [...T];
47+
48+
// // Main type that transforms union of objects to union of tuples of keys
49+
// type ObjectKeysToTuple<T> = T extends object
50+
// ? ToTuple<keyof T extends infer K ? K extends PropertyKey ? [K] : never : never>
51+
// : never;
52+
53+
// type X = ObjectKeysToTuple<ExampleAllPossibleScopes> // ["ticketId"] | ["userId"] | ["customFieldId"] | ["customId", "customOtherId"] | ["ticketId", "eventId"]
2254

2355
const mockSpanWithoutScope = {
2456
name: 'some-span',
@@ -92,21 +124,24 @@ describe.skip('type tests', () => {
92124
const ticketActivationTracer = traceManager.createTracer({
93125
name: 'ticket.activation',
94126
scopes: ['ticketId'],
127+
// scope: 'global',
95128
requiredToEnd: [{ matchScopes: ['ticketId'] }],
96129
})
97130

98131
const ticketActivationTracer2 = traceManager.createTracer({
99132
name: 'ticket.activation',
100-
scopes: ['ticketId'],
133+
scopes: ['customId', 'customOtherId'],
101134
requiredToEnd: [
102-
// match.withAllConditions(
103-
// match.withName((name, scopes) => name === `${scopes.ticketId}.end`),
104-
// // match.withName('end'),
105-
// // match.withMatchingScopes(['ticketId']),
106-
// ),
107-
// match.withName((name, scopes) => name === `${scopes.ticketId}.end`),
108-
// match.withName('customFieldId'),
109-
match.withMatchingScopes(['customFieldId']),
135+
match.withAllConditions(
136+
match.withName((name, scopes) => name === `${scopes.customId}.end`),
137+
match.withName('end'),
138+
match.withMatchingScopes(['customId']),
139+
),
140+
match.withName((name, scopes) => name === `${scopes.customId}.end`),
141+
match.withName('customFieldId'),
142+
match.withMatchingScopes(['customId']),
143+
// @ts-expect-error invalid scope
144+
match.withMatchingScopes(['sticketId']),
110145
],
111146
})
112147

@@ -232,6 +267,56 @@ describe.skip('type tests', () => {
232267
})
233268
})
234269

270+
it('mixed scopes', () => {
271+
const tracer = traceManager.createTracer({
272+
name: 'ticket.scope-operation',
273+
type: 'operation',
274+
scopes: ['ticketId', 'customFieldId'],
275+
timeoutDuration: 5_000,
276+
requiredToEnd: [{ name: 'end', matchScopes: true }],
277+
})
278+
const traceId = tracer.start({
279+
scope: {
280+
customFieldId: '3',
281+
ticketId: '4',
282+
},
283+
})
284+
})
285+
286+
it('redaction example', () => {
287+
const tracer = traceManager.createTracer({
288+
name: 'ticket.event.redacted',
289+
type: 'operation',
290+
scopes: ['ticketId', 'eventId'],
291+
timeoutDuration: 5_000,
292+
requiredToEnd: [{ name: 'OmniLogEvent', matchScopes: true }],
293+
debounceOn: [{ name: 'OmniLog', matchScopes: ['ticketId'] }],
294+
})
295+
const traceId = tracer.start({
296+
scope: {
297+
ticketId: '4',
298+
eventId: '3',
299+
},
300+
})
301+
})
302+
303+
it('redaction invalid example', () => {
304+
const tracer = traceManager.createTracer({
305+
name: 'ticket.event.redacted',
306+
type: 'operation',
307+
// @ts-expect-error enforce a complete set of keys of a given scope
308+
scopes: ['eventId'],
309+
timeoutDuration: 5_000,
310+
requiredToEnd: [{ name: 'OmniLogEvent', matchScopes: true }],
311+
})
312+
const traceId = tracer.start({
313+
scope: {
314+
ticketId: '4',
315+
eventId: '3',
316+
},
317+
})
318+
})
319+
235320
it('does not allow to include invalid scope key', () => {
236321
const tracer = traceManager.createTracer({
237322
name: 'ticket.scope-operation',

src/v3/traceManager.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { ensureTimestamp } from './ensureTimestamp'
55
import { createQuietWindowDurationCalculator } from './getDynamicQuietWindowDuration'
66
import * as matchSpan from './matchSpan'
77
import type { Span } from './spanTypes'
8+
import { shouldCompleteAndHaveInteractiveTime } from './test/fixtures/shouldCompleteAndHaveInteractiveTime'
9+
import { shouldNotEndWithInteractiveTimeout } from './test/fixtures/shouldNotEndWithInteractiveTimeout'
810
import {
911
type TicketIdScope,
1012
ticketActivationDefinition,
@@ -17,8 +19,6 @@ import {
1719
Render,
1820
} from './test/makeTimeline'
1921
import processSpans from './test/processSpans'
20-
import { shouldCompleteAndHaveInteractiveTime } from './test/fixtures/shouldCompleteAndHaveInteractiveTime'
21-
import { shouldNotEndWithInteractiveTimeout } from './test/fixtures/shouldNotEndWithInteractiveTimeout'
2222
// import { shouldNotEndWithInterruption } from './test/fixtures/shouldNotEndWithInterruption'
2323
import { TraceManager } from './traceManager'
2424
import type { ReportFn } from './types'

src/v3/types.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,18 @@ export interface ComputedValueDefinitionInput<
321321
) => number | string | boolean
322322
}
323323

324+
// export type SelectScopeByKey<
325+
// SelectScopeKeyT extends keyof ScopesT,
326+
// ScopesT,
327+
// > = Prettify<
328+
// ScopesT extends (
329+
// SelectScopeKeyT extends SelectScopeKeyT
330+
// ? { [AnyKey in SelectScopeKeyT]: ScopeValue }
331+
// : never
332+
// )
333+
// ? ScopesT
334+
// : never
335+
// >
324336
export type SelectScopeByKey<
325337
SelectScopeKeyT extends keyof ScopesT,
326338
ScopesT,

0 commit comments

Comments
 (0)