Skip to content

Commit

Permalink
feat(reporting): Add reportTiming option to EngineReportingOptions (
Browse files Browse the repository at this point in the history
#3918)

* Add trace reporting api

Co-Authored-By: Trevor Scheer <trevor@apollographql.com>

* Set captureTraces to false if we don't report

* Fix tests

* Apply suggestions from code review

Co-authored-by: Jesse Rosenberger <git@jro.cc>

* Address comments

* Prettier

* Revert "Prettier"

This reverts commit e00dfe1.

The need to revert this is not the fault of the author at all.  The
thrashing here is truly unfortunate, but that's a result of past patterns
and this monorepo cannot tolerate any more burying of history.

By reverting this commit, the blame for the lines it tried to change will
not be re-directed at the author of `e00dfe1db` when they were introduced by
others.

Most importantly, this makes it reasonable to accurately review this PR in a
timely fashion.

* Move code around and clean

* Set capture traces in the reporting agent.

* Clean up

* Add documentation

* Remove unnecessary check

* Update CHANGELOG.md

Co-authored-by: Jesse Rosenberger <git@jro.cc>

* Update packages/apollo-engine-reporting/src/__tests__/plugin.test.ts

Co-authored-by: Jesse Rosenberger <git@jro.cc>

* Apply suggestions from code review

Co-authored-by: Jesse Rosenberger <git@jro.cc>

* Prettier

* Remove shouldReportTrace

* Update docs/source/api/apollo-server.md

Co-authored-by: Jesse Rosenberger <git@jro.cc>

* Update packages/apollo-engine-reporting/src/agent.ts

Co-authored-by: Jesse Rosenberger <git@jro.cc>

* Update packages/apollo-engine-reporting/src/agent.ts

Co-authored-by: Jesse Rosenberger <git@jro.cc>

* Address comments

* Update changelog

* Re-prettier agent.ts

* Address comments

* Quick comment change

* Rename to instrument operation

* Update apollo-server.md

* Rename to `timeOperation`

* chore(deps): update dependency gatsby-theme-apollo-docs to v4.2.6 (#4243)

Co-authored-by: Renovate Bot <bot@renovateapp.com>

* chore(deps): update dependency @types/aws-lambda to v8.10.56 (#4247)

Co-authored-by: Renovate Bot <bot@renovateapp.com>

* chore(deps): update dependency @types/ioredis to v4.16.5 (#4248)

Co-authored-by: Renovate Bot <bot@renovateapp.com>

* chore(deps): update dependency @types/uuid to v7.0.4 (#4250)

Co-authored-by: Renovate Bot <bot@renovateapp.com>

* chore(deps): update dependency gatsby to v2.23.3 (#4251)

Co-authored-by: Renovate Bot <bot@renovateapp.com>

* chore(deps): update dependency @types/supertest to v2.0.9 (#4249)

Co-authored-by: Renovate Bot <bot@renovateapp.com>

* chore(deps): update dependency lerna to v3.22.1 (#4252)

Co-authored-by: Renovate Bot <bot@renovateapp.com>

* Rename to report timing

* Update changelog

* Use code fences in API documentation

Co-authored-by: Joshua Segaran <joshua.segaran@apollographql.com>
Co-authored-by: Trevor Scheer <trevor@apollographql.com>
Co-authored-by: Jesse Rosenberger <git@jro.cc>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Renovate Bot <bot@renovateapp.com>
  • Loading branch information
6 people authored Jun 16, 2020
1 parent a4c7e15 commit 54ff1e6
Show file tree
Hide file tree
Showing 10 changed files with 338 additions and 43 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ The version headers in this history reflect the versions of Apollo Server itself

### vNEXT

- _Nothing yet! Stay tuned._
- `apollo-engine-reporting`: Added a `reportTiming` API to allow trace reporting to be enabled or disabled on a per request basis. The option takes either a boolean or a predicate function that takes a [`GraphQLRequestContextDidResolveOperation`](https://github.com/apollographql/apollo-server/blob/a926b7eedbb87abab2ec70fb03d71743985cb18d/packages/apollo-server-types/src/index.ts#L185-L190) or [`GraphQLRequestContextDidEncounterErrors`](https://github.com/apollographql/apollo-server/blob/a926b7eedbb87abab2ec70fb03d71743985cb18d/packages/apollo-server-types/src/index.ts#L191-L195) and returns a boolean. If the boolean is false the request will not be instrumented for tracing and no trace will be sent to Apollo Graph Manager. The default is `true` so all traces will get instrumented and sent, which is the same as the previous default behavior. [PR #3918](https://github.com/apollographql/apollo-server/pull/3918)
- `apollo-engine-reporting`: Removed `GraphQLServerOptions.reporting`. It isn't known whether a trace will be reported at the beginning of the request because of the above change. We believe this field was only used internally within Apollo Server; let us know if this is a problem and we can suggest alternatives. Additionally, the field requestContext.metrics.captureTraces is now initialized later in the request pipeline. [PR #3918](https://github.com/apollographql/apollo-server/pull/3918)

### v2.14.4

Expand Down
12 changes: 12 additions & 0 deletions docs/source/api/apollo-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,18 @@ addMockFunctionsToSchema({
A human-readable name for the variant of a schema (i.e. staging, EU). Setting this value will cause metrics to be segmented in the Apollo Graph Manager UI. Additionally schema validation with a graph variant will only check metrics associated with the same string.
* `reportTiming`: Boolean | async (GraphQLRequestContextDidResolveOperation | GraphQLRequestContextDidEncounterErrors) => Boolean
Specify whether to instrument an operation to send traces and metrics to Apollo.
This may resolve to a boolean or a async function returning a promise resolving to a boolean.
If the option resolves to false for an operation the operation will not be instrumented
and no metrics information will be sent to Apollo.
The function will receive a `GraphQLRequestContextDidResolveOperation` with client and operation
information or a `GraphQLRequestContextDiDEncounterErrors` in the case an operation failed
to resolve properly. This allows the choice of whether to include a given request in trace
and metric reporting to be made on a per-request basis. The default value is true.
* `generateClientInfo`: (GraphQLRequestContext) => ClientInfo **AS 2.2**
Creates a client context(ClientInfo) based on the request pipeline's
Expand Down
179 changes: 173 additions & 6 deletions packages/apollo-engine-reporting/src/__tests__/plugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { graphql, GraphQLError, printSchema } from 'graphql';
import { Request } from 'node-fetch';
import { makeTraceDetails, makeHTTPRequestHeaders, plugin } from '../plugin';
import { Headers } from 'apollo-server-env';
import { AddTraceArgs, computeExecutableSchemaId } from '../agent';
import { computeExecutableSchemaId } from '../agent';
import { Trace } from 'apollo-engine-reporting-protobuf';
import pluginTestHarness from 'apollo-server-core/dist/utils/pluginTestHarness';

Expand Down Expand Up @@ -42,11 +42,23 @@ const query = `
}
`;

const queryReport = `
query report {
author(id: 5) {
name
posts(limit: 2) {
id
}
}
aBoolean
}
`;

describe('schema reporting', () => {
const schema = makeExecutableSchema({ typeDefs });
addMockFunctionsToSchema({ schema });

const addTrace = jest.fn().mockResolvedValue(undefined);
const addTrace = jest.fn(() => Promise.resolve());
const startSchemaReporting = jest.fn();
const executableSchemaIdGenerator = jest.fn(computeExecutableSchemaId);

Expand Down Expand Up @@ -191,12 +203,9 @@ it('trace construction', async () => {
const schema = makeExecutableSchema({ typeDefs });
addMockFunctionsToSchema({ schema });

const traces: Array<AddTraceArgs> = [];
async function addTrace(args: AddTraceArgs) {
traces.push(args);
}
const startSchemaReporting = jest.fn();
const executableSchemaIdGenerator = jest.fn();
const addTrace = jest.fn(() => Promise.resolve());

const pluginInstance = plugin(
{
Expand Down Expand Up @@ -462,6 +471,164 @@ function makeTestHTTP(): Trace.HTTP {
});
}

describe('tests for the "reportTiming', () => {
const schemaReportingFunctions = {
startSchemaReporting: jest.fn(),
executableSchemaIdGenerator: jest.fn(),
};
const schema = makeExecutableSchema({ typeDefs });
addMockFunctionsToSchema({ schema });

const addTrace = jest.fn(() => Promise.resolve());
beforeEach(() => {
addTrace.mockClear();
});

it('report no traces', async () => {
const pluginInstance = plugin(
{ reportTiming: false },
addTrace,
schemaReportingFunctions,
);

const context = await pluginTestHarness({
pluginInstance,
schema,
graphqlRequest: {
query,
operationName: 'q',
extensions: {
clientName: 'testing suite',
},
http: new Request('http://localhost:123/foo'),
},
executor: async ({ request: { query: source } }) => {
return await graphql({
schema,
source,
});
},
});
expect(context.metrics.captureTraces).toBeFalsy();
});

it('report traces based on operation name', async () => {
const pluginInstance = plugin(
{
reportTiming: async request => {
return request.request.operationName === 'report';
},
},
addTrace,
schemaReportingFunctions,
);

const context1 = await pluginTestHarness({
pluginInstance,
schema,
graphqlRequest: {
query: queryReport,
operationName: 'report',
extensions: {
clientName: 'testing suite',
},
http: new Request('http://localhost:123/foo'),
},
executor: async ({ request: { query: source } }) => {
return await graphql({
schema,
source,
});
},
});

expect(addTrace).toBeCalledTimes(1);
expect(context1.metrics.captureTraces).toBeTruthy();
addTrace.mockClear();

const context2 = await pluginTestHarness({
pluginInstance,
schema,
graphqlRequest: {
query,
operationName: 'q',
extensions: {
clientName: 'testing suite',
},
http: new Request('http://localhost:123/foo'),
},
executor: async ({ request: { query: source } }) => {
return await graphql({
schema,
source,
});
},
});

expect(addTrace).not.toBeCalled();
expect(context2.metrics.captureTraces).toBeFalsy();
});

it('report traces async based on operation name', async () => {
const pluginInstance = plugin(
{
reportTiming: async request => {
return await (async () => {
return request.request.operationName === 'report';
})();
},
},
addTrace,
schemaReportingFunctions
);

const context1 = await pluginTestHarness({
pluginInstance,
schema,
graphqlRequest: {
query: queryReport,
operationName: 'report',
extensions: {
clientName: 'testing suite',
},
http: new Request('http://localhost:123/foo'),
},
executor: async ({ request: { query: source } }) => {
return await graphql({
schema,
source,
});
},
});

expect(addTrace).toBeCalledTimes(1);
expect(context1.metrics.captureTraces).toBeTruthy();
addTrace.mockClear();

const context2 = await pluginTestHarness({
pluginInstance,
schema,
graphqlRequest: {
query,
operationName: 'q',
extensions: {
clientName: 'testing suite',
},
http: new Request('http://localhost:123/foo'),
},
executor: async ({ request: { query: source } }) => {
return await graphql({
schema,
source,
});
},
});

expect(addTrace).not.toBeCalled();
expect(context2.metrics.captureTraces).toBeFalsy();
});
});

/**
* TESTS FOR THE sendHeaders REPORTING OPTION
*/
Expand Down
Loading

0 comments on commit 54ff1e6

Please sign in to comment.