Skip to content

Commit 6b024a3

Browse files
committed
Add field usage query analyzer
1 parent 9b6fd30 commit 6b024a3

File tree

3 files changed

+106
-0
lines changed

3 files changed

+106
-0
lines changed

lib/graphql/analysis.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
require "graphql/analysis/query_complexity"
44
require "graphql/analysis/query_depth"
55
require "graphql/analysis/analyze_query"
6+
require "graphql/analysis/field_usage"

lib/graphql/analysis/field_usage.rb

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
module GraphQL
2+
module Analysis
3+
# A query reducer for tracking both field usage and deprecated field usage.
4+
#
5+
# @example Logging field usage and deprecated field usage
6+
# Schema.query_analyzers << GraphQL::Analysis::FieldUsage.new { |query, used_fields, used_deprecated_fields|
7+
# puts "Used GraphQL fields: #{used_fields.join(', ')}"
8+
# puts "Used deprecated GraphQL fields: #{used_deprecated_fields.join(', ')}"
9+
# }
10+
# Schema.execute(query_str)
11+
# # Used GraphQL fields: Cheese.id, Cheese.fatContent, Query.cheese
12+
# # Used deprecated GraphQL fields: Cheese.fatContent
13+
#
14+
class FieldUsage
15+
def initialize(&block)
16+
@field_usage_handler = block
17+
end
18+
19+
def initial_value(query)
20+
{
21+
query: query,
22+
used_fields: Set.new,
23+
used_deprecated_fields: Set.new
24+
}
25+
end
26+
27+
def call(memo, visit_type, irep_node)
28+
if irep_node.ast_node.is_a?(GraphQL::Language::Nodes::Field) && visit_type == :leave
29+
irep_node.definitions.each do |type_defn, field_defn|
30+
field = "#{type_defn.name}.#{field_defn.name}"
31+
memo[:used_fields] << field
32+
memo[:used_deprecated_fields] << field if field_defn.deprecation_reason
33+
end
34+
end
35+
36+
memo
37+
end
38+
39+
def final_value(memo)
40+
@field_usage_handler.call(memo[:query], memo[:used_fields].to_a, memo[:used_deprecated_fields].to_a)
41+
end
42+
end
43+
end
44+
end
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
require "spec_helper"
2+
3+
describe GraphQL::Analysis::FieldUsage do
4+
let(:result) { [] }
5+
let(:field_usage_analyzer) { GraphQL::Analysis::FieldUsage.new { |query, used_fields, used_deprecated_fields| result << query << used_fields << used_deprecated_fields } }
6+
let(:reduce_result) { GraphQL::Analysis.analyze_query(query, [field_usage_analyzer]) }
7+
let(:query) { GraphQL::Query.new(DummySchema, query_string, variables: variables) }
8+
let(:variables) { {} }
9+
10+
describe "query with deprecated fields" do
11+
let(:query_string) {%|
12+
query {
13+
cheese(id: 1) {
14+
id
15+
fatContent
16+
}
17+
}
18+
|}
19+
20+
it "returns query in reduced result" do
21+
reduce_result
22+
assert_equal query, result[0]
23+
end
24+
25+
it "keeps track of used fields" do
26+
reduce_result
27+
assert_equal ['Cheese.id', 'Cheese.fatContent', 'Query.cheese'], result[1]
28+
end
29+
30+
it "keeps track of deprecated fields" do
31+
reduce_result
32+
assert_equal ['Cheese.fatContent'], result[2]
33+
end
34+
end
35+
36+
describe "query with deprecated fields used more than once" do
37+
let(:query_string) {%|
38+
query {
39+
cheese1: cheese(id: 1) {
40+
id
41+
fatContent
42+
}
43+
44+
cheese2: cheese(id: 2) {
45+
id
46+
fatContent
47+
}
48+
}
49+
|}
50+
51+
it "omits duplicate usage of a field" do
52+
reduce_result
53+
assert_equal ['Cheese.id', 'Cheese.fatContent', 'Query.cheese'], result[1]
54+
end
55+
56+
it "omits duplicate usage of a deprecated field" do
57+
reduce_result
58+
assert_equal ['Cheese.fatContent'], result[2]
59+
end
60+
end
61+
end

0 commit comments

Comments
 (0)