Skip to content

Commit

Permalink
New OnDelegate hook (#4409)
Browse files Browse the repository at this point in the history
* New OnDelegate hook

* Go

* chore(dependencies): updated changesets for modified dependencies

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
ardatan and github-actions[bot] authored Sep 1, 2022
1 parent 7a4023a commit 15f3159
Show file tree
Hide file tree
Showing 8 changed files with 140 additions and 36 deletions.
7 changes: 7 additions & 0 deletions .changeset/@graphql-mesh_types-4409-dependencies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@graphql-mesh/types": patch
---

dependencies updates:

- Added dependency [`@graphql-tools/[email protected]` ↗︎](https://www.npmjs.com/package/@graphql-tools/batch-delegate/v/8.3.7) (to `dependencies`)
7 changes: 7 additions & 0 deletions .changeset/young-days-hang.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@graphql-mesh/plugin-newrelic': minor
'@graphql-mesh/runtime': minor
'@graphql-mesh/types': minor
---

New onDelegate hook
1 change: 1 addition & 0 deletions examples/openapi-javascript-wiki/.meshrc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ additionalResolvers:

documents:
- example-queries/*.graphql

65 changes: 36 additions & 29 deletions packages/plugins/newrelic/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { Path } from '@envelop/core';
import { MeshPlugin, MeshPluginOptions, YamlConfig } from '@graphql-mesh/types';
import { useNewRelic } from '@envelop/newrelic';
import { stringInterpolator } from '@graphql-mesh/string-interpolation';
Expand Down Expand Up @@ -54,31 +53,47 @@ export default function useMeshNewrelic(options: MeshPluginOptions<YamlConfig.Ne
const operationSegment = instrumentationApi.getActiveSegment() || instrumentationApi.getSegment();
segmentByContext.set(contextValue, operationSegment);
},
async onFetch({ url, options, context, info }) {
async onDelegate({ sourceName, fieldName, args, context, key }) {
const instrumentationApi = await instrumentationApi$;
const logger = await logger$;
const agent = instrumentationApi?.agent;
const operationSegment = segmentByContext.get(context);
const transaction = operationSegment?.transaction;
const parentSegment =
instrumentationApi.getActiveSegment() || instrumentationApi.getSegment() || segmentByContext.get(context);
const transaction = parentSegment?.transaction;
if (transaction != null) {
const transactionNameState = transaction.nameState;
const delimiter = transactionNameState?.delimiter || '/';
const formattedPath = flattenPath(info.path, delimiter);
const sourceSegment = instrumentationApi.createSegment(
`source${delimiter}${(info as any).sourceName || 'unknown'}${delimiter}${formattedPath}`,
`source${delimiter}${sourceName || 'unknown'}${delimiter}${fieldName}`,
null,
operationSegment
parentSegment
);
if (!sourceSegment) {
logger.trace('Source segment was not created (%s).', formattedPath);
return undefined;
if (args) {
sourceSegment.addAttribute('args', JSON.stringify(args));
}
if (key) {
sourceSegment.addAttribute('key', JSON.stringify(key));
}
sourceSegment.start();
return ({ result }) => {
sourceSegment.addAttribute('result', JSON.stringify(result));
sourceSegment.end();
};
}
return undefined;
},
async onFetch({ url, options, context }) {
const instrumentationApi = await instrumentationApi$;
const logger = await logger$;
const agent = instrumentationApi?.agent;
const parentSegment =
instrumentationApi.getActiveSegment() || instrumentationApi.getSegment() || segmentByContext.get(context);
const transaction = parentSegment?.transaction;
if (transaction != null) {
const parsedUrl = new URL(url);
const name = NAMES.EXTERNAL.PREFIX + parsedUrl.host + parsedUrl.pathname;
const httpDetailSegment = instrumentationApi.createSegment(
name,
recordExternal(parsedUrl.host, 'graphql-mesh'),
sourceSegment
parentSegment
);
if (httpDetailSegment) {
httpDetailSegment.start();
Expand All @@ -101,13 +116,20 @@ export default function useMeshNewrelic(options: MeshPluginOptions<YamlConfig.Ne
for (const key in outboundHeaders) {
options.headers[key] = outboundHeaders[key];
}
for (const key in options.headers) {
httpDetailSegment.addAttribute(`request.headers.${key}`, options.headers[key]);
}
}
return ({ response }) => {
httpDetailSegment.addAttribute('http.statusCode', response.status);
httpDetailSegment.addAttribute('http.statusText', response.statusText);
const responseHeadersObj = getHeadersObj(response.headers);
for (const key in responseHeadersObj) {
httpDetailSegment.addAttribute(`response.headers.${key}`, responseHeadersObj[key]);
}
if (agent.config.cross_application_tracer.enabled && !agent.config.distributed_tracing.enabled) {
try {
const { appData } = cat.extractCatHeaders(getHeadersObj(response.headers));
const { appData } = cat.extractCatHeaders(responseHeadersObj);
const decodedAppData = cat.parseAppData(agent.config, appData);
const attrs = httpDetailSegment.getAttributes();
const url = new URL(attrs.url);
Expand All @@ -116,25 +138,10 @@ export default function useMeshNewrelic(options: MeshPluginOptions<YamlConfig.Ne
logger.warn(err, 'Cannot add CAT data to segment');
}
}
sourceSegment.end();
httpDetailSegment.end();
};
}
return undefined;
},
};
}

function flattenPath(fieldPath: Path, delimiter = '/') {
const pathArray = [];
let thisPath: Path | undefined = fieldPath;

while (thisPath) {
if (typeof thisPath.key !== 'number') {
pathArray.push(thisPath.key);
}
thisPath = thisPath.prev;
}

return pathArray.reverse().join(delimiter);
}
9 changes: 8 additions & 1 deletion packages/runtime/src/get-mesh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
MeshFetch,
MeshPlugin,
OnFetchHookDone,
OnDelegateHook,
} from '@graphql-mesh/types';

import { MESH_CONTEXT_SYMBOL } from './constants';
Expand Down Expand Up @@ -214,7 +215,13 @@ export async function getMesh(options: GetMeshOptions): Promise<MeshInstance> {

const inContextSDKPlugin = useExtendContext(() => {
if (!inContextSDK$) {
inContextSDK$ = getInContextSDK(finalSchema, rawSources, logger);
const onDelegateHooks: OnDelegateHook<any>[] = [];
for (const plugin of initialPluginList) {
if (plugin?.onDelegate != null) {
onDelegateHooks.push(plugin.onDelegate);
}
}
inContextSDK$ = getInContextSDK(finalSchema, rawSources, logger, onDelegateHooks);
}
return inContextSDK$;
});
Expand Down
64 changes: 59 additions & 5 deletions packages/runtime/src/in-context-sdk.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { Logger, RawSourceOutput, SelectionSetParam, SelectionSetParamOrFactory } from '@graphql-mesh/types';
import {
Logger,
OnDelegateHook,
OnDelegateHookDone,
RawSourceOutput,
SelectionSetParam,
SelectionSetParamOrFactory,
} from '@graphql-mesh/types';
import { printWithCache } from '@graphql-mesh/utils';
import { BatchDelegateOptions, batchDelegateToSchema } from '@graphql-tools/batch-delegate';
import { SubschemaConfig, StitchingInfo, IDelegateToSchemaOptions, delegateToSchema } from '@graphql-tools/delegate';
Expand All @@ -16,7 +23,12 @@ import {
} from 'graphql';
import { MESH_API_CONTEXT_SYMBOL } from './constants';

export async function getInContextSDK(unifiedSchema: GraphQLSchema, rawSources: RawSourceOutput[], logger: Logger) {
export async function getInContextSDK(
unifiedSchema: GraphQLSchema,
rawSources: RawSourceOutput[],
logger: Logger,
onDelegateHooks: OnDelegateHook<any>[]
) {
const inContextSDK: Record<string, any> = {};
const sourceMap = unifiedSchema.extensions.sourceMap as Map<RawSourceOutput, GraphQLSchema>;
for (const rawSource of rawSources) {
Expand Down Expand Up @@ -58,7 +70,7 @@ export async function getInContextSDK(unifiedSchema: GraphQLSchema, rawSources:
const inContextSdkLogger = rawSourceLogger.child(`InContextSDK.${rootType.name}.${fieldName}`);
const namedReturnType = getNamedType(rootTypeField.type);
const shouldHaveSelectionSet = !isLeafType(namedReturnType);
rawSourceContext[rootType.name][fieldName] = ({
rawSourceContext[rootType.name][fieldName] = async ({
root,
args,
context,
Expand Down Expand Up @@ -169,7 +181,28 @@ export async function getInContextSDK(unifiedSchema: GraphQLSchema, rawSources:
const wrapQueryTransform = new WrapQuery(path, selectionSetFactory, identical);
batchDelegationOptions.transforms = [wrapQueryTransform as any];
}
return batchDelegateToSchema(batchDelegationOptions);
const onDelegateHookDones: OnDelegateHookDone[] = [];
for (const onDelegateHook of onDelegateHooks) {
const onDelegateDone = await onDelegateHook({
...batchDelegationOptions,
sourceName: rawSource.name,
typeName: rootType.name,
fieldName,
});
if (onDelegateDone) {
onDelegateHookDones.push(onDelegateDone);
}
}
let result = await batchDelegateToSchema(batchDelegationOptions);
for (const onDelegateHookDone of onDelegateHookDones) {
await onDelegateHookDone({
result,
setResult(newResult) {
result = newResult;
},
});
}
return result;
} else {
const regularDelegateOptions: IDelegateToSchemaOptions = {
...commonDelegateOptions,
Expand All @@ -181,7 +214,28 @@ export async function getInContextSDK(unifiedSchema: GraphQLSchema, rawSources:
const wrapQueryTransform = new WrapQuery(path, selectionSetFactory, valuesFromResults || identical);
regularDelegateOptions.transforms = [wrapQueryTransform as any];
}
return delegateToSchema(regularDelegateOptions);
const onDelegateHookDones: OnDelegateHookDone[] = [];
for (const onDelegateHook of onDelegateHooks) {
const onDelegateDone = await onDelegateHook({
...regularDelegateOptions,
sourceName: rawSource.name,
typeName: rootType.name,
fieldName,
});
if (onDelegateDone) {
onDelegateHookDones.push(onDelegateDone);
}
}
let result = await delegateToSchema(regularDelegateOptions);
for (const onDelegateHookDone of onDelegateHookDones) {
await onDelegateHookDone({
result,
setResult(newResult) {
result = newResult;
},
});
}
return result;
}
};
}
Expand Down
1 change: 1 addition & 0 deletions packages/types/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"graphql": "*"
},
"dependencies": {
"@graphql-tools/batch-delegate": "8.3.7",
"@graphql-mesh/store": "0.8.32",
"@graphql-tools/delegate": "9.0.4",
"@graphql-tools/utils": "8.10.1",
Expand Down
22 changes: 21 additions & 1 deletion packages/types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
import { IResolvers, Executor } from '@graphql-tools/utils';
import { GraphQLSchema, GraphQLResolveInfo, DocumentNode, SelectionSetNode } from 'graphql';
import * as YamlConfig from './config';
import { Transform, MergedTypeConfig, SubschemaConfig } from '@graphql-tools/delegate';
import { Transform, MergedTypeConfig, SubschemaConfig, IDelegateToSchemaOptions } from '@graphql-tools/delegate';
import { TypedDocumentNode } from '@graphql-typed-document-node/core';
import { MeshStore } from '@graphql-mesh/store';
import configSchema from './config-schema.json';
import type { Plugin } from '@envelop/core';
import { PromiseOrValue } from 'graphql/jsutils/PromiseOrValue';
import { BatchDelegateOptions } from '@graphql-tools/batch-delegate';

export const jsonSchema: any = configSchema;

Expand Down Expand Up @@ -128,8 +129,27 @@ export type MeshPluginOptions<TConfig> = TConfig & {

export type MeshPluginFactory<TConfig> = (options: MeshPluginOptions<TConfig>) => Plugin;

export type OnDelegateHookPayload<TContext> = Partial<BatchDelegateOptions<TContext>> &
Partial<IDelegateToSchemaOptions<TContext>> & {
sourceName: string;
typeName: string;
fieldName: string;
};

export type OnDelegateHook<TContext> = (
payload: OnDelegateHookPayload<TContext>
) => PromiseOrValue<OnDelegateHookDone | void>;

export type OnDelegateHookDonePayload = {
result: any;
setResult: (result: any) => void;
};

export type OnDelegateHookDone = (payload: OnDelegateHookDonePayload) => PromiseOrValue<void>;

export type MeshPlugin<TContext> = Plugin<TContext> & {
onFetch?: OnFetchHook<TContext>;
onDelegate?: OnDelegateHook<TContext>;
};

export type MeshFetch = (
Expand Down

1 comment on commit 15f3159

@vercel
Copy link

@vercel vercel bot commented on 15f3159 Sep 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.