Skip to content

Commit f1c6af2

Browse files
umaannamalaiTimPansino
authored andcommitted
GraphQL Fragment Spread Logic (#293)
* Implement fragment spread deepest path logic. * Reformat files.
1 parent 3d88d90 commit f1c6af2

File tree

5 files changed

+207
-97
lines changed

5 files changed

+207
-97
lines changed

newrelic/hooks/framework_graphql.py

Lines changed: 81 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
def graphql_version():
3232
from graphql import __version__ as version
33+
3334
return tuple(int(v) for v in version.split("."))
3435

3536

@@ -69,13 +70,13 @@ def wrap_executor_context_init(wrapped, instance, args, kwargs):
6970
instance.field_resolver = wrap_resolver(instance.field_resolver)
7071
instance.field_resolver._nr_wrapped = True
7172

72-
7373
return result
7474

7575

7676
def bind_operation_v3(operation, root_value):
7777
return operation
7878

79+
7980
def bind_operation_v2(exe_context, operation, root_value):
8081
return operation
8182

@@ -105,12 +106,22 @@ def wrap_execute_operation(wrapped, instance, args, kwargs):
105106
if get_node_value(field, "name") in GRAPHQL_INTROSPECTION_FIELDS:
106107
ignore_transaction()
107108

108-
deepest_path = traverse_deepest_unique_path(fields)
109+
if graphql_version() <= (3, 0, 0):
110+
fragments = args[
111+
0
112+
].fragments # In v2, args[0] is the ExecutionContext object
113+
else:
114+
fragments = instance.fragments # instance is the ExecutionContext object
115+
deepest_path = traverse_deepest_unique_path(fields, fragments)
109116
trace.deepest_path = deepest_path = ".".join(deepest_path) or ""
110117

111118
transaction.set_transaction_name(callable_name(wrapped), "GraphQL", priority=11)
112119
result = wrapped(*args, **kwargs)
113-
transaction_name = "%s/%s/%s" % (operation_type, operation_name, deepest_path) if deepest_path else "%s/%s" % (operation_type, operation_name)
120+
transaction_name = (
121+
"%s/%s/%s" % (operation_type, operation_name, deepest_path)
122+
if deepest_path
123+
else "%s/%s" % (operation_type, operation_name)
124+
)
114125
transaction.set_transaction_name(transaction_name, "GraphQL", priority=14)
115126

116127
return result
@@ -123,74 +134,121 @@ def get_node_value(field, attr, subattr="value"):
123134
return field_name
124135

125136

137+
def is_fragment_spread_node(field):
138+
# Resolve version specific imports
139+
try:
140+
from graphql.language.ast import FragmentSpread
141+
except ImportError:
142+
from graphql import FragmentSpreadNode as FragmentSpread
143+
144+
return isinstance(field, FragmentSpread)
145+
146+
126147
def is_fragment(field):
127148
# Resolve version specific imports
128149
try:
129150
from graphql.language.ast import FragmentSpread, InlineFragment
130151
except ImportError:
131-
from graphql import FragmentSpreadNode as FragmentSpread, InlineFragmentNode as InlineFragment
152+
from graphql import (
153+
FragmentSpreadNode as FragmentSpread,
154+
InlineFragmentNode as InlineFragment,
155+
)
132156

133157
_fragment_types = (InlineFragment, FragmentSpread)
134158

135159
return isinstance(field, _fragment_types)
136160

161+
137162
def is_named_fragment(field):
138163
# Resolve version specific imports
139164
try:
140165
from graphql.language.ast import NamedType
141166
except ImportError:
142167
from graphql import NamedTypeNode as NamedType
143168

144-
return is_fragment(field) and getattr(field, "type_condition", None) is not None and isinstance(field.type_condition, NamedType)
169+
return (
170+
is_fragment(field)
171+
and getattr(field, "type_condition", None) is not None
172+
and isinstance(field.type_condition, NamedType)
173+
)
145174

146175

147-
def traverse_deepest_unique_path(fields):
148-
deepest_path = deque()
176+
def filter_ignored_fields(fields):
177+
filtered_fields = [
178+
f for f in fields if get_node_value(f, "name") not in GRAPHQL_IGNORED_FIELDS
179+
]
180+
return filtered_fields
181+
149182

183+
def traverse_deepest_unique_path(fields, fragments):
184+
deepest_path = deque()
150185
while fields is not None and len(fields) > 0:
151-
fields = [f for f in fields if get_node_value(f, "name") not in GRAPHQL_IGNORED_FIELDS]
186+
fields = filter_ignored_fields(fields)
152187
if len(fields) != 1: # Either selections is empty, or non-unique
153188
return deepest_path
154189
field = fields[0]
155-
156190
field_name = get_node_value(field, "name")
191+
fragment_selection_set = []
192+
157193
if is_named_fragment(field):
158194
name = get_node_value(field.type_condition, "name")
159195
if name:
160196
deepest_path.append("%s<%s>" % (deepest_path.pop(), name))
197+
161198
elif is_fragment(field):
162-
break
199+
if len(list(fragments.values())) != 1:
200+
return deepest_path
201+
202+
# list(fragments.values())[0] 's index is OK because the previous line
203+
# ensures that there is only one field in the list
204+
full_fragment_selection_set = list(fragments.values())[
205+
0
206+
].selection_set.selections
207+
fragment_selection_set = filter_ignored_fields(full_fragment_selection_set)
208+
209+
if len(fragment_selection_set) != 1:
210+
return deepest_path
211+
else:
212+
fragment_field_name = get_node_value(fragment_selection_set[0], "name")
213+
deepest_path.append(fragment_field_name)
214+
163215
else:
164216
if field_name:
165217
deepest_path.append(field_name)
166218

219+
if is_fragment_spread_node(field):
220+
field = fragment_selection_set[0]
167221
if field.selection_set is None:
168222
break
169223
else:
170224
fields = field.selection_set.selections
171225

172226
return deepest_path
173227

228+
174229
def bind_get_middleware_resolvers(middlewares):
175230
return middlewares
176231

177232

178233
def wrap_get_middleware_resolvers(wrapped, instance, args, kwargs):
179234
middlewares = bind_get_middleware_resolvers(*args, **kwargs)
180-
middlewares = [wrap_middleware(m) if not hasattr(m, "_nr_wrapped") else m for m in middlewares]
235+
middlewares = [
236+
wrap_middleware(m) if not hasattr(m, "_nr_wrapped") else m for m in middlewares
237+
]
181238
for m in middlewares:
182239
m._nr_wrapped = True
183240

184241
return wrapped(middlewares)
185242

243+
186244
@function_wrapper
187245
def wrap_middleware(wrapped, instance, args, kwargs):
188246
transaction = current_transaction()
189247
if transaction is None:
190248
return wrapped(*args, **kwargs)
191249

192250
name = callable_name(wrapped)
193-
transaction.set_transaction_name(name, 'GraphQL', priority=12)
251+
transaction.set_transaction_name(name, "GraphQL", priority=12)
194252
with FunctionTrace(name):
195253
with ErrorTrace(ignore=ignore_graphql_duplicate_exception):
196254
return wrapped(*args, **kwargs)
@@ -253,7 +311,7 @@ def wrap_validate(wrapped, instance, args, kwargs):
253311
if transaction is None:
254312
return wrapped(*args, **kwargs)
255313

256-
transaction.set_transaction_name(callable_name(wrapped),"GraphQL", priority=10)
314+
transaction.set_transaction_name(callable_name(wrapped), "GraphQL", priority=10)
257315

258316
# Run and collect errors
259317
errors = wrapped(*args, **kwargs)
@@ -267,6 +325,7 @@ def wrap_validate(wrapped, instance, args, kwargs):
267325

268326
return errors
269327

328+
270329
def wrap_parse(wrapped, instance, args, kwargs):
271330
transaction = current_transaction()
272331
if transaction is None:
@@ -281,7 +340,9 @@ def bind_resolve_field_v3(parent_type, source, field_nodes, path):
281340
return parent_type, field_nodes, path
282341

283342

284-
def bind_resolve_field_v2(exe_context, parent_type, source, field_asts, parent_info, field_path):
343+
def bind_resolve_field_v2(
344+
exe_context, parent_type, source, field_asts, parent_info, field_path
345+
):
285346
return parent_type, field_asts, field_path
286347

287348

@@ -323,7 +384,8 @@ def bind_execute_graphql_query(
323384
operation_name=None,
324385
middleware=None,
325386
backend=None,
326-
**execute_options):
387+
**execute_options
388+
):
327389

328390
return request_string
329391

@@ -335,9 +397,9 @@ def wrap_graphql_impl(wrapped, instance, args, kwargs):
335397
return wrapped(*args, **kwargs)
336398

337399
version = graphql_version()
338-
framework_version = '.'.join(map(str, version))
400+
framework_version = ".".join(map(str, version))
339401

340-
transaction.add_framework_info(name='GraphQL', version=framework_version)
402+
transaction.add_framework_info(name="GraphQL", version=framework_version)
341403

342404
if graphql_version() <= (3, 0, 0):
343405
bind_query = bind_execute_graphql_query
@@ -378,9 +440,8 @@ def instrument_graphql_execute(module):
378440
wrap_function_wrapper(module, "resolve_field", wrap_resolve_field)
379441

380442
if hasattr(module, "execute_operation"):
381-
wrap_function_wrapper(
382-
module, "execute_operation", wrap_execute_operation
383-
)
443+
wrap_function_wrapper(module, "execute_operation", wrap_execute_operation)
444+
384445

385446
def instrument_graphql_execution_utils(module):
386447
if hasattr(module, "ExecutionContext"):

0 commit comments

Comments
 (0)