diff --git a/src/plugins/graphql.js b/src/plugins/graphql.js index 6fe39d6c22d..b86f0788d3c 100644 --- a/src/plugins/graphql.js +++ b/src/plugins/graphql.js @@ -20,7 +20,7 @@ function createWrapExecute (tracer, config, defaultFieldResolver, responsePathAs args.contextValue = contextValue if (!schema._datadog_patched) { - wrapFields(schema._queryType._fields, tracer, config, responsePathAsArray) + wrapFields(schema._queryType, tracer, config, responsePathAsArray) schema._datadog_patched = true } @@ -52,24 +52,35 @@ function createWrapParse () { } } -function wrapFields (fields, tracer, config, responsePathAsArray) { - Object.keys(fields).forEach(key => { - const field = fields[key] +function wrapFields (type, tracer, config, responsePathAsArray) { + if (type._datadog_patched) { + return + } + + type._datadog_patched = true + + Object.keys(type._fields).forEach(key => { + const field = type._fields[key] - if (typeof field.resolve === 'function') { + if (typeof field.resolve === 'function' && !field.resolve._datadog_patched) { field.resolve = wrapResolve(field.resolve, tracer, config, responsePathAsArray) } - if (field.type && field.type._fields) { - wrapFields(field.type._fields, tracer, config, responsePathAsArray) + if (field.type) { + if (field.type._fields) { + wrapFields(field.type, tracer, config, responsePathAsArray) + } else if (field.type.ofType && field.type.ofType._fields) { + wrapFields(field.type.ofType, tracer, config, responsePathAsArray) + } } }) } function wrapResolve (resolve, tracer, config, responsePathAsArray) { - return function resolveWithTrace (source, args, contextValue, info) { + function resolveWithTrace (source, args, contextValue, info) { const path = responsePathAsArray(info.path) const fieldParent = getFieldParent(contextValue, path) + const childOf = createSpan('graphql.field', tracer, config, fieldParent, path) const deferred = defer(tracer) @@ -88,6 +99,10 @@ function wrapResolve (resolve, tracer, config, responsePathAsArray) { return result } + + resolveWithTrace._datadog_patched = true + + return resolveWithTrace } function wrapFieldResolver (fieldResolver, tracer, config, responsePathAsArray) { diff --git a/test/plugins/graphql.spec.js b/test/plugins/graphql.spec.js index c283cb79e75..1a5f21597b7 100644 --- a/test/plugins/graphql.spec.js +++ b/test/plugins/graphql.spec.js @@ -38,6 +38,42 @@ describe('Plugin', () => { resolve (obj, args) { return {} } + }, + pets: { + type: new graphql.GraphQLList(new graphql.GraphQLObjectType({ + name: 'Pet', + fields: () => ({ + type: { + type: graphql.GraphQLString, + resolve: () => 'dog' + }, + name: { + type: graphql.GraphQLString, + resolve: () => 'foo bar' + }, + owner: { + type: Human, + resolve: () => ({}) + }, + colours: { + type: new graphql.GraphQLList(new graphql.GraphQLObjectType({ + name: 'Colour', + fields: { + code: { + type: graphql.GraphQLString, + resolve: () => '#ffffff' + } + } + })), + resolve (obj, args) { + return [{}, {}] + } + } + }) + })), + resolve (obj, args) { + return [{}, {}, {}] + } } } }) @@ -288,6 +324,18 @@ describe('Plugin', () => { graphql.graphql(schema, source).catch(done) }) + it('should handle a circular schema', done => { + const source = `{ human { pets { owner { name } } } }` + + graphql.graphql(schema, source) + .then((result) => { + expect(result.data.human.pets[0].owner.name).to.equal('test') + + done() + }) + .catch(done) + }) + it('should ignore the default field resolver', done => { const schema = graphql.buildSchema(` type Query {