From daf9eaa4b26a247930ec88593fc64e1d7753fae1 Mon Sep 17 00:00:00 2001 From: Kamil Kisiela Date: Thu, 14 Dec 2023 09:47:50 +0100 Subject: [PATCH] Update `exclude` arg to accept RegExp (#3555) (#3608) Co-authored-by: Mina Kong <35077931+kongMina@users.noreply.github.com> --- .changeset/late-pumpkins-switch.md | 5 + .../libraries/client/src/internal/types.ts | 4 +- .../libraries/client/src/internal/usage.ts | 9 +- packages/libraries/client/tests/usage.spec.ts | 127 ++++++++++++++++++ 4 files changed, 141 insertions(+), 4 deletions(-) create mode 100644 .changeset/late-pumpkins-switch.md diff --git a/.changeset/late-pumpkins-switch.md b/.changeset/late-pumpkins-switch.md new file mode 100644 index 000000000..e7353df0a --- /dev/null +++ b/.changeset/late-pumpkins-switch.md @@ -0,0 +1,5 @@ +--- +"@graphql-hive/client": minor +--- + +Changed `exclude` argument type to accept RegEX diff --git a/packages/libraries/client/src/internal/types.ts b/packages/libraries/client/src/internal/types.ts index 448339830..e63631c7e 100644 --- a/packages/libraries/client/src/internal/types.ts +++ b/packages/libraries/client/src/internal/types.ts @@ -67,9 +67,9 @@ export interface HiveUsagePluginOptions { */ ttl?: number; /** - * A list of operations (by name) to be ignored by Hive. + * A list of operations (by name or regular expression) that should be excluded from reporting. */ - exclude?: string[]; + exclude?: Array; /** * Sample rate to determine sampling. * 0.0 = 0% chance of being sent diff --git a/packages/libraries/client/src/internal/usage.ts b/packages/libraries/client/src/internal/usage.ts index 502e960b4..fe5dd182a 100644 --- a/packages/libraries/client/src/internal/usage.ts +++ b/packages/libraries/client/src/internal/usage.ts @@ -177,9 +177,14 @@ export function createUsage(pluginOptions: HivePluginOptions): UsageCollector { ) as OperationDefinitionNode; providedOperationName = args.operationName || rootOperation.name?.value; const operationName = providedOperationName || 'anonymous'; - + // Check if operationName is a match with any string or regex in excludeSet + const isMatch = Array.from(excludeSet).some(excludingValue => + excludingValue instanceof RegExp + ? excludingValue.test(operationName) + : operationName === excludingValue, + ); if ( - !excludeSet.has(operationName) && + !isMatch && shouldInclude({ operationName, document, diff --git a/packages/libraries/client/tests/usage.spec.ts b/packages/libraries/client/tests/usage.spec.ts index 24abeee6e..bf834e024 100644 --- a/packages/libraries/client/tests/usage.spec.ts +++ b/packages/libraries/client/tests/usage.spec.ts @@ -556,3 +556,130 @@ test('should send data to Hive at least once when using atLeastOnceSampler', asy const operations = report.operations; expect(operations).toHaveLength(2); // two operations }); + +test('should not send excluded operation name data to Hive', async () => { + const logger = { + error: vi.fn(), + info: vi.fn(), + }; + + const token = 'Token'; + + let report: Report = { + size: 0, + map: {}, + operations: [], + }; + const http = nock('http://localhost') + .post('/200') + .once() + .reply((_, _body) => { + report = _body as any; + return [200]; + }); + + const hive = createHive({ + enabled: true, + debug: true, + agent: { + timeout: 500, + maxRetries: 0, + logger, + }, + token, + selfHosting: { + graphqlEndpoint: 'http://localhost/graphql', + applicationUrl: 'http://localhost/', + usageEndpoint: 'http://localhost/200', + }, + usage: { + exclude: ['deleteProjectShouldntBeIncluded', new RegExp('ExcludeThis$')], + }, + }); + + const collect = hive.collectUsage(); + + await waitFor(2000); + collect( + { + schema, + document: op, + operationName: 'deleteProjectExcludeThis', + }, + {}, + ); + collect( + { + schema, + document: op, + operationName: 'deleteProjectShouldntBeIncluded', + }, + {}, + ); + collect( + { + schema, + document: op, + operationName: 'deleteProject', + }, + {}, + ); + collect( + { + schema, + document: op2, + operationName: 'getProject', + }, + {}, + ); + await hive.dispose(); + await waitFor(1000); + http.done(); + + expect(logger.error).not.toHaveBeenCalled(); + expect(logger.info).toHaveBeenCalledWith(`[hive][usage] Sending (queue 2) (attempt 1)`); + expect(logger.info).toHaveBeenCalledWith(`[hive][usage] Sent!`); + + // Map + expect(report.size).toEqual(2); + expect(Object.keys(report.map)).toHaveLength(2); + + const key = Object.keys(report.map)[0]; + const record = report.map[key]; + + // operation + expect(record.operation).toMatch('mutation deleteProject'); + expect(record.operationName).toMatch('deleteProject'); + // fields + expect(record.fields).toMatchInlineSnapshot(` + [ + Mutation.deleteProject, + Mutation.deleteProject.selector, + DeleteProjectPayload.selector, + ProjectSelector.organization, + ProjectSelector.project, + DeleteProjectPayload.deletedProject, + Project.id, + Project.cleanId, + Project.name, + Project.type, + ProjectSelectorInput.organization, + ID, + ProjectSelectorInput.project, + ] + `); + + // Operations + const operations = report.operations; + expect(operations).toHaveLength(2); // two operations + const operation = operations[0]; + + expect(operation.operationMapKey).toEqual(key); + expect(operation.timestamp).toEqual(expect.any(Number)); + // execution + expect(operation.execution.duration).toBeGreaterThanOrEqual(1500 * 1_000_000); // >=1500ms in microseconds + expect(operation.execution.duration).toBeLessThan(3000 * 1_000_000); // <3000ms + expect(operation.execution.errorsTotal).toBe(0); + expect(operation.execution.errors).toHaveLength(0); + expect(operation.execution.ok).toBe(true); +});