Skip to content

Commit

Permalink
Release 2.19.0 (#4626)
Browse files Browse the repository at this point in the history
Co-authored-by: Jesse Rosenberger <git@jro.cc>
Co-authored-by: David Glasser <glasser@davidglasser.net>
Co-authored-by: Joshua Segaran <joshua.segaran@apollographql.com>
Co-authored-by: Joshua Segaran <josh.segaran@apollographql.com>
Co-authored-by: Oliver Plummer <oliver.plummer@outlook.com>
Co-authored-by: Oliver Salzburg <oliver.salzburg@gmail.com>
Co-authored-by: Douglas Gabriel <douglasgabriel99@gmail.com>
Co-authored-by: Trevor Scheer <trevor.scheer@gmail.com>
Co-authored-by: Arnaud Gissinger <37625778+mathix420@users.noreply.github.com>
  • Loading branch information
9 people authored Oct 30, 2020
1 parent 2bf06b5 commit 9fbf668
Show file tree
Hide file tree
Showing 39 changed files with 313 additions and 74 deletions.
2 changes: 1 addition & 1 deletion .github/APOLLO_RELEASE_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ As with [release PRs in the past](https://github.com/apollographql/apollo-server

Check the appropriate milestone (to the right) for more details on what we hope to get into this release!

The intention of these release branches is to gather changes which are intended to land in a specific version (again, indiciated by the subject of this PR). Release branches allow additional clarity into what is being staged, provide a forum for comments from the community pertaining to the release's stability, and to facilitate the creation of pre-releases (e.g. `alpha`, `beta`, `rc`) without affecting the `main` branch.
The intention of these release branches is to gather changes which are intended to land in a specific version (again, indicated by the subject of this PR). Release branches allow additional clarity into what is being staged, provide a forum for comments from the community pertaining to the release's stability, and to facilitate the creation of pre-releases (e.g. `alpha`, `beta`, `rc`) without affecting the `main` branch.

PRs for new features might be opened against or re-targeted to this branch by the project maintainers. The `main` branch may be periodically merged into this branch up until the point in time that this branch is being prepared for release. Depending on the size of the release, this may be once it reaches RC (release candidate) stage with an `-rc.x` release suffix. Some less substantial releases may be short-lived and may never have pre-release versions.

Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ The version headers in this history reflect the versions of Apollo Server itself

> The changes noted within this `vNEXT` section have not been released yet. New PRs and commits which introduce changes should include an entry in this `vNEXT` section as part of their development. With few exceptions, the format of the entry should follow convention (i.e., prefix with package name, use markdown `backtick formatting` for package names and code, suffix with a link to the change-set à la `[PR #YYY](https://link/pull/YYY)`, etc.). When a release is being prepared, a new header will be (manually) created below and the appropriate changes within that release will be moved into the new section.
## v2.19.0

- `apollo-server-testing`: types: Allow generic `variables` usage of `query` and `mutate` functions. [PR #4383](https://github.com/apollograpqh/apollo-server/pull/4383)
- `apollo-server-express`: Export the `GetMiddlewareOptions` type. [PR #4599](https://github.com/apollograpqh/apollo-server/pull/4599)
- `apollo-server-lambda`: Fix file uploads - ignore base64 decoding for multipart queries. [PR #4506](https://github.com/apollographql/apollo-server/pull/4506)
- `apollo-server-core`: Do not send operation documents that cannot be executed to Apollo Studio. Instead, information about these operations will be combined into one "operation" for parse failures, one for validation failures, and one for unknown operation names.

## v2.18.2

- `apollo-server-core`: Explicitly include `lru-cache` dependency in `apollo-server-core`'s dependencies. [PR #4600](https://github.com/apollographql/apollo-server/pull/4600)
Expand Down
14 changes: 14 additions & 0 deletions docs/source/api/plugin/usage-reporting.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,20 @@ If you're using the `overrideReportedSchema` option with the [schema reporting p
</td>
</tr>

<tr>
<td>

###### `sendUnexecutableOperationDocuments`

`Boolean`
</td>
<td>

Statistics about operations that your server cannot execute are not reported under each document separately to Apollo Studio, but are grouped together as "parse failure", "validation failure", or "unknown operation name". By default, the usage reporting plugin does not include the full operation document in reported traces, because it is challenging to strip potential private information (like string constants) from invalid operations. If you'd like the usage reporting plugin to send the full operation document and operation name so you can view it in Apollo Studio's trace view, set this to true.

</td>
</tr>

<tr>
<td colspan="2">

Expand Down
2 changes: 1 addition & 1 deletion packages/apollo-cache-control/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "apollo-cache-control",
"version": "0.11.3",
"version": "0.11.4",
"description": "A GraphQL extension for cache control",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ describe('plugin', () => {
return pluginTestHarness({
pluginInstance,
overallCachePolicy,
graphqlRequest: { query: 'does not matter' },
// This query needs to pass graphql validation
graphqlRequest: { query: 'query { hello }' },
executor: () => {
const response: GraphQLResponse = {
http: {
Expand Down
2 changes: 1 addition & 1 deletion packages/apollo-datasource-rest/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "apollo-datasource-rest",
"version": "0.9.4",
"version": "0.9.5",
"author": "Apollo <opensource@apollographql.com>",
"license": "MIT",
"repository": {
Expand Down
2 changes: 1 addition & 1 deletion packages/apollo-reporting-protobuf/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "apollo-reporting-protobuf",
"version": "0.6.0",
"version": "0.6.1",
"description": "Protobuf format for Apollo usage reporting",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
6 changes: 6 additions & 0 deletions packages/apollo-reporting-protobuf/src/reports.proto
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,12 @@ message Trace {
// instead.
string signature = 19;

// Optional: when GraphQL parsing or validation against the GraphQL schema fails, these fields
// can include reference to the operation being sent for users to dig into the set of operations
// that are failing validation.
string unexecutedOperationBody = 27;
string unexecutedOperationName = 28;

Details details = 6;

// Note: engineproxy always sets client_name, client_version, and client_address to "none".
Expand Down
2 changes: 1 addition & 1 deletion packages/apollo-server-azure-functions/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "apollo-server-azure-functions",
"version": "2.18.2",
"version": "2.19.0",
"description": "Production-ready Node.js GraphQL server for Azure Functions",
"keywords": [
"GraphQL",
Expand Down
2 changes: 1 addition & 1 deletion packages/apollo-server-cloud-functions/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "apollo-server-cloud-functions",
"version": "2.18.2",
"version": "2.19.0",
"description": "Production-ready Node.js GraphQL server for Google Cloud Functions",
"keywords": [
"GraphQL",
Expand Down
2 changes: 1 addition & 1 deletion packages/apollo-server-cloudflare/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "apollo-server-cloudflare",
"version": "2.18.2",
"version": "2.19.0",
"description": "Production-ready Node.js GraphQL server for Cloudflare workers",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion packages/apollo-server-core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "apollo-server-core",
"version": "2.18.2",
"version": "2.19.0",
"description": "Core engine for Apollo GraphQL server",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion packages/apollo-server-core/src/plugin/traceTreeBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export class TraceTreeBuilder {
private nodes = new Map<string, Trace.Node>([
[responsePathAsString(), this.rootNode],
]);
private rewriteError?: (err: GraphQLError) => GraphQLError | null;
private readonly rewriteError?: (err: GraphQLError) => GraphQLError | null;

public constructor(options: {
logger?: Logger;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,13 @@ describe('end-to-end', () => {
async function runTest({
pluginOptions = {},
expectReport = true,
query,
operationName,
}: {
pluginOptions?: ApolloServerPluginUsageReportingOptions<any>;
expectReport?: boolean;
query?: string;
operationName?: string | null;
}) {
const typeDefs = `
type User {
Expand All @@ -43,7 +47,7 @@ describe('end-to-end', () => {
}
`;

const query = `
const defaultQuery = `
query q {
author(id: 5) {
name
Expand Down Expand Up @@ -81,8 +85,10 @@ describe('end-to-end', () => {
pluginInstance,
schema,
graphqlRequest: {
query,
operationName: 'q',
query: query ?? defaultQuery,
// If operation name is specified use it. If it is specified as null convert it to
// undefined because graphqlRequest expects string | undefined
operationName: operationName === undefined ? 'q' : (operationName || undefined),
extensions: {
clientName: 'testing suite',
},
Expand Down Expand Up @@ -124,6 +130,47 @@ describe('end-to-end', () => {
).toBeTruthy();
});

it('fails parse for non-parseable gql', async () => {
const { report } = await runTest({ query: 'random text' });
expect(Object.keys(report!.tracesPerQuery)).toHaveLength(1);
expect(Object.keys(report!.tracesPerQuery)[0]).toBe(
'## GraphQLParseFailure\n',
);
const traces = Object.values(report!.tracesPerQuery)[0].trace;
expect(traces).toHaveLength(1);
});

it('validation fails for invalid operation', async () => {
const { report } = await runTest({ query: 'query q { nonExistentField }' });
expect(Object.keys(report!.tracesPerQuery)).toHaveLength(1);
expect(Object.keys(report!.tracesPerQuery)[0]).toBe(
'## GraphQLValidationFailure\n',
);
const traces = Object.values(report!.tracesPerQuery)[0].trace;
expect(traces).toHaveLength(1);
});

it('unknown operation error if not specified', async () => {
const { report } = await runTest({ query: 'query notQ { aString }' });
expect(Object.keys(report!.tracesPerQuery)).toHaveLength(1);
expect(Object.keys(report!.tracesPerQuery)[0]).toBe(
'## GraphQLUnknownOperationName\n',
);
const traces = Object.values(report!.tracesPerQuery)[0].trace;
expect(traces).toHaveLength(1);
});

it('handles anonymous operation', async () => {
const { report } = await runTest({
query: 'query { aString }',
operationName: null,
});
expect(Object.keys(report!.tracesPerQuery)).toHaveLength(1);
expect(Object.keys(report!.tracesPerQuery)[0]).toMatch(/^# -\n/);
const traces = Object.values(report!.tracesPerQuery)[0].trace;
expect(traces).toHaveLength(1);
});

describe('includeRequest', () => {
it('include based on operation name', async () => {
const { report, context } = await runTest({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ describe('signature cache key', () => {
expect(signatureCacheKey('abc123', '')).toEqual('abc123');
});

it('generates without the operationName', () => {
it('generates with the operationName', () => {
expect(signatureCacheKey('abc123', 'myOperation')).toEqual(
'abc123:myOperation',
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,15 @@ export interface ApolloServerPluginUsageReportingOptions<TContext> {
* ID that the other plugin reports.
*/
overrideReportedSchema?: string;
/**
* Whether to include the entire document in the trace if the operation
* was a GraphQL parse or validation error (i.e. failed the GraphQL parse or
* validation phases). This will be included as a separate field on the trace
* and the operation name and signature will always be reported with a cosntant
* identifier. Whether the operation was a parse failure or a validation
* failure will be embedded within the stats report key itself.
*/
sendUnexecutableOperationDocuments?: boolean;
//#endregion

//#region Configure the mechanics of communicating with Apollo's servers.
Expand Down
58 changes: 41 additions & 17 deletions packages/apollo-server-core/src/plugin/usageReporting/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,8 @@ export function ApolloServerPluginUsageReporting<TContext>(
});
treeBuilder.startTiming();
metrics.startHrTime = treeBuilder.startHrTime;
let graphqlValidationFailure = false;
let graphqlUnknownOperationName = false;

if (http) {
treeBuilder.trace.http = new Trace.HTTP({
Expand Down Expand Up @@ -471,15 +473,35 @@ export function ApolloServerPluginUsageReporting<TContext>(
const reportData = getReportData(executableSchemaId);
const { report } = reportData;

let statsReportKey: string | undefined = undefined;
if (!requestContext.document) {
statsReportKey = `## GraphQLParseFailure\n`;
} else if (graphqlValidationFailure) {
statsReportKey = `## GraphQLValidationFailure\n`;
} else if (graphqlUnknownOperationName) {
statsReportKey = `## GraphQLUnknownOperationName\n`;
}

if (statsReportKey) {
if (
options.sendUnexecutableOperationDocuments
) {
treeBuilder.trace.unexecutedOperationBody =
requestContext.source;
treeBuilder.trace.unexecutedOperationName = operationName;
}
} else {
const signature = getTraceSignature();
statsReportKey = `# ${operationName || '-'}\n${signature}`;
}

const protobufError = Trace.verify(treeBuilder.trace);
if (protobufError) {
throw new Error(`Error encoding trace: ${protobufError}`);
}
const encodedTrace = Trace.encode(treeBuilder.trace).finish();

const signature = getTraceSignature();
const encodedTrace = Trace.encode(treeBuilder.trace).finish();

const statsReportKey = `# ${operationName || '-'}\n${signature}`;
if (!report.tracesPerQuery.hasOwnProperty(statsReportKey)) {
report.tracesPerQuery[statsReportKey] = new TracesAndStats();
(report.tracesPerQuery[statsReportKey] as any).encodedTraces = [];
Expand All @@ -489,6 +511,7 @@ export function ApolloServerPluginUsageReporting<TContext>(
(report.tracesPerQuery[statsReportKey] as any).encodedTraces.push(
encodedTrace,
);

reportData.size +=
encodedTrace.length + Buffer.byteLength(statsReportKey);

Expand All @@ -503,9 +526,10 @@ export function ApolloServerPluginUsageReporting<TContext>(
}

function getTraceSignature(): string {
if (!requestContext.document && !requestContext.source) {
// This shouldn't happen: one of those options must be passed to runQuery.
throw new Error('No document or source?');
if (!requestContext.document) {
// This shouldn't happen: no document means parse failure, which
// uses its own special statsReportKey.
throw new Error('No document?');
}

const cacheKey = signatureCacheKey(
Expand All @@ -521,17 +545,6 @@ export function ApolloServerPluginUsageReporting<TContext>(
return cachedSignature;
}

if (!requestContext.document) {
// We didn't get an AST, possibly because of a parse failure. Let's just
// use the full query string.
//
// XXX This does mean that even if you use a calculateSignature which
// hides literals, you might end up sending literals for queries
// that fail parsing or validation. Provide some way to mask them
// anyway?
return requestContext.source as string;
}

const generatedSignature = (
options.calculateSignature || defaultUsageReportingSignature
)(requestContext.document, operationName);
Expand Down Expand Up @@ -591,7 +604,18 @@ export function ApolloServerPluginUsageReporting<TContext>(
treeBuilder.trace.clientName = clientName || '';
}
},
validationDidStart() {
return (validationErrors?: ReadonlyArray<Error>) => {
graphqlValidationFailure = validationErrors
? validationErrors.length !== 0
: false;
};
},
async didResolveOperation(requestContext) {
// If operation is undefined then `getOperationAST` returned null
// and an unknown operation was specified.
graphqlUnknownOperationName =
requestContext.operation === undefined;
await shouldIncludeRequest(requestContext);

if (metrics.captureTraces === false) {
Expand Down
Loading

0 comments on commit 9fbf668

Please sign in to comment.