8000 feat(core): Make sure Supabase db query insights are populated (#16169) · getsentry/sentry-javascript@ff7ab11 · GitHub
[go: up one dir, main page]

Skip to content

Commit ff7ab11

Browse files
feat(core): Make sure Supabase db query insights are populated (#16169)
Co-authored-by: Onur Temizkan <onur@narval.co.uk>
1 parent 7116874 commit ff7ab11

File tree

4 files changed

+91
-37
lines changed

4 files changed

+91
-37
lines changed

dev-packages/browser-integration-tests/suites/integrations/supabase/auth/test.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -81,34 +81,40 @@ sentryTest('should capture Supabase authentication spans', async ({ getLocalTest
8181
const url = await getLocalTestUrl({ testDir: __dirname });
8282

8383
const eventData = await getFirstSentryEnvelopeRequest<Event>(page, url);
84-
const supabaseSpans = eventData.spans?.filter(({ op }) => op?.startsWith('db.auth'));
84+
const supabaseSpans = eventData.spans?.filter(({ op }) => op?.startsWith('db'));
8585

8686
expect(supabaseSpans).toHaveLength(2);
8787
expect(supabaseSpans![0]).toMatchObject({
88-
description: 'signInWithPassword',
88+
description: 'auth signInWithPassword',
89+
op: 'db',
8990
parent_span_id: eventData.contexts?.trace?.span_id,
9091
span_id: expect.any(String),
9192
start_timestamp: expect.any(Number),
9293
timestamp: expect.any(Number),
9394
trace_id: eventData.contexts?.trace?.trace_id,
9495
status: 'ok',
9596
data: expect.objectContaining({
96-
'sentry.op': 'db.auth.signInWithPassword',
97+
'sentry.op': 'db',
9798
'sentry.origin': 'auto.db.supabase',
99+
'db.operation': 'auth.signInWithPassword',
100+
'db.system': 'postgresql',
98101
}),
99102
});
100103

101104
expect(supabaseSpans![1]).toMatchObject({
102-
description: 'signOut',
105+
description: 'auth signOut',
106+
op: 'db',
103107
parent_span_id: eventData.contexts?.trace?.span_id,
104108
span_id: expect.any(String),
105109
start_timestamp: expect.any(Number),
106110
timestamp: expect.any(Number),
107111
trace_id: eventData.contexts?.trace?.trace_id,
108112
status: 'ok',
109113
data: expect.objectContaining({
110-
'sentry.op': 'db.auth.signOut',
114+
'sentry.op': 'db',
111115
'sentry.origin': 'auto.db.supabase',
116+
'db.operation': 'auth.signOut',
117+
'db.system': 'postgresql',
112118
}),
113119
});
114120
});
@@ -124,22 +130,25 @@ sentryTest('should capture Supabase authentication errors', async ({ getLocalTes
124130

125131
const [errorEvent, transactionEvent] = await getMultipleSentryEnvelopeRequests<Event>(page, 2, { url });
126132

127-
const supabaseSpans = transactionEvent.spans?.filter(({ op }) => op?.startsWith('db.auth'));
133+
const supabaseSpans = transactionEvent.spans?.filter(({ op }) => op?.startsWith('db'));
128134

129135
expect(errorEvent.exception?.values?.[0].value).toBe('Invalid email or password');
130136

131137
expect(supabaseSpans).toHaveLength(2);
132138
expect(supabaseSpans![0]).toMatchObject({
133-
description: 'signInWithPassword',
139+
description: 'auth signInWithPassword',
140+
op: 'db',
134141
parent_span_id: transactionEvent.contexts?.trace?.span_id,
135142
span_id: expect.any(String),
136143
start_timestamp: expect.any(Number),
137144
timestamp: expect.any(Number),
138145
trace_id: transactionEvent.contexts?.trace?.trace_id,
139146
status: 'unknown_error',
140147
data: expect.objectContaining({
141-
'sentry.op': 'db.auth.signInWithPassword',
148+
'sentry.op': 'db',
142149
'sentry.origin': 'auto.db.supabase',
150+
'db.operation': 'auth.signInWithPassword',
151+
'db.system': 'postgresql',
143152
}),
144153
});
145154
});

dev-packages/browser-integration-tests/suites/integrations/supabase/db-operations/test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,10 @@ sentryTest('should capture Supabase database operation breadcrumbs', async ({ ge
4444
timestamp: expect.any(Number),
4545
type: 'supabase',
4646
category: 'db.insert',
47-
message: 'from(todos)',
48-
data: expect.any(Object),
47+
message: 'insert(...) filter(columns, ) from(todos)',
48+
data: expect.objectContaining({
49+
query: expect.arrayContaining(['filter(columns, )']),
50+
}),
4951
});
5052
});
5153

dev-packages/e2e-tests/test-applications/supabase-nextjs/tests/performance.test.ts

Lines changed: 57 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,14 @@ test('Sends server-side Supabase auth admin `createUser` span', async ({ page, b
1717
const transactionEvent = await httpTransactionPromise;
1818

1919
expect(transactionEvent.spans).toContainEqual({
20-
data: expect.any(Object),
21-
description: 'createUser',
22-
op: 'db.auth.admin.createUser',
20+
data: expect.objectContaining({
21+
'db.operation': 'auth.admin.createUser',
22+
'db.system': 'postgresql',
23+
'sentry.op': 'db',
24+
'sentry.origin': 'auto.db.supabase',
25+
}),
26+
description: 'auth (admin) createUser',
27+
op: 'db',
2328
parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
2429
span_id: expect.stringMatching(/[a-f0-9]{16}/),
2530
start_timestamp: expect.any(Number),
@@ -54,8 +59,15 @@ test('Sends client-side Supabase db-operation spans and breadcrumbs to Sentry',
5459

5560
expect(transactionEvent.spans).toContainEqual(
5661
expect.objectContaining({
57-
description: 'from(todos)',
58-
op: 'db.select',
62+
description: 'select(*) filter(order, asc) from(todos)',
63+
op: 'db',
64+
data: expect.objectContaining({
65+
'db.operation': 'select',
66+
'db.query': ['select(*)', 'filter(order, asc)'],
67+
'db.system': 'postgresql',
68+
'sentry.op': 'db',
69+
'sentry.origin': 'auto.db.supabase',
70+
}),
5971
parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
6072
span_id: expect.stringMatching(/[a-f0-9]{16}/),
6173
start_timestamp: expect.any(Number),
@@ -67,9 +79,15 @@ test('Sends client-side Supabase db-operation spans and breadcrumbs to Sentry',
6779
);
6880

6981
expect(transactionEvent.spans).toContainEqual({
70-
data: expect.any(Object),
71-
description: 'from(todos)',
72-
op: 'db.insert',
82+
data: expect.objectContaining({
83+
'db.operation': 'select',
84+
'db.query': ['select(*)', 'filter(order, asc)'],
85+
'db.system': 'postgresql',
86+
'sentry.op': 'db',
87+
'sentry.origin': 'auto.db.supabase',
88+
}),
89+
description: 'select(*) filter(order, asc) from(todos)',
90+
op: 'db',
7391
parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
7492
span_id: expect.stringMatching(/[a-f0-9]{16}/),
7593
start_timestamp: expect.any(Number),
@@ -83,15 +101,15 @@ test('Sends client-side Supabase db-operation spans and breadcrumbs to Sentry',
83101
timestamp: expect.any(Number),
84102
type: 'supabase',
85103
category: 'db.select',
86-
message: 'from(todos)',
104+
message: 'select(*) filter(order, asc) from(todos)',
87105
data: expect.any(Object),
88106
});
89107

90108
expect(transactionEvent.breadcrumbs).toContainEqual({
91109
timestamp: expect.any(Number),
92110
type: 'supabase',
93111
category: 'db.insert',
94-
message: 'from(todos)',
112+
message: 'insert(...) select(*) from(todos)',
95113
data: expect.any(Object),
96114
});
97115
});
@@ -109,8 +127,15 @@ test('Sends server-side Supabase db-operation spans and breadcrumbs to Sentry',
109127

110128
expect(transactionEvent.spans).toContainEqual(
111129
expect.objectContaining({
112-
description: 'from(todos)',
113-
op: 'db.select',
130+
data: expect.objectContaining({
131+
'db.operation': 'insert',
132+
'db.query': ['select(*)'],
133+
'db.system': 'postgresql',
134+
'sentry.op': 'db',
135+
'sentry.origin': 'auto.db.supabase',
136+
}),
137+
description: 'insert(...) select(*) from(todos)',
138+
op: 'db',
114139
parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
115140
span_id: expect.stringMatching(/[a-f0-9]{16}/),
116141
start_timestamp: expect.any(Number),
@@ -122,9 +147,15 @@ test('Sends server-side Supabase db-operation spans and breadcrumbs to Sentry',
122147
);
123148

124149
expect(transactionEvent.spans).toContainEqual({
125-
data: expect.any(Object),
126-
description: 'from(todos)',
127-
op: 'db.insert',
150+
data: expect.objectContaining({
151+
'db.operation': 'select',
152+
'db.query': ['select(*)'],
153+
'db.system': 'postgresql',
154+
'sentry.op': 'db',
155+
'sentry.origin': 'auto.db.supabase',
156+
}),
157+
description: 'select(*) from(todos)',
158+
op: 'db',
128159
parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
129160
span_id: expect.stringMatching(/[a-f0-9]{16}/),
130161
start_timestamp: expect.any(Number),
@@ -138,34 +169,38 @@ test('Sends server-side Supabase db-operation spans and breadcrumbs to Sentry',
138169
timestamp: expect.any(Number),
139170
type: 'supabase',
140171
category: 'db.select',
141-
message: 'from(todos)',
172+
message: 'select(*) from(todos)',
142173
data: expect.any(Object),
143174
});
144175

145176
expect(transactionEvent.breadcrumbs).toContainEqual({
146177
timestamp: expect.any(Number),
147178
type: 'supabase',
148179
category: 'db.insert',
149-
message: 'from(todos)',
180+
message: 'insert(...) select(*) from(todos)',
150181
data: expect.any(Object),
151182
});
152183
});
153184

154185
test('Sends server-side Supabase auth admin `listUsers` span', async ({ page, baseURL }) => {
155186
const httpTransactionPromise = waitForTransaction('supabase-nextjs', transactionEvent => {
156187
return (
157-
transactionEvent?.contexts?.trace?.op === 'http.server' &&
158-
transactionEvent?.transaction === 'GET /api/list-users'
188+
transactionEvent?.contexts?.trace?.op === 'http.server' && transactionEvent?.transaction === 'GET /api/list-users'
159189
);
160190
});
161191

162192
await fetch(`${baseURL}/api/list-users`);
163193
const transactionEvent = await httpTransactionPromise;
164194

165195
expect(transactionEvent.spans).toContainEqual({
166-
data: expect.any(Object),
167-
description: 'listUsers',
168-
op: 'db.auth.admin.listUsers',
196+
data: expect.objectContaining({
197+
'db.operation': 'auth.admin.listUsers',
198+
'db.system': 'postgresql',
199+
'sentry.op': 'db',
200+
'sentry.origin': 'auto.db.supabase',
201+
}),
202+
description: 'auth (admin) listUsers',
203+
op: 'db',
169204
parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
170205
span_id: expect.stringMatching(/[a-f0-9]{16}/),
171206
start_timestamp: expect.any(Number),

packages/core/src/integrations/supabase.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -219,10 +219,12 @@ function instrumentAuthOperation(operation: AuthOperationFn, isAdmin = false): A
219219
apply(target, thisArg, argumentsList) {
220220
return startSpan(
221221
{
222-
name: operation.name,
222+
name: `auth ${isAdmin ? '(admin) ' : ''}${operation.name}`,
223223
attributes: {
224224
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.db.supabase',
225-
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: `db.auth.${isAdmin ? 'admin.' : ''}${operation.name}`,
225+
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'db',
226+
'db.system': 'postgresql',
227+
'db.operation': `auth.${isAdmin ? 'admin.' : ''}${operation.name}`,
226228
},
227229
},
228230
span => {
@@ -341,30 +343,36 @@ function instrumentPostgRESTFilterBuilder(PostgRESTFilterBuilder: PostgRESTFilte
341343

342344
const pathParts = typedThis.url.pathname.split('/');
343345
const table = pathParts.length > 0 ? pathParts[pathParts.length - 1] : '';
344-
const description = `from(${table})`;
345346

346347
const queryItems: string[] = [];
347348
for (const [key, value] of typedThis.url.searchParams.entries()) {
348349
// It's possible to have multiple entries for the same key, eg. `id=eq.7&id=eq.3`,
349350
// so we need to use array instead of object to collect them.
350351
queryItems.push(translateFiltersIntoMethods(key, value));
351352
}
352-
353353
const body: Record<string, unknown> = Object.create(null);
354354
if (isPlainObject(typedThis.body)) {
355355
for (const [key, value] of Object.entries(typedThis.body)) {
356356
body[key] = value;
357357
}
358358
}
359359

360+
// Adding operation to the beginning of the description if it's not a `select` operation
361+
// For example, it can be an `insert` or `update` operation but the query can be `select(...)`
362+
// For `select` operations, we don't need repeat it in the description
363+
const description = `${operation === 'select' ? '' : `${operation}${body ? '(...) ' : ''}`}${queryItems.join(
364+
' ',
365+
)} from(${table})`;
366+
360367
const attributes: Record<string, any> = {
361368
'db.table': table,
362369
'db.schema': typedThis.schema,
363370
'db.url': typedThis.url.origin,
364371
'db.sdk': typedThis.headers['X-Client-Info'],
365372
'db.system': 'postgresql',
373+
'db.operation': operation,
366374
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.db.supabase',
367-
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: `db.${operation}`,
375+
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'db',
368376
};
369377

370378
if (queryItems.length) {

0 commit comments

Comments
 (0)
0