optics-agent 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +15 -0
- data/LICENSE +21 -0
- data/README.md +139 -0
- data/lib/apollo/optics/proto/reports_pb.rb +145 -0
- data/lib/optics-agent.rb +4 -0
- data/lib/optics-agent/agent.rb +56 -0
- data/lib/optics-agent/graphql-middleware.rb +18 -0
- data/lib/optics-agent/instrumentation/introspection-query.graphql +91 -0
- data/lib/optics-agent/instrumentation/query-schema.rb +9 -0
- data/lib/optics-agent/normalization/latency.rb +14 -0
- data/lib/optics-agent/normalization/query.rb +156 -0
- data/lib/optics-agent/rack-middleware.rb +43 -0
- data/lib/optics-agent/reporting/helpers.rb +32 -0
- data/lib/optics-agent/reporting/query-trace.rb +54 -0
- data/lib/optics-agent/reporting/query.rb +65 -0
- data/lib/optics-agent/reporting/report.rb +102 -0
- data/lib/optics-agent/reporting/report_job.rb +24 -0
- data/lib/optics-agent/reporting/schema.rb +56 -0
- data/lib/optics-agent/reporting/schema_job.rb +14 -0
- data/lib/optics-agent/reporting/send-message.rb +23 -0
- data/spec/graphql-middleware_spec.rb +46 -0
- data/spec/latency_spec.rb +34 -0
- data/spec/query-normalization_spec.rb +105 -0
- data/spec/query_trace_spec.rb +43 -0
- data/spec/report_spec.rb +194 -0
- data/spec/schema-introspection_spec.rb +27 -0
- data/spec/schema_spec.rb +33 -0
- data/spec/spec_helper.rb +103 -0
- metadata +148 -0
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'sucker_punch'
|
2
|
+
require 'optics-agent/reporting/report'
|
3
|
+
|
4
|
+
module OpticsAgent::Reporting
|
5
|
+
class ReportJob
|
6
|
+
include SuckerPunch::Job
|
7
|
+
|
8
|
+
def perform(agent)
|
9
|
+
report = OpticsAgent::Reporting::Report.new
|
10
|
+
agent.clear_query_queue.each do |item|
|
11
|
+
report.add_query(*item)
|
12
|
+
|
13
|
+
# XXX: don't send *every* trace
|
14
|
+
query_trace = QueryTrace.new(*item)
|
15
|
+
query_trace.send
|
16
|
+
end
|
17
|
+
|
18
|
+
report.decorate_from_schema(agent.schema)
|
19
|
+
report.send
|
20
|
+
|
21
|
+
self.class.perform_in(60, agent)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'graphql'
|
3
|
+
|
4
|
+
require 'apollo/optics/proto/reports_pb'
|
5
|
+
require 'optics-agent/reporting/helpers'
|
6
|
+
require 'optics-agent/reporting/send-message'
|
7
|
+
require 'optics-agent/instrumentation/query-schema'
|
8
|
+
|
9
|
+
module OpticsAgent::Reporting
|
10
|
+
# A report for a whole schema
|
11
|
+
class Schema
|
12
|
+
include Apollo::Optics::Proto
|
13
|
+
include OpticsAgent::Instrumentation
|
14
|
+
include OpticsAgent::Reporting
|
15
|
+
|
16
|
+
attr_accessor :message
|
17
|
+
|
18
|
+
def initialize(schema)
|
19
|
+
@message = SchemaReport.new({
|
20
|
+
header: generate_report_header(),
|
21
|
+
introspection_result: JSON.generate(introspect_schema(schema)),
|
22
|
+
type: get_types(schema)
|
23
|
+
})
|
24
|
+
end
|
25
|
+
|
26
|
+
# construct an array of Type (protobuf) objects
|
27
|
+
def get_types(schema)
|
28
|
+
types = []
|
29
|
+
|
30
|
+
schema.types.keys.each do |type_name|
|
31
|
+
next if type_name =~ /^__/
|
32
|
+
type = schema.types[type_name]
|
33
|
+
next unless type.is_a? GraphQL::ObjectType
|
34
|
+
|
35
|
+
fields = type.fields.values.map do |field|
|
36
|
+
Field.new({
|
37
|
+
name: field.name,
|
38
|
+
# XXX: does this actually work for all types?
|
39
|
+
returnType: field.type.to_s
|
40
|
+
})
|
41
|
+
end
|
42
|
+
|
43
|
+
types << Type.new({
|
44
|
+
name: type_name,
|
45
|
+
field: fields
|
46
|
+
})
|
47
|
+
end
|
48
|
+
|
49
|
+
types
|
50
|
+
end
|
51
|
+
|
52
|
+
def send
|
53
|
+
send_message('/api/ss/schema', @message)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'sucker_punch'
|
2
|
+
require 'optics-agent/reporting/schema'
|
3
|
+
|
4
|
+
module OpticsAgent::Reporting
|
5
|
+
class SchemaJob
|
6
|
+
include SuckerPunch::Job
|
7
|
+
|
8
|
+
def perform(agent)
|
9
|
+
puts 'performing schema job'
|
10
|
+
schema = OpticsAgent::Reporting::Schema.new agent.schema
|
11
|
+
schema.send
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
|
3
|
+
module OpticsAgent
|
4
|
+
module Reporting
|
5
|
+
OPTICS_URL = 'https://optics-report.apollodata.com'
|
6
|
+
def send_message(path, message)
|
7
|
+
|
8
|
+
req = Net::HTTP::Post.new(path)
|
9
|
+
req['x-api-key'] = ENV['OPTICS_API_KEY']
|
10
|
+
req['user-agent'] = "optics-agent-rb"
|
11
|
+
|
12
|
+
req.body = message.class.encode(message)
|
13
|
+
puts message.class.encode_json(message)
|
14
|
+
|
15
|
+
uri = URI.parse(OPTICS_URL)
|
16
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
17
|
+
http.use_ssl = true
|
18
|
+
res = http.request(req)
|
19
|
+
p res
|
20
|
+
p res.body
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'optics-agent/graphql-middleware'
|
2
|
+
require 'graphql'
|
3
|
+
|
4
|
+
include OpticsAgent
|
5
|
+
|
6
|
+
describe GraphqlMiddleware do
|
7
|
+
it 'collects the correct query stats' do
|
8
|
+
person_type = GraphQL::ObjectType.define do
|
9
|
+
name "Person"
|
10
|
+
field :firstName do
|
11
|
+
type types.String
|
12
|
+
resolve -> (obj, args, ctx) { sleep(0.100); return 'Tom' }
|
13
|
+
end
|
14
|
+
field :lastName do
|
15
|
+
type types.String
|
16
|
+
resolve -> (obj, args, ctx) { sleep(0.100); return 'Coleman' }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
query_type = GraphQL::ObjectType.define do
|
20
|
+
name 'Query'
|
21
|
+
field :person do
|
22
|
+
type person_type
|
23
|
+
resolve -> (obj, args, ctx) { sleep(0.050); return {} }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
schema = GraphQL::Schema.define do
|
28
|
+
query query_type
|
29
|
+
end
|
30
|
+
|
31
|
+
schema.middleware << GraphqlMiddleware.new
|
32
|
+
|
33
|
+
query = spy("query")
|
34
|
+
schema.execute('{ person { firstName lastName } }', {
|
35
|
+
context: { optics_agent: { query: query } }
|
36
|
+
})
|
37
|
+
|
38
|
+
expect(query).to have_received(:report_field).exactly(3).times
|
39
|
+
expect(query).to have_received(:report_field)
|
40
|
+
.with('Query', 'person', be_instance_of(Time), be_instance_of(Time))
|
41
|
+
expect(query).to have_received(:report_field)
|
42
|
+
.with('Person', 'firstName', be_instance_of(Time), be_instance_of(Time))
|
43
|
+
expect(query).to have_received(:report_field)
|
44
|
+
.with('Person', 'lastName', be_instance_of(Time), be_instance_of(Time))
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'optics-agent/normalization/latency'
|
2
|
+
include OpticsAgent::Normalization
|
3
|
+
|
4
|
+
|
5
|
+
describe 'latency helpers' do
|
6
|
+
describe 'empty_latency_count' do
|
7
|
+
it 'returns 256 zeros' do
|
8
|
+
zeros = empty_latency_count
|
9
|
+
expect(zeros.length).to equal(256)
|
10
|
+
zeros.each { |z| expect(z).to eq(0) }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe 'latency_bucket' do
|
15
|
+
it 'returns the right values' do
|
16
|
+
tests = [
|
17
|
+
[0.1, 0],
|
18
|
+
[0.9, 0],
|
19
|
+
[1, 0],
|
20
|
+
[1.1, 1],
|
21
|
+
[1.21, 2],
|
22
|
+
[100, 49],
|
23
|
+
[1000, 73],
|
24
|
+
[1000 * 1000, 145],
|
25
|
+
[1.1**254, 255],
|
26
|
+
[1000 * 1000 * 1000 * 1000, 255]
|
27
|
+
]
|
28
|
+
|
29
|
+
tests.each do |test|
|
30
|
+
expect(latency_bucket test.first).to eq(test.last)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'optics-agent/normalization/query'
|
2
|
+
|
3
|
+
TEST_QUERIES = [
|
4
|
+
[
|
5
|
+
'basic test',
|
6
|
+
'{
|
7
|
+
user {
|
8
|
+
name
|
9
|
+
}
|
10
|
+
}',
|
11
|
+
'{user {name}}',
|
12
|
+
],
|
13
|
+
[
|
14
|
+
'basic test with query',
|
15
|
+
'query {
|
16
|
+
user {
|
17
|
+
name
|
18
|
+
}
|
19
|
+
}',
|
20
|
+
'{user {name}}',
|
21
|
+
],
|
22
|
+
[
|
23
|
+
'basic with operation name',
|
24
|
+
'query OpName {
|
25
|
+
user {
|
26
|
+
name
|
27
|
+
}
|
28
|
+
}',
|
29
|
+
'query OpName {user {name}}',
|
30
|
+
],
|
31
|
+
[
|
32
|
+
'with various inline types',
|
33
|
+
'query OpName {
|
34
|
+
user {
|
35
|
+
name(apple: [[10]], cat: ENUM_VALUE, bag: {input: "value"})
|
36
|
+
}
|
37
|
+
}',
|
38
|
+
'query OpName {user {name(apple:[], bag:{}, cat:ENUM_VALUE)}}',
|
39
|
+
],
|
40
|
+
[
|
41
|
+
'with various argument types',
|
42
|
+
'query OpName($c: Int!, $a: [[Boolean!]!], $b: EnumType) {
|
43
|
+
user {
|
44
|
+
name(apple: $a, cat: $c, bag: $b)
|
45
|
+
}
|
46
|
+
}',
|
47
|
+
'query OpName($a:[[Boolean!]!],$b:EnumType,$c:Int!) {user {name(apple:$a, bag:$b, cat:$c)}}',
|
48
|
+
],
|
49
|
+
[
|
50
|
+
'fragment',
|
51
|
+
'{
|
52
|
+
user {
|
53
|
+
name
|
54
|
+
...Bar
|
55
|
+
}
|
56
|
+
}
|
57
|
+
fragment Bar on User {
|
58
|
+
asd
|
59
|
+
}
|
60
|
+
fragment Baz on User {
|
61
|
+
jkl
|
62
|
+
}',
|
63
|
+
'{user {name ...Bar}} fragment Bar on User {asd}',
|
64
|
+
],
|
65
|
+
[
|
66
|
+
'full test',
|
67
|
+
'query Foo ($b: Int, $a: Boolean){
|
68
|
+
user(name: "hello", age: 5) {
|
69
|
+
... Bar
|
70
|
+
... on User {
|
71
|
+
hello
|
72
|
+
bee
|
73
|
+
}
|
74
|
+
tz
|
75
|
+
aliased: name
|
76
|
+
}
|
77
|
+
}
|
78
|
+
fragment Baz on User {
|
79
|
+
asd
|
80
|
+
}
|
81
|
+
fragment Bar on User {
|
82
|
+
age @skip(if: $a)
|
83
|
+
...Nested
|
84
|
+
}
|
85
|
+
fragment Nested on User {
|
86
|
+
blah
|
87
|
+
}',
|
88
|
+
'query Foo($a:Boolean,$b:Int) {user(age:0, name:"") {name tz ...Bar ... on User {bee hello}}}' +
|
89
|
+
' fragment Bar on User {age @skip(if:$a) ...Nested} fragment Nested on User {blah}',
|
90
|
+
],
|
91
|
+
]
|
92
|
+
|
93
|
+
|
94
|
+
describe OpticsAgent::Normalization::Query do
|
95
|
+
include OpticsAgent::Normalization::Query
|
96
|
+
|
97
|
+
TEST_QUERIES.each do |spec|
|
98
|
+
test_name, query, expected_signature = spec
|
99
|
+
|
100
|
+
it test_name do
|
101
|
+
signature = normalize(query)
|
102
|
+
expect(signature).to eq(expected_signature)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'optics-agent/reporting/query-trace'
|
2
|
+
require 'optics-agent/reporting/query'
|
3
|
+
require 'apollo/optics/proto/reports_pb'
|
4
|
+
require 'graphql'
|
5
|
+
|
6
|
+
include Apollo::Optics::Proto
|
7
|
+
include OpticsAgent::Reporting
|
8
|
+
|
9
|
+
class DocumentMock
|
10
|
+
def initialize(key)
|
11
|
+
@key = key
|
12
|
+
end
|
13
|
+
|
14
|
+
def [](name) # used for [:query]
|
15
|
+
@key
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
describe QueryTrace do
|
21
|
+
it "can represent a simple query" do
|
22
|
+
query = Query.new
|
23
|
+
query.report_field 'Person', 'firstName', 1, 1.1
|
24
|
+
query.report_field 'Person', 'lastName', 1, 1.1
|
25
|
+
query.report_field 'Query', 'person', 1, 1.22
|
26
|
+
query.document = DocumentMock.new('key')
|
27
|
+
|
28
|
+
trace = QueryTrace.new(query, {}, 1, 1.25)
|
29
|
+
|
30
|
+
expect(trace.report).to be_instance_of(TracesReport)
|
31
|
+
expect(trace.report.trace.length).to eq(1)
|
32
|
+
|
33
|
+
trace_obj = trace.report.trace.first
|
34
|
+
nodes = trace_obj.execute.child
|
35
|
+
expect(nodes.length).to eq(3)
|
36
|
+
expect(nodes.map(&:field_name)).to \
|
37
|
+
match_array(['Query.person', 'Person.firstName', 'Person.lastName'])
|
38
|
+
|
39
|
+
firstName_node = nodes.find { |n| n.field_name == 'Person.firstName' }
|
40
|
+
expect(firstName_node.start_time).to eq(0)
|
41
|
+
expect(firstName_node.end_time).to eq(0.1 * 1e9)
|
42
|
+
end
|
43
|
+
end
|
data/spec/report_spec.rb
ADDED
@@ -0,0 +1,194 @@
|
|
1
|
+
require 'optics-agent/reporting/report'
|
2
|
+
require 'optics-agent/reporting/query'
|
3
|
+
require 'apollo/optics/proto/reports_pb'
|
4
|
+
require 'graphql'
|
5
|
+
|
6
|
+
include OpticsAgent::Reporting
|
7
|
+
include Apollo::Optics::Proto
|
8
|
+
|
9
|
+
class DocumentMock
|
10
|
+
def initialize(key)
|
11
|
+
@key = key
|
12
|
+
end
|
13
|
+
|
14
|
+
def [](name) # used for [:query]
|
15
|
+
@key
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
describe Report do
|
21
|
+
it "can represent a simple query" do
|
22
|
+
query = Query.new
|
23
|
+
query.report_field 'Person', 'firstName', 1, 1.1
|
24
|
+
query.report_field 'Person', 'lastName', 1, 1.1
|
25
|
+
query.report_field 'Query', 'person', 1, 1.22
|
26
|
+
query.document = DocumentMock.new('key')
|
27
|
+
|
28
|
+
report = Report.new
|
29
|
+
report.add_query query, {}, 1, 1.25
|
30
|
+
report.finish!
|
31
|
+
|
32
|
+
expect(report.report).to be_an_instance_of(StatsReport)
|
33
|
+
stats_report = report.report
|
34
|
+
expect(stats_report.per_signature.keys).to match_array(['key'])
|
35
|
+
|
36
|
+
signature_stats = stats_report.per_signature.values.first
|
37
|
+
expect(signature_stats.per_type.length).to equal(2)
|
38
|
+
expect(signature_stats.per_type.map &:name).to match_array(['Person', 'Query'])
|
39
|
+
|
40
|
+
person_stats = signature_stats.per_type.find { |s| s.name === 'Person' }
|
41
|
+
expect(person_stats.field.length).to equal(2)
|
42
|
+
expect(person_stats.field.map &:name).to match_array(['firstName', 'lastName'])
|
43
|
+
|
44
|
+
firstName_stats = person_stats.field.find { |s| s.name === 'firstName' }
|
45
|
+
expect(firstName_stats.latency_count.length).to eq(256)
|
46
|
+
expect(firstName_stats.latency_count.reduce(&:+)).to eq(1)
|
47
|
+
expect(firstName_stats.latency_count[121]).to eq(1)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "can aggregate the results of multiple queries with the same shape" do
|
51
|
+
queryOne = Query.new
|
52
|
+
queryOne.report_field 'Person', 'firstName', 1, 1.1
|
53
|
+
queryOne.report_field 'Person', 'lastName', 1, 1.1
|
54
|
+
queryOne.report_field 'Query', 'person', 1, 1.22
|
55
|
+
queryOne.document = DocumentMock.new('key')
|
56
|
+
|
57
|
+
queryTwo = Query.new
|
58
|
+
queryTwo.report_field 'Person', 'firstName', 1, 1.05
|
59
|
+
queryTwo.report_field 'Person', 'lastName', 1, 1.05
|
60
|
+
queryTwo.report_field 'Query', 'person', 1, 1.2
|
61
|
+
queryTwo.document = DocumentMock.new('key')
|
62
|
+
|
63
|
+
report = Report.new
|
64
|
+
report.add_query queryOne, {}, 1, 1.1
|
65
|
+
report.add_query queryTwo, {}, 1, 1.1
|
66
|
+
report.finish!
|
67
|
+
|
68
|
+
expect(report.report).to be_an_instance_of(StatsReport)
|
69
|
+
stats_report = report.report
|
70
|
+
expect(stats_report.per_signature.keys).to match_array(['key'])
|
71
|
+
|
72
|
+
signature_stats = stats_report.per_signature.values.first
|
73
|
+
expect(signature_stats.per_type.length).to equal(2)
|
74
|
+
expect(signature_stats.per_type.map &:name).to match_array(['Person', 'Query'])
|
75
|
+
|
76
|
+
person_stats = signature_stats.per_type.find { |s| s.name === 'Person' }
|
77
|
+
expect(person_stats.field.length).to equal(2)
|
78
|
+
expect(person_stats.field.map &:name).to match_array(['firstName', 'lastName'])
|
79
|
+
|
80
|
+
firstName_stats = person_stats.field.find { |s| s.name === 'firstName' }
|
81
|
+
expect(firstName_stats.latency_count.reduce(&:+)).to eq(2)
|
82
|
+
expect(firstName_stats.latency_count[114]).to eq(1)
|
83
|
+
expect(firstName_stats.latency_count[121]).to eq(1)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "can aggregate the results of multiple queries with a different shape" do
|
87
|
+
queryOne = Query.new
|
88
|
+
queryOne.report_field 'Person', 'firstName', 1, 1.1
|
89
|
+
queryOne.report_field 'Person', 'lastName', 1, 1.1
|
90
|
+
queryOne.report_field 'Query', 'person', 1, 1.22
|
91
|
+
queryOne.document = DocumentMock.new('keyOne')
|
92
|
+
|
93
|
+
queryTwo = Query.new
|
94
|
+
queryTwo.report_field 'Person', 'firstName', 1, 1.05
|
95
|
+
queryTwo.report_field 'Person', 'lastName', 1, 1.05
|
96
|
+
queryTwo.report_field 'Query', 'person', 1, 1.02
|
97
|
+
queryTwo.document = DocumentMock.new('keyTwo')
|
98
|
+
|
99
|
+
report = Report.new
|
100
|
+
report.add_query queryOne, {}, 1, 1.1
|
101
|
+
report.add_query queryTwo, {}, 1, 1.1
|
102
|
+
report.finish!
|
103
|
+
|
104
|
+
expect(report.report).to be_an_instance_of(StatsReport)
|
105
|
+
stats_report = report.report
|
106
|
+
expect(stats_report.per_signature.keys).to match_array(['keyOne', 'keyTwo'])
|
107
|
+
|
108
|
+
signature_stats = stats_report.per_signature['keyOne']
|
109
|
+
expect(signature_stats.per_type.length).to equal(2)
|
110
|
+
expect(signature_stats.per_type.map &:name).to match_array(['Person', 'Query'])
|
111
|
+
|
112
|
+
person_stats = signature_stats.per_type.find { |s| s.name === 'Person' }
|
113
|
+
expect(person_stats.field.length).to equal(2)
|
114
|
+
expect(person_stats.field.map &:name).to match_array(['firstName', 'lastName'])
|
115
|
+
|
116
|
+
firstName_stats = person_stats.field.find { |s| s.name === 'firstName' }
|
117
|
+
expect(firstName_stats.latency_count.reduce(&:+)).to eq(1)
|
118
|
+
expect(firstName_stats.latency_count[121]).to eq(1)
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
it "can decorate it's fields with resultTypes from a schema" do
|
123
|
+
query = Query.new
|
124
|
+
query.report_field 'Person', 'firstName', 1, 1.1
|
125
|
+
query.report_field 'Person', 'age', 1, 1.1
|
126
|
+
query.document = DocumentMock.new('key')
|
127
|
+
|
128
|
+
report = Report.new
|
129
|
+
report.add_query query, {}, 1, 1.25
|
130
|
+
report.finish!
|
131
|
+
|
132
|
+
person_type = GraphQL::ObjectType.define do
|
133
|
+
name 'Person'
|
134
|
+
field :firstName, types.String
|
135
|
+
field :age, !types.Int
|
136
|
+
end
|
137
|
+
query_type = GraphQL::ObjectType.define do
|
138
|
+
name 'Query'
|
139
|
+
field :person, person_type
|
140
|
+
end
|
141
|
+
|
142
|
+
schema = GraphQL::Schema.define do
|
143
|
+
query query_type
|
144
|
+
end
|
145
|
+
|
146
|
+
report.decorate_from_schema(schema)
|
147
|
+
|
148
|
+
stats_report = report.report
|
149
|
+
signature_stats = stats_report.per_signature.values.first
|
150
|
+
person_stats = signature_stats.per_type.find { |s| s.name === 'Person' }
|
151
|
+
|
152
|
+
firstName_stats = person_stats.field.find { |s| s.name === 'firstName' }
|
153
|
+
expect(firstName_stats.returnType).to eq('String')
|
154
|
+
|
155
|
+
age_stats = person_stats.field.find { |s| s.name === 'age' }
|
156
|
+
expect(age_stats.returnType).to eq('Int!')
|
157
|
+
end
|
158
|
+
|
159
|
+
it "can handle introspection fields" do
|
160
|
+
query = Query.new
|
161
|
+
query.report_field 'Query', '__schema', 1, 1.1
|
162
|
+
query.report_field 'Query', '__typename', 1, 1.1
|
163
|
+
query.report_field 'Query', '__type', 1, 1.1
|
164
|
+
query.document = DocumentMock.new('key')
|
165
|
+
|
166
|
+
report = Report.new
|
167
|
+
report.add_query query, {}, 1, 1.25
|
168
|
+
report.finish!
|
169
|
+
|
170
|
+
query_type = GraphQL::ObjectType.define do
|
171
|
+
name 'Query'
|
172
|
+
end
|
173
|
+
|
174
|
+
schema = GraphQL::Schema.define do
|
175
|
+
query query_type
|
176
|
+
end
|
177
|
+
|
178
|
+
report.decorate_from_schema(schema)
|
179
|
+
|
180
|
+
stats_report = report.report
|
181
|
+
signature_stats = stats_report.per_signature.values.first
|
182
|
+
query_stats = signature_stats.per_type.find { |s| s.name === 'Query' }
|
183
|
+
|
184
|
+
schema_stats = query_stats.field.find { |s| s.name === '__schema' }
|
185
|
+
expect(schema_stats.returnType).to eq('__Schema')
|
186
|
+
|
187
|
+
type_stats = query_stats.field.find { |s| s.name === '__type' }
|
188
|
+
expect(type_stats.returnType).to eq('__Type')
|
189
|
+
|
190
|
+
typename_stats = query_stats.field.find { |s| s.name === '__typename' }
|
191
|
+
expect(typename_stats.returnType).to eq('Query')
|
192
|
+
end
|
193
|
+
|
194
|
+
end
|