graphiti_gql 0.2.26 → 0.2.29

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 00d26d5be2b1e9f1f277e0d0922faec91599f72a28fabe77ff16fb1f2ae6870f
4
- data.tar.gz: a868703b6bf2a2fbf88464396f0fe17ba836ffc183cee22e2aad06b5bd6a1223
3
+ metadata.gz: ade099acc586ef5902b1b30659ca47efed5cfb3e05e3fe1a778fa81b31b27528
4
+ data.tar.gz: 3a2226feab0dd284c3aeb832b30d90dc8165a9abc03fc40921aab47eece3d315
5
5
  SHA512:
6
- metadata.gz: 02f91817b443a31d0eaa280316ddd1ab18691936e401c431a38354370f8a6a1b435076fa022df6d37041f35e08d06fb99fc8104aa957cca4e3b9507bba828117
7
- data.tar.gz: 94ca96b977800303c54ecf61fabfeeca65c4b839fc1ae501e6192fdbb30af86181a1aada59d64a47339c28ff3ca4e5469d071a27db9280a59e8c0b339b0f70f6
6
+ metadata.gz: 318d2eb2831f5d19bdcbdd9fbe6dd0a21fed8435875d9d4de0ca45b65b97515b5ca4f80afca282a5c1e11b85986133f5942ef4e96b21b4cb31ce492d35a07728
7
+ data.tar.gz: bc7b778005b056904a8fc4d3be767ee62a711f07fff533ddac833b96723abc5fc8368e9f63ecf2ddc40aee59544990939ff7d26a570a95ccc15ce2d8d5e93542
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- graphiti_gql (0.2.26)
4
+ graphiti_gql (0.2.29)
5
5
  activemodel (> 6.0, < 8.0)
6
6
  graphiti (~> 1.3.9)
7
7
  graphql (~> 2.0)
@@ -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
@@ -10,6 +10,7 @@ module GraphitiGql
10
10
  end
11
11
 
12
12
  def perform(ids)
13
+ Graphiti.broadcast("association", { sideload: @sideload })
13
14
  # process nils
14
15
  ids.each { |id| fulfill(id, nil) if id.nil? }
15
16
  ids.compact!
@@ -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
- stats = object.proxy.stats.deep_dup
14
- stats.each_pair do |attr, calc|
15
- calc.each_pair do |calc_name, value|
16
- if value.is_a?(Hash)
17
- stats[attr][calc_name] = value[parent.id]
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),
@@ -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
@@ -1,3 +1,3 @@
1
1
  module GraphitiGql
2
- VERSION = "0.2.26"
2
+ VERSION = "0.2.29"
3
3
  end
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 :application_controller
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
- result = schema.execute query_string,
80
+ payload = {
81
+ query: query_string,
67
82
  variables: variables,
68
83
  context: context
69
- result.to_h
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.26
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-15 00:00:00.000000000 Z
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