graphiti_gql 0.2.26 → 0.2.29
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/lib/graphiti_gql/engine.rb +11 -2
- data/lib/graphiti_gql/exception_handler.rb +52 -0
- data/lib/graphiti_gql/graphiti_hax.rb +23 -0
- data/lib/graphiti_gql/loaders/belongs_to.rb +1 -0
- data/lib/graphiti_gql/loaders/many.rb +1 -0
- data/lib/graphiti_gql/log_subscriber.rb +177 -0
- data/lib/graphiti_gql/schema/fields/stats.rb +9 -6
- data/lib/graphiti_gql/schema/list_arguments.rb +2 -0
- data/lib/graphiti_gql/schema.rb +8 -0
- data/lib/graphiti_gql/version.rb +1 -1
- data/lib/graphiti_gql.rb +27 -3
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ade099acc586ef5902b1b30659ca47efed5cfb3e05e3fe1a778fa81b31b27528
|
4
|
+
data.tar.gz: 3a2226feab0dd284c3aeb832b30d90dc8165a9abc03fc40921aab47eece3d315
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 318d2eb2831f5d19bdcbdd9fbe6dd0a21fed8435875d9d4de0ca45b65b97515b5ca4f80afca282a5c1e11b85986133f5942ef4e96b21b4cb31ce492d35a07728
|
7
|
+
data.tar.gz: bc7b778005b056904a8fc4d3be767ee62a711f07fff533ddac833b96723abc5fc8368e9f63ecf2ddc40aee59544990939ff7d26a570a95ccc15ce2d8d5e93542
|
data/Gemfile.lock
CHANGED
data/lib/graphiti_gql/engine.rb
CHANGED
@@ -2,11 +2,20 @@ module GraphitiGql
|
|
2
2
|
class Engine < ::Rails::Engine
|
3
3
|
isolate_namespace GraphitiGql
|
4
4
|
|
5
|
-
# TODO improvable?
|
6
5
|
config.to_prepare do
|
7
|
-
# initializer "graphiti_gql.generate_schema" do
|
8
6
|
Dir.glob("#{Rails.root}/app/resources/**/*").each { |f| require(f) }
|
9
7
|
GraphitiGql.schema!
|
8
|
+
|
9
|
+
log_level = ENV.fetch('GRAPHITI_LOG_LEVEL', '1').to_i
|
10
|
+
log_activerecord = false
|
11
|
+
if log_level == -1 && defined?(ActiveRecord)
|
12
|
+
log_level = 0
|
13
|
+
log_activerecord = true
|
14
|
+
end
|
15
|
+
Graphiti.logger.level = log_level
|
16
|
+
if GraphitiGql.config.log
|
17
|
+
GraphitiGql::LogSubscriber.subscribe!(activerecord: log_activerecord)
|
18
|
+
end
|
10
19
|
end
|
11
20
|
end
|
12
21
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module GraphitiGql
|
2
|
+
class ExceptionHandler
|
3
|
+
attr_reader :error, :context, :field
|
4
|
+
class_attribute :registry, :log, :notify, :default_message, :default_code
|
5
|
+
|
6
|
+
self.registry = {}
|
7
|
+
self.default_message = "We're sorry, something went wrong."
|
8
|
+
self.default_code = 500
|
9
|
+
|
10
|
+
def self.register_exception(err, opts)
|
11
|
+
registry[err] = opts
|
12
|
+
end
|
13
|
+
|
14
|
+
register_exception Graphiti::Errors::RecordNotFound, code: 404
|
15
|
+
register_exception Graphiti::Errors::SingularSideload, code: 400
|
16
|
+
register_exception Graphiti::Errors::InvalidAttributeAccess, code: 403
|
17
|
+
register_exception GraphitiGql::Errors::UnsupportedLast, code: 400
|
18
|
+
|
19
|
+
def initialize(err, obj, args, ctx, field)
|
20
|
+
@error = err
|
21
|
+
@obj = obj
|
22
|
+
@args = args
|
23
|
+
@context = ctx
|
24
|
+
@field = field
|
25
|
+
@config = get_config(err)
|
26
|
+
end
|
27
|
+
|
28
|
+
def notify
|
29
|
+
# noop
|
30
|
+
end
|
31
|
+
|
32
|
+
def log
|
33
|
+
# noop
|
34
|
+
end
|
35
|
+
|
36
|
+
def handle
|
37
|
+
notify if @config[:notify] != false
|
38
|
+
log if @config[:log] != false
|
39
|
+
|
40
|
+
message = @config[:message] ? err.message : default_message
|
41
|
+
code = @config[:code] || default_code
|
42
|
+
raise GraphQL::ExecutionError.new(message, extensions: { code: code })
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def get_config(error)
|
48
|
+
registered = registry.find { |e, _| error.is_a?(e) }
|
49
|
+
registered ? registered[1] : {}
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -143,6 +143,12 @@ module GraphitiGql
|
|
143
143
|
deprecation_reason: opts[:deprecation_reason]
|
144
144
|
)
|
145
145
|
end
|
146
|
+
|
147
|
+
def all(*args)
|
148
|
+
params = args[0]
|
149
|
+
Graphiti.broadcast("resource.all", { params: params, resource: self })
|
150
|
+
super
|
151
|
+
end
|
146
152
|
end
|
147
153
|
end
|
148
154
|
Graphiti::Resource.send(:prepend, ResourceExtras)
|
@@ -519,4 +525,21 @@ class Graphiti::ValueObjectAssociation
|
|
519
525
|
instance.parent = parent
|
520
526
|
instance
|
521
527
|
end
|
528
|
+
end
|
529
|
+
|
530
|
+
module Graphiti
|
531
|
+
def self.debug(msg, color = :white, bold = false)
|
532
|
+
log(msg, color, bold, level: :debug)
|
533
|
+
end
|
534
|
+
|
535
|
+
def self.info(msg, color = :white, bold = false)
|
536
|
+
log(msg, color, bold, level: :info)
|
537
|
+
end
|
538
|
+
|
539
|
+
def self.log(msg, color = :white, bold = false, level: nil)
|
540
|
+
return unless ::GraphitiGql.config.log
|
541
|
+
colored = ActiveSupport::LogSubscriber.new.send(:color, msg, color, bold)
|
542
|
+
level ||= :debug
|
543
|
+
logger.send(level, colored)
|
544
|
+
end
|
522
545
|
end
|
@@ -19,6 +19,7 @@ module GraphitiGql
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def perform(parent_records)
|
22
|
+
Graphiti.broadcast("association", { sideload: @sideload })
|
22
23
|
raise ::Graphiti::Errors::UnsupportedPagination if paginating? && parent_records.length > 1
|
23
24
|
raise Errors::UnsupportedStats if requesting_stats? && parent_records.length > 1 && !can_group?
|
24
25
|
|
@@ -0,0 +1,177 @@
|
|
1
|
+
# TODO: remove OG graphiti debugger
|
2
|
+
module GraphitiGql
|
3
|
+
class LogSubscriber
|
4
|
+
def self.subscribe!(activerecord: false)
|
5
|
+
instance = LogSubscriber.new
|
6
|
+
instance.subscribe!('resolve', :on_data)
|
7
|
+
instance.subscribe!('schema.before_execute', :on_schema_before_execute)
|
8
|
+
instance.subscribe!('schema.execute', :on_schema_execute)
|
9
|
+
instance.subscribe!('resource.all', :on_resource_all)
|
10
|
+
instance.subscribe!('association', :on_association)
|
11
|
+
instance.subscribe!('before_stats', :on_before_stats)
|
12
|
+
instance.subscribe!('after_stats', :on_after_stats)
|
13
|
+
if activerecord
|
14
|
+
ActiveSupport::Notifications
|
15
|
+
.subscribe("sql.active_record", instance.method(:on_activerecord))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
@chunks = {}
|
21
|
+
end
|
22
|
+
|
23
|
+
def subscribe!(name, method_name)
|
24
|
+
ActiveSupport::Notifications
|
25
|
+
.subscribe("#{name}.graphiti", method(method_name))
|
26
|
+
end
|
27
|
+
|
28
|
+
def on_data(name, start, stop, id, payload)
|
29
|
+
@resolving = false
|
30
|
+
if payload[:exception]
|
31
|
+
@error_on_resolve = true
|
32
|
+
return
|
33
|
+
end
|
34
|
+
|
35
|
+
num_results = payload[:results].length
|
36
|
+
klasses = payload[:results].map(&:class).map(&:name).uniq
|
37
|
+
color = num_results == 0 ? :yellow : :green
|
38
|
+
stmt = "#{indent} #{num_results} #{"result".pluralize(num_results)}"
|
39
|
+
stmt << " of #{"type".pluralize(klasses.length)} #{klasses.to_sentence}" if num_results > 0
|
40
|
+
add_chunk(stmt, color, true)
|
41
|
+
|
42
|
+
took = ((stop - start) * 1000.0).round(2)
|
43
|
+
add_chunk("#{indent} Took: #{took}ms", :magenta, true)
|
44
|
+
end
|
45
|
+
|
46
|
+
def on_schema_before_execute(name, start, stop, id, payload)
|
47
|
+
Graphiti.debug(payload[:query].strip_heredoc, :white, true)
|
48
|
+
unless payload[:variables].empty?
|
49
|
+
Graphiti.debug("✨ Variables: #{payload[:variables].inspect}", :yellow, true)
|
50
|
+
end
|
51
|
+
unless payload[:context].empty?
|
52
|
+
Graphiti.debug("✨ Context: #{payload[:context].inspect}", :blue, true)
|
53
|
+
end
|
54
|
+
Graphiti.debug(%|💡 Debug tip! Override Resource#resolve:
|
55
|
+
|
56
|
+
class YourResource < ApplicationResource
|
57
|
+
# ... code ...
|
58
|
+
def resolve(scope)
|
59
|
+
debugger
|
60
|
+
# if activerecord, call scope.to_sql/scope.to_a
|
61
|
+
super
|
62
|
+
end
|
63
|
+
end|, :white, true)
|
64
|
+
Graphiti.debug("🤠🚀🤠🚀🤠🚀🤠🚀🤠🚀🤠🚀🤠🚀🤠 Executing! 🤠🚀🤠🚀🤠🚀🤠🚀🤠🚀🤠🚀🤠🚀🤠", :white, true)
|
65
|
+
end
|
66
|
+
|
67
|
+
def on_schema_execute(name, start, stop, id, payload)
|
68
|
+
if payload[:exception] || (response_errors = payload[:result]["errors"])
|
69
|
+
indent = indent(path: @last_association_path)
|
70
|
+
add_chunk("#{indent}❌🚨❌🚨❌🚨❌ ERROR! ❌🚨❌🚨❌🚨❌", :red, true, path: @last_association_path)
|
71
|
+
if @error_on_resolve
|
72
|
+
add_chunk("#{indent}This error occurred while executing the above query, so it's likely not caused by Graphiti itself. Maybe bad SQL? Try running again and putting a debugger in this Resource's #resolve, or try to run the query independent of Graphiti/GraphQL.",
|
73
|
+
:red, true, path: @last_association_path)
|
74
|
+
end
|
75
|
+
flush_chunks(@chunks)
|
76
|
+
if response_errors
|
77
|
+
Graphiti.info("❌🚨 Response contained errors!", :red, true)
|
78
|
+
response_errors.each do |err|
|
79
|
+
Graphiti.info("#{err['extensions']['code']} - #{err['message']}", :red, true)
|
80
|
+
Graphiti.info("#{err['path'].join(".")}", :red, false) if err['path']
|
81
|
+
end
|
82
|
+
end
|
83
|
+
else
|
84
|
+
flush_chunks(@chunks)
|
85
|
+
took = ((stop - start) * 1000.0).round(2)
|
86
|
+
Graphiti.info("✅ Completed successfully in #{took}ms", :magenta, true)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def on_resource_all(name, start, stop, id, payload)
|
91
|
+
@resolving = true
|
92
|
+
params = payload[:params].inspect
|
93
|
+
resource = payload[:resource].name
|
94
|
+
if thin_path.length == 1
|
95
|
+
add_chunk("Query.#{thin_path.first}", :yellow, true)
|
96
|
+
end
|
97
|
+
add_chunk("#{indent}\\_ #{resource}.all(#{params})", :cyan, true)
|
98
|
+
end
|
99
|
+
|
100
|
+
def on_association(name, start, stop, id, payload)
|
101
|
+
@last_association_path = thin_path
|
102
|
+
sideload = payload[:sideload]
|
103
|
+
add_chunk("#{indent}🔗 #{sideload.type} :#{sideload.name}", :white, true)
|
104
|
+
end
|
105
|
+
|
106
|
+
def on_before_stats(name, start, stop, id, payload)
|
107
|
+
@stats = true
|
108
|
+
add_chunk("#{indent}🔢 Calculating Statistics...", :yellow, true)
|
109
|
+
end
|
110
|
+
|
111
|
+
def on_after_stats(name, start, stop, id, payload)
|
112
|
+
@stats = false
|
113
|
+
took = ((stop - start) * 1000.0).round(2)
|
114
|
+
add_chunk("#{indent}🔢 Done! Took #{took}ms", :yellow, true)
|
115
|
+
end
|
116
|
+
|
117
|
+
def on_activerecord(name, start, stop, id, payload)
|
118
|
+
if @resolving || @stats
|
119
|
+
sql = payload[:sql]
|
120
|
+
unless sql.starts_with?('SHOW ')
|
121
|
+
add_chunk("#{indent}#{sql}", :blue, true)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
def flush_chunks(chunks)
|
129
|
+
chunks.each_pair do |_, value|
|
130
|
+
value[:lines].each do |line|
|
131
|
+
Graphiti.info(line[:text], line[:color], line[:bold])
|
132
|
+
end
|
133
|
+
flush_chunks(value.except(:lines))
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def add_chunk(text, color, bold, path: nil)
|
138
|
+
path ||= thin_path
|
139
|
+
current_chunks = @chunks
|
140
|
+
path.each_with_index do |subpath, index|
|
141
|
+
last = index == path.length - 1
|
142
|
+
line = { text: text, color: color, bold: bold }
|
143
|
+
if current_chunks.key?(subpath)
|
144
|
+
if last
|
145
|
+
current_chunks[subpath][:lines] << line
|
146
|
+
else
|
147
|
+
current_chunks = current_chunks[subpath]
|
148
|
+
end
|
149
|
+
else
|
150
|
+
current_chunks[subpath] ||= { lines: [] }
|
151
|
+
current_chunks[subpath][:lines] << line
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def thin_path
|
157
|
+
path = Graphiti.context[:object][:current_path]
|
158
|
+
return [] unless path
|
159
|
+
path.reject do |p|
|
160
|
+
p.is_a?(Integer) ||
|
161
|
+
p == "nodes" ||
|
162
|
+
p == "node" ||
|
163
|
+
p == "edges"
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def indent(path: nil)
|
168
|
+
path ||= thin_path
|
169
|
+
" " * [path.length - 1, 0].max
|
170
|
+
end
|
171
|
+
|
172
|
+
def current_path
|
173
|
+
path = Graphiti.context[:object][:current_path].join(".")
|
174
|
+
"Query.#{path}"
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
@@ -9,16 +9,19 @@ module GraphitiGql
|
|
9
9
|
def apply(type)
|
10
10
|
type.field :stats, build_stat_class, null: false
|
11
11
|
type.define_method :stats do
|
12
|
+
Graphiti.broadcast('before_stats', {})
|
12
13
|
# Process grouped (to-many relationship) stats
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
Graphiti.broadcast('after_stats', {}) do
|
15
|
+
stats = object.proxy.stats.deep_dup
|
16
|
+
stats.each_pair do |attr, calc|
|
17
|
+
calc.each_pair do |calc_name, value|
|
18
|
+
if value.is_a?(Hash)
|
19
|
+
stats[attr][calc_name] = value[parent.id]
|
20
|
+
end
|
18
21
|
end
|
19
22
|
end
|
23
|
+
stats
|
20
24
|
end
|
21
|
-
stats
|
22
25
|
end
|
23
26
|
type
|
24
27
|
end
|
@@ -54,6 +54,8 @@ module GraphitiGql
|
|
54
54
|
end
|
55
55
|
end
|
56
56
|
@resource.filters.each_pair do |name, config|
|
57
|
+
next if config[:schema] == false
|
58
|
+
|
57
59
|
attr_type = generate_filter_attribute_type(type_name, name, config)
|
58
60
|
required = !!config[:required] || required_via_group.include?(name)
|
59
61
|
klass.argument name.to_s.camelize(:lower),
|
data/lib/graphiti_gql/schema.rb
CHANGED
@@ -101,6 +101,14 @@ module GraphitiGql
|
|
101
101
|
klass.connections.add(ResponseShim, Connection)
|
102
102
|
klass.connections.add(Array, ToManyConnection)
|
103
103
|
klass.orphan_types [GraphQL::Types::JSON]
|
104
|
+
klass.rescue_from(Exception) do |err, obj, args, ctx, field|
|
105
|
+
if GraphitiGql.config.error_handling
|
106
|
+
handler = GraphitiGql.config.exception_handler
|
107
|
+
handler.new(err, obj, args, ctx, field).handle
|
108
|
+
else
|
109
|
+
raise err
|
110
|
+
end
|
111
|
+
end
|
104
112
|
klass
|
105
113
|
end
|
106
114
|
end
|
data/lib/graphiti_gql/version.rb
CHANGED
data/lib/graphiti_gql.rb
CHANGED
@@ -27,13 +27,27 @@ require "graphiti_gql/schema/fields/to_one"
|
|
27
27
|
require "graphiti_gql/schema/fields/attribute"
|
28
28
|
require "graphiti_gql/schema/fields/stats"
|
29
29
|
require "graphiti_gql/active_resource"
|
30
|
+
require "graphiti_gql/exception_handler"
|
31
|
+
require "graphiti_gql/log_subscriber"
|
30
32
|
require "graphiti_gql/engine" if defined?(Rails)
|
31
33
|
|
32
34
|
module GraphitiGql
|
33
35
|
class Error < StandardError; end
|
34
36
|
|
35
37
|
class Configuration
|
36
|
-
attr_accessor :
|
38
|
+
attr_accessor :exception_handler, :error_handling, :logging
|
39
|
+
|
40
|
+
def exception_handler
|
41
|
+
@exception_handler ||= ExceptionHandler
|
42
|
+
end
|
43
|
+
|
44
|
+
def error_handling
|
45
|
+
@error_handling != false
|
46
|
+
end
|
47
|
+
|
48
|
+
def log
|
49
|
+
@log ||= !ENV['GRAPHITI_LOG_LEVEL'].nil?
|
50
|
+
end
|
37
51
|
end
|
38
52
|
|
39
53
|
def self.schema!
|
@@ -63,10 +77,20 @@ module GraphitiGql
|
|
63
77
|
context = Graphiti.context[:object]
|
64
78
|
end
|
65
79
|
Graphiti.with_context(context) do
|
66
|
-
|
80
|
+
payload = {
|
81
|
+
query: query_string,
|
67
82
|
variables: variables,
|
68
83
|
context: context
|
69
|
-
|
84
|
+
}
|
85
|
+
Graphiti.broadcast("schema.before_execute", payload)
|
86
|
+
Graphiti.broadcast("schema.execute", payload) do
|
87
|
+
result = schema.execute query_string,
|
88
|
+
variables: variables,
|
89
|
+
context: context
|
90
|
+
result_hash = result.to_h
|
91
|
+
payload[:result] = result_hash
|
92
|
+
result_hash
|
93
|
+
end
|
70
94
|
end
|
71
95
|
end
|
72
96
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphiti_gql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.29
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lee Richmond
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-08-
|
11
|
+
date: 2022-08-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: graphql
|
@@ -146,6 +146,7 @@ files:
|
|
146
146
|
- lib/graphiti_gql/active_resource.rb
|
147
147
|
- lib/graphiti_gql/engine.rb
|
148
148
|
- lib/graphiti_gql/errors.rb
|
149
|
+
- lib/graphiti_gql/exception_handler.rb
|
149
150
|
- lib/graphiti_gql/graphiti_hax.rb
|
150
151
|
- lib/graphiti_gql/loaders/belongs_to.rb
|
151
152
|
- lib/graphiti_gql/loaders/has_many.rb
|
@@ -153,6 +154,7 @@ files:
|
|
153
154
|
- lib/graphiti_gql/loaders/many.rb
|
154
155
|
- lib/graphiti_gql/loaders/many_to_many.rb
|
155
156
|
- lib/graphiti_gql/loaders/polymorphic_has_many.rb
|
157
|
+
- lib/graphiti_gql/log_subscriber.rb
|
156
158
|
- lib/graphiti_gql/response_shim.rb
|
157
159
|
- lib/graphiti_gql/schema.rb
|
158
160
|
- lib/graphiti_gql/schema/connection.rb
|