From 4694cfbac09e98888b12bccdc324fa422e3c9f4e Mon Sep 17 00:00:00 2001 From: Daniel Tijerina Date: Fri, 21 Nov 2025 14:43:50 -0600 Subject: [PATCH 1/4] Add support for persisted GraphQL Queries --- .../src/domain/resource/graphql.spec.ts | 19 +++++++++++++++++++ .../rum-core/src/domain/resource/graphql.ts | 16 ++++++++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/packages/rum-core/src/domain/resource/graphql.spec.ts b/packages/rum-core/src/domain/resource/graphql.spec.ts index 1ae18ba17b..dbb93f129d 100644 --- a/packages/rum-core/src/domain/resource/graphql.spec.ts +++ b/packages/rum-core/src/domain/resource/graphql.spec.ts @@ -142,6 +142,25 @@ describe('GraphQL detection and metadata extraction', () => { const result = extractGraphQlRequestMetadata(requestBody, true) expect(result).toBeUndefined() }) + + it('should extract query operation name and variables for persisted queries', () => { + const requestBody = JSON.stringify({ + extensions: { + persistedQuery: { + sha256Hash: 'somehashvalue', + }, + }, + operationName: 'GetUser', + variables: { id: '123' }, + }) + + const result = extractGraphQlRequestMetadata(requestBody, true) + + expect(result).toEqual({ + operationName: 'GetUser', + variables: '{"id":"123"}', + }) + }) }) describe('request payload truncation', () => { diff --git a/packages/rum-core/src/domain/resource/graphql.ts b/packages/rum-core/src/domain/resource/graphql.ts index 1f622c2d09..8cf77ee0ed 100644 --- a/packages/rum-core/src/domain/resource/graphql.ts +++ b/packages/rum-core/src/domain/resource/graphql.ts @@ -15,7 +15,7 @@ export interface GraphQlError { } export interface GraphQlMetadata { - operationType: 'query' | 'mutation' | 'subscription' + operationType?: 'query' | 'mutation' | 'subscription' operationName?: string variables?: string payload?: string @@ -88,7 +88,12 @@ export function extractGraphQlRequestMetadata( return } - let graphqlBody: { query?: string; operationName?: string; variables?: unknown } + let graphqlBody: { + query?: string + operationName?: string + variables?: unknown + extensions?: { persistedQuery?: unknown } + } try { graphqlBody = JSON.parse(requestBody) @@ -97,6 +102,13 @@ export function extractGraphQlRequestMetadata( return } + if (graphqlBody && graphqlBody.extensions?.persistedQuery) { + return { + operationName: graphqlBody.operationName, + variables: graphqlBody.variables ? JSON.stringify(graphqlBody.variables) : undefined, + } + } + if (!graphqlBody || !graphqlBody.query) { return } From e734b1287111f5d82f9e2b963d300fa22061adbc Mon Sep 17 00:00:00 2001 From: "roman.gaignault" Date: Wed, 3 Dec 2025 11:23:51 +0100 Subject: [PATCH 2/4] fix: preserve full GraphQL metadata when both query and persistedQuery are present --- .../src/domain/resource/graphql.spec.ts | 24 ++++++++++++++++++- .../rum-core/src/domain/resource/graphql.ts | 2 +- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/packages/rum-core/src/domain/resource/graphql.spec.ts b/packages/rum-core/src/domain/resource/graphql.spec.ts index dbb93f129d..080024950a 100644 --- a/packages/rum-core/src/domain/resource/graphql.spec.ts +++ b/packages/rum-core/src/domain/resource/graphql.spec.ts @@ -143,7 +143,7 @@ describe('GraphQL detection and metadata extraction', () => { expect(result).toBeUndefined() }) - it('should extract query operation name and variables for persisted queries', () => { + it('should extract query operation name and variables for persisted queries without query', () => { const requestBody = JSON.stringify({ extensions: { persistedQuery: { @@ -161,6 +161,28 @@ describe('GraphQL detection and metadata extraction', () => { variables: '{"id":"123"}', }) }) + + it('should extract full metadata when both query and persistedQuery are present', () => { + const requestBody = JSON.stringify({ + query: 'query GetUser { user { id name } }', + extensions: { + persistedQuery: { + sha256Hash: 'somehashvalue', + }, + }, + operationName: 'GetUser', + variables: { id: '123' }, + }) + + const result = extractGraphQlRequestMetadata(requestBody, true) + + expect(result).toEqual({ + operationType: 'query', + operationName: 'GetUser', + variables: '{"id":"123"}', + payload: 'query GetUser { user { id name } }', + }) + }) }) describe('request payload truncation', () => { diff --git a/packages/rum-core/src/domain/resource/graphql.ts b/packages/rum-core/src/domain/resource/graphql.ts index 8cf77ee0ed..e919bf66a5 100644 --- a/packages/rum-core/src/domain/resource/graphql.ts +++ b/packages/rum-core/src/domain/resource/graphql.ts @@ -102,7 +102,7 @@ export function extractGraphQlRequestMetadata( return } - if (graphqlBody && graphqlBody.extensions?.persistedQuery) { + if (graphqlBody && graphqlBody.extensions?.persistedQuery && !graphqlBody.query) { return { operationName: graphqlBody.operationName, variables: graphqlBody.variables ? JSON.stringify(graphqlBody.variables) : undefined, From 02e0cd8c4f1b0cf4abff5cf405dce654f2ebb8cd Mon Sep 17 00:00:00 2001 From: "roman.gaignault" Date: Wed, 3 Dec 2025 13:43:46 +0100 Subject: [PATCH 3/4] =?UTF-8?q?=E2=9C=A8=20Add=20support=20for=20persisted?= =?UTF-8?q?=20GraphQL=20queries?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/domain/resource/graphql.spec.ts | 25 +++++++++++++++--- .../rum-core/src/domain/resource/graphql.ts | 26 ++++++++----------- 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/packages/rum-core/src/domain/resource/graphql.spec.ts b/packages/rum-core/src/domain/resource/graphql.spec.ts index 080024950a..935ec828ef 100644 --- a/packages/rum-core/src/domain/resource/graphql.spec.ts +++ b/packages/rum-core/src/domain/resource/graphql.spec.ts @@ -98,10 +98,15 @@ describe('GraphQL detection and metadata extraction', () => { expect(result).toBeUndefined() }) - it('should return undefined for non-GraphQL request body', () => { + it('should return metadata with undefined fields for non-GraphQL request body', () => { const requestBody = JSON.stringify({ data: 'some data' }) const result = extractGraphQlRequestMetadata(requestBody, true) - expect(result).toBeUndefined() + expect(result).toEqual({ + operationType: undefined, + operationName: undefined, + variables: undefined, + payload: undefined, + }) }) it('should handle GraphQL queries with leading and trailing whitespace', () => { @@ -129,7 +134,12 @@ describe('GraphQL detection and metadata extraction', () => { }) const result = extractGraphQlRequestMetadata(requestBody, true) - expect(result).toBeUndefined() + expect(result).toEqual({ + operationType: undefined, + operationName: 'GetUser', + variables: '{"id":"123"}', + payload: '{ user { id name } }', + }) }) it('should return undefined for queries with invalid operation type', () => { @@ -140,7 +150,12 @@ describe('GraphQL detection and metadata extraction', () => { }) const result = extractGraphQlRequestMetadata(requestBody, true) - expect(result).toBeUndefined() + expect(result).toEqual({ + operationType: undefined, + operationName: 'GetUser', + variables: '{"id":"123"}', + payload: 'invalid GetUser { user { id name } }', + }) }) it('should extract query operation name and variables for persisted queries without query', () => { @@ -157,8 +172,10 @@ describe('GraphQL detection and metadata extraction', () => { const result = extractGraphQlRequestMetadata(requestBody, true) expect(result).toEqual({ + operationType: undefined, operationName: 'GetUser', variables: '{"id":"123"}', + payload: undefined, }) }) diff --git a/packages/rum-core/src/domain/resource/graphql.ts b/packages/rum-core/src/domain/resource/graphql.ts index e919bf66a5..18f7a4b089 100644 --- a/packages/rum-core/src/domain/resource/graphql.ts +++ b/packages/rum-core/src/domain/resource/graphql.ts @@ -92,7 +92,6 @@ export function extractGraphQlRequestMetadata( query?: string operationName?: string variables?: unknown - extensions?: { persistedQuery?: unknown } } try { @@ -102,25 +101,22 @@ export function extractGraphQlRequestMetadata( return } - if (graphqlBody && graphqlBody.extensions?.persistedQuery && !graphqlBody.query) { - return { - operationName: graphqlBody.operationName, - variables: graphqlBody.variables ? JSON.stringify(graphqlBody.variables) : undefined, - } - } - - if (!graphqlBody || !graphqlBody.query) { + if (!graphqlBody) { return } - const query = graphqlBody.query.trim() - const operationType = getOperationType(query) - const operationName = graphqlBody.operationName + let operationType: 'query' | 'mutation' | 'subscription' | undefined + let payload: string | undefined - if (!operationType) { - return + if (graphqlBody.query) { + const trimmedQuery = graphqlBody.query.trim() + operationType = getOperationType(trimmedQuery) + if (trackPayload) { + payload = safeTruncate(trimmedQuery, GRAPHQL_PAYLOAD_LIMIT, '...') + } } + const operationName = graphqlBody.operationName let variables: string | undefined if (graphqlBody.variables) { variables = JSON.stringify(graphqlBody.variables) @@ -130,7 +126,7 @@ export function extractGraphQlRequestMetadata( operationType, operationName, variables, - payload: trackPayload ? safeTruncate(query, GRAPHQL_PAYLOAD_LIMIT, '...') : undefined, + payload, } } From e3e23d22375d9a2797ad15e2be5e8193474b9635 Mon Sep 17 00:00:00 2001 From: "roman.gaignault" Date: Mon, 8 Dec 2025 13:49:28 +0100 Subject: [PATCH 4/4] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Remove=20unnecessary?= =?UTF-8?q?=20persistedQuery=20from=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/domain/resource/graphql.spec.ts | 29 +------------------ 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/packages/rum-core/src/domain/resource/graphql.spec.ts b/packages/rum-core/src/domain/resource/graphql.spec.ts index 935ec828ef..3211a846ab 100644 --- a/packages/rum-core/src/domain/resource/graphql.spec.ts +++ b/packages/rum-core/src/domain/resource/graphql.spec.ts @@ -158,13 +158,8 @@ describe('GraphQL detection and metadata extraction', () => { }) }) - it('should extract query operation name and variables for persisted queries without query', () => { + it('should extract operation name and variables when query is absent', () => { const requestBody = JSON.stringify({ - extensions: { - persistedQuery: { - sha256Hash: 'somehashvalue', - }, - }, operationName: 'GetUser', variables: { id: '123' }, }) @@ -178,28 +173,6 @@ describe('GraphQL detection and metadata extraction', () => { payload: undefined, }) }) - - it('should extract full metadata when both query and persistedQuery are present', () => { - const requestBody = JSON.stringify({ - query: 'query GetUser { user { id name } }', - extensions: { - persistedQuery: { - sha256Hash: 'somehashvalue', - }, - }, - operationName: 'GetUser', - variables: { id: '123' }, - }) - - const result = extractGraphQlRequestMetadata(requestBody, true) - - expect(result).toEqual({ - operationType: 'query', - operationName: 'GetUser', - variables: '{"id":"123"}', - payload: 'query GetUser { user { id name } }', - }) - }) }) describe('request payload truncation', () => {