activeadmin-graphql 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.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +6 -0
  3. data/CODE_OF_CONDUCT.md +31 -0
  4. data/CONTRIBUTING.md +27 -0
  5. data/LICENSE.md +21 -0
  6. data/README.md +49 -0
  7. data/activeadmin-graphql.gemspec +66 -0
  8. data/app/controllers/active_admin/graphql_controller.rb +168 -0
  9. data/docs/graphql-api.md +486 -0
  10. data/lib/active_admin/graphql/auth_context.rb +35 -0
  11. data/lib/active_admin/graphql/engine.rb +9 -0
  12. data/lib/active_admin/graphql/integration.rb +135 -0
  13. data/lib/active_admin/graphql/key_value_pair_input.rb +48 -0
  14. data/lib/active_admin/graphql/railtie.rb +10 -0
  15. data/lib/active_admin/graphql/record_source.rb +30 -0
  16. data/lib/active_admin/graphql/resource_config.rb +68 -0
  17. data/lib/active_admin/graphql/resource_definition_dsl.rb +117 -0
  18. data/lib/active_admin/graphql/resource_interface.rb +25 -0
  19. data/lib/active_admin/graphql/resource_query_proxy/controller.rb +149 -0
  20. data/lib/active_admin/graphql/resource_query_proxy.rb +112 -0
  21. data/lib/active_admin/graphql/run_action_mutation_config.rb +23 -0
  22. data/lib/active_admin/graphql/run_action_mutation_dsl.rb +32 -0
  23. data/lib/active_admin/graphql/run_action_payload.rb +27 -0
  24. data/lib/active_admin/graphql/schema_builder/build.rb +84 -0
  25. data/lib/active_admin/graphql/schema_builder/graph_params.rb +75 -0
  26. data/lib/active_admin/graphql/schema_builder/mutation_action_types.rb +52 -0
  27. data/lib/active_admin/graphql/schema_builder/mutation_batch.rb +61 -0
  28. data/lib/active_admin/graphql/schema_builder/mutation_collection.rb +118 -0
  29. data/lib/active_admin/graphql/schema_builder/mutation_create.rb +65 -0
  30. data/lib/active_admin/graphql/schema_builder/mutation_member.rb +122 -0
  31. data/lib/active_admin/graphql/schema_builder/mutation_type_builder.rb +52 -0
  32. data/lib/active_admin/graphql/schema_builder/mutation_update_destroy.rb +120 -0
  33. data/lib/active_admin/graphql/schema_builder/query_type.rb +53 -0
  34. data/lib/active_admin/graphql/schema_builder/query_type_collection.rb +84 -0
  35. data/lib/active_admin/graphql/schema_builder/query_type_member.rb +91 -0
  36. data/lib/active_admin/graphql/schema_builder/query_type_pages.rb +44 -0
  37. data/lib/active_admin/graphql/schema_builder/query_type_registered.rb +57 -0
  38. data/lib/active_admin/graphql/schema_builder/resolvers.rb +116 -0
  39. data/lib/active_admin/graphql/schema_builder/resources.rb +48 -0
  40. data/lib/active_admin/graphql/schema_builder/types_inputs.rb +119 -0
  41. data/lib/active_admin/graphql/schema_builder/types_object.rb +96 -0
  42. data/lib/active_admin/graphql/schema_builder/visibility.rb +58 -0
  43. data/lib/active_admin/graphql/schema_builder/wire.rb +36 -0
  44. data/lib/active_admin/graphql/schema_builder.rb +62 -0
  45. data/lib/active_admin/graphql/schema_field.rb +29 -0
  46. data/lib/active_admin/graphql/version.rb +7 -0
  47. data/lib/active_admin/graphql.rb +68 -0
  48. data/lib/active_admin/primary_key.rb +117 -0
  49. data/lib/activeadmin/graphql.rb +5 -0
  50. metadata +389 -0
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAdmin
4
+ module GraphQL
5
+ class SchemaBuilder
6
+ module MutationUpdateDestroy
7
+ private
8
+
9
+ def mutation_update_field(sb, ns, aa_res, model, type_c)
10
+ fname = "update_#{aa_res.resource_name.route_key.singularize.tr("-", "_")}"
11
+ find_type = @find_input_types[model]
12
+ update_input = @update_input_types[model]
13
+
14
+ proc do
15
+ field fname.to_sym, type_c, null: true, camelize: false,
16
+ visibility: {kind: :mutation_update, graphql_type_name: sb.send(:graphql_type_name_for, aa_res), resource: aa_res},
17
+ description: "Update #{model.name}" do
18
+ argument :where, find_type, required: true, camelize: false
19
+ argument :input, update_input, required: true, camelize: false
20
+ end
21
+
22
+ define_method(fname.to_sym) do |where:, input:, **|
23
+ auth = context[:auth]
24
+ blob = where.to_h.stringify_keys
25
+ graph = sb.graph_params_from_find_blob(aa_res, blob)
26
+ begin
27
+ rid = ActiveAdmin::PrimaryKey.member_param_hash(model, blob)
28
+ rescue ArgumentError => e
29
+ raise ::GraphQL::ExecutionError, e.message
30
+ end
31
+
32
+ proxy = ResourceQueryProxy.new(
33
+ aa_resource: aa_res,
34
+ user: auth.user,
35
+ namespace: ns,
36
+ graph_params: graph
37
+ )
38
+ record = proxy.find_member(rid)
39
+ raise ::GraphQL::ExecutionError, "not found" unless record
40
+
41
+ unless auth.authorized?(aa_res, ActiveAdmin::Authorization::UPDATE, record)
42
+ raise ::GraphQL::ExecutionError, "not authorized to update this record"
43
+ end
44
+
45
+ attrs = sb.assignable_slice_from_input(aa_res, input)
46
+ record = if (hook = aa_res.graphql_config.resolve_update_proc)
47
+ hook.call(
48
+ proxy: proxy,
49
+ input: input,
50
+ attributes: attrs,
51
+ record: record,
52
+ context: context,
53
+ auth: auth,
54
+ aa_resource: aa_res
55
+ )
56
+ else
57
+ attrs = attrs.stringify_keys.slice(*aa_res.graphql_assignable_attribute_names)
58
+ unless record.update(attrs)
59
+ raise ::GraphQL::ExecutionError, record.errors.full_messages.to_sentence
60
+ end
61
+ record
62
+ end
63
+ record
64
+ end
65
+ end
66
+ end
67
+
68
+ def mutation_destroy_field(sb, ns, aa_res, model)
69
+ fname = "delete_#{aa_res.resource_name.route_key.singularize.tr("-", "_")}"
70
+ find_type = @find_input_types[model]
71
+
72
+ proc do
73
+ field fname.to_sym, ::GraphQL::Types::Boolean, null: false, camelize: false,
74
+ visibility: {kind: :mutation_delete, graphql_type_name: sb.send(:graphql_type_name_for, aa_res), resource: aa_res},
75
+ description: "Delete #{model.name}" do
76
+ argument :where, find_type, required: true, camelize: false
77
+ end
78
+
79
+ define_method(fname.to_sym) do |where:, **|
80
+ auth = context[:auth]
81
+ blob = where.to_h.stringify_keys
82
+ graph = sb.graph_params_from_find_blob(aa_res, blob)
83
+ begin
84
+ rid = ActiveAdmin::PrimaryKey.member_param_hash(model, blob)
85
+ rescue ArgumentError => e
86
+ raise ::GraphQL::ExecutionError, e.message
87
+ end
88
+
89
+ proxy = ResourceQueryProxy.new(
90
+ aa_resource: aa_res,
91
+ user: auth.user,
92
+ namespace: ns,
93
+ graph_params: graph
94
+ )
95
+ record = proxy.find_member(rid)
96
+ raise ::GraphQL::ExecutionError, "not found" unless record
97
+
98
+ unless auth.authorized?(aa_res, ActiveAdmin::Authorization::DESTROY, record)
99
+ raise ::GraphQL::ExecutionError, "not authorized to destroy this record"
100
+ end
101
+
102
+ if (hook = aa_res.graphql_config.resolve_destroy_proc)
103
+ hook.call(
104
+ proxy: proxy,
105
+ record: record,
106
+ context: context,
107
+ auth: auth,
108
+ aa_resource: aa_res
109
+ )
110
+ else
111
+ record.destroy!
112
+ end
113
+ true
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAdmin
4
+ module GraphQL
5
+ class SchemaBuilder
6
+ module QueryType
7
+ include QueryTypeRegistered
8
+ include QueryTypeCollection
9
+ include QueryTypeMember
10
+ include QueryTypePages
11
+
12
+ def build_query_type(registered_resource_union:)
13
+ builder = self
14
+ ns = @namespace
15
+ aa_by_model = @aa_by_model
16
+ object_types = @object_types
17
+ aa_by_graphql_type_name = @aa_by_graphql_type_name
18
+ list_filter_input_types = @list_filter_input_types
19
+ find_input_types = @find_input_types
20
+
21
+ Class.new(::GraphQL::Schema::Object) do
22
+ field_class ::ActiveAdmin::GraphQL::SchemaField
23
+
24
+ graphql_name "Query"
25
+ description "ActiveAdmin GraphQL API (#{ns.name})"
26
+
27
+ if registered_resource_union
28
+ builder.add_registered_resource_query_field!(
29
+ self,
30
+ builder: builder,
31
+ ns: ns,
32
+ registered_resource_union: registered_resource_union,
33
+ aa_by_graphql_type_name: aa_by_graphql_type_name
34
+ )
35
+ end
36
+
37
+ builder.add_model_query_fields!(
38
+ self,
39
+ builder: builder,
40
+ ns: ns,
41
+ aa_by_model: aa_by_model,
42
+ object_types: object_types,
43
+ list_filter_input_types: list_filter_input_types,
44
+ find_input_types: find_input_types
45
+ )
46
+
47
+ builder.add_page_query_fields!(self, builder: builder, ns: ns)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAdmin
4
+ module GraphQL
5
+ class SchemaBuilder
6
+ module QueryTypeCollection
7
+ def add_model_query_fields!(query_class, builder:, ns:, aa_by_model:, object_types:,
8
+ list_filter_input_types:, find_input_types:)
9
+ aa_by_model.each do |model, aa_res|
10
+ builder.define_query_collection_field!(
11
+ query_class, model, aa_res, builder, ns, object_types, list_filter_input_types
12
+ )
13
+ builder.define_query_member_field!(
14
+ query_class, model, aa_res, builder, ns, object_types, find_input_types
15
+ )
16
+ end
17
+ end
18
+
19
+ def define_query_collection_field!(query_class, model, aa_res, builder, ns, object_types,
20
+ list_filter_input_types)
21
+ plural_route = aa_res.resource_name.route_key.tr("-", "_")
22
+ type_gn = builder.send(:graphql_type_name_for, aa_res)
23
+ filter_type = list_filter_input_types[model]
24
+ type_c = object_types[model]
25
+ return unless type_c
26
+
27
+ query_class.class_eval do
28
+ field plural_route.to_sym, type_c.connection_type, null: false, connection: true, camelize: false,
29
+ visibility: {
30
+ kind: :query_collection_field,
31
+ graphql_type_name: type_gn,
32
+ field_name: plural_route,
33
+ resource: aa_res
34
+ },
35
+ description: "Paginated list of #{model.name} (#{aa_res.resource_name.human}); " \
36
+ "pass +filter+ or legacy +q+ / +scope+ / +order+ (Ransack +q+ matches the JSON index param). " \
37
+ "Nested resources require the parent foreign key (same as REST route params)." do
38
+ argument :filter, filter_type, required: false, camelize: false
39
+ argument :scope, ::GraphQL::Types::String, required: false, camelize: false
40
+ argument :q, ::GraphQL::Types::JSON, required: false, camelize: false
41
+ argument :order, ::GraphQL::Types::String, required: false, camelize: false
42
+ if (btc = aa_res.belongs_to_config)
43
+ argument btc.to_param.to_sym, ::GraphQL::Types::ID, required: btc.required?, camelize: false
44
+ end
45
+ end
46
+
47
+ define_method(plural_route.to_sym) do |filter: nil, scope: nil, q: nil, order: nil, **kw|
48
+ auth = context[:auth]
49
+ unless auth.authorized?(aa_res, ActiveAdmin::Authorization::READ, model)
50
+ raise ::GraphQL::ExecutionError, "not authorized to read #{model.name}"
51
+ end
52
+
53
+ graph_params = builder.list_graph_params(
54
+ aa_res,
55
+ filter: filter,
56
+ scope: scope,
57
+ q: q,
58
+ order: order,
59
+ **kw
60
+ )
61
+ proxy = ResourceQueryProxy.new(
62
+ aa_resource: aa_res,
63
+ user: auth.user,
64
+ namespace: ns,
65
+ graph_params: graph_params
66
+ )
67
+ builder.graphql_resolve_index(
68
+ aa_res,
69
+ proxy: proxy,
70
+ context: context,
71
+ graph_params: graph_params,
72
+ filter: filter,
73
+ scope: scope,
74
+ q: q,
75
+ order: order,
76
+ **kw
77
+ )
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAdmin
4
+ module GraphQL
5
+ class SchemaBuilder
6
+ module QueryTypeMember
7
+ def define_query_member_field!(query_class, model, aa_res, builder, ns, object_types, find_input_types)
8
+ singular = aa_res.resource_name.route_key.singularize.camelize(:lower)
9
+ type_gn = builder.send(:graphql_type_name_for, aa_res)
10
+ find_type = find_input_types[model]
11
+ type_c = object_types[model]
12
+ return unless type_c
13
+
14
+ query_class.class_eval do
15
+ field singular.to_sym, type_c, null: true, camelize: false,
16
+ visibility: {
17
+ kind: :query_member_field,
18
+ graphql_type_name: type_gn,
19
+ field_name: singular,
20
+ resource: aa_res
21
+ },
22
+ description: "Find #{model.name} by id (same scoping chain as REST +show+). Use +where+ or legacy +id+." do
23
+ argument :where, find_type, required: false, camelize: false
24
+ if ActiveAdmin::PrimaryKey.composite?(model)
25
+ argument :id, ::GraphQL::Types::ID, required: false, camelize: false,
26
+ description: "JSON object string with all primary keys, or use per-key arguments"
27
+ else
28
+ argument :id, ::GraphQL::Types::ID, required: false, camelize: false
29
+ end
30
+ if ActiveAdmin::PrimaryKey.composite?(model)
31
+ ActiveAdmin::PrimaryKey.ordered_columns(model).each do |col|
32
+ coldef = model.columns_hash[col]
33
+ next unless coldef
34
+
35
+ gql_t = builder.send(:graphql_scalar_for_column, aa_res, model, coldef)
36
+ argument col.to_sym, gql_t, required: false, camelize: false
37
+ end
38
+ end
39
+ if (btc = aa_res.belongs_to_config)
40
+ argument btc.to_param.to_sym, ::GraphQL::Types::ID, required: false, camelize: false
41
+ end
42
+ end
43
+
44
+ define_method(singular.to_sym) do |where: nil, id: nil, **kw|
45
+ auth = context[:auth]
46
+ unless auth.authorized?(aa_res, ActiveAdmin::Authorization::READ, model)
47
+ raise ::GraphQL::ExecutionError, "not authorized to read #{model.name}"
48
+ end
49
+
50
+ begin
51
+ if where
52
+ blob = where.to_h.stringify_keys
53
+ graph = builder.graph_params_from_find_blob(aa_res, blob)
54
+ rid = ActiveAdmin::PrimaryKey.member_param_hash(model, blob)
55
+ else
56
+ graph = builder.graph_params_for_mutation(aa_res, kw)
57
+ rid = ActiveAdmin::PrimaryKey.field_kw_to_param_hash(model, id: id, **kw)
58
+ end
59
+ rescue ArgumentError => e
60
+ raise ::GraphQL::ExecutionError, e.message
61
+ end
62
+
63
+ proxy = ResourceQueryProxy.new(
64
+ aa_resource: aa_res,
65
+ user: auth.user,
66
+ namespace: ns,
67
+ graph_params: graph
68
+ )
69
+ record = builder.graphql_resolve_show(
70
+ aa_res,
71
+ proxy: proxy,
72
+ context: context,
73
+ id: rid,
74
+ graph_params: graph,
75
+ where: where,
76
+ id_argument: id
77
+ )
78
+ return nil unless record
79
+
80
+ unless auth.authorized?(aa_res, ActiveAdmin::Authorization::READ, record)
81
+ raise ::GraphQL::ExecutionError, "not authorized to read this record"
82
+ end
83
+
84
+ record
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAdmin
4
+ module GraphQL
5
+ class SchemaBuilder
6
+ module QueryTypePages
7
+ def add_page_query_fields!(query_class, builder:, ns:)
8
+ ns.resources.each do |page|
9
+ next unless page.is_a?(ActiveAdmin::Page)
10
+
11
+ page.graphql_fields.each do |spec|
12
+ fname = spec[:name].to_sym
13
+ gql_t = spec[:type]
14
+ null = spec[:null]
15
+ desc = spec[:description]
16
+ resolver = spec[:resolver]
17
+
18
+ if gql_t.nil?
19
+ raise ArgumentError,
20
+ "graphql_field :#{spec[:name]} on page #{page.name.inspect} requires a GraphQL type"
21
+ end
22
+
23
+ kwargs = {null: null, camelize: false, visibility: {kind: :query_page_field, field_name: fname.to_s, page: page}}
24
+ kwargs[:description] = desc if desc
25
+ query_class.class_eval do
26
+ field fname, gql_t, **kwargs
27
+
28
+ define_method(fname) do
29
+ auth = context[:auth]
30
+ unless auth.authorized?(page, ActiveAdmin::Authorization::READ, page)
31
+ raise ::GraphQL::ExecutionError, "not authorized for page field #{spec[:name]}"
32
+ end
33
+ raise ::GraphQL::ExecutionError, "graphql_field :#{spec[:name]} needs a resolver block" unless resolver
34
+
35
+ instance_exec(auth.user, context, &resolver)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAdmin
4
+ module GraphQL
5
+ class SchemaBuilder
6
+ module QueryTypeRegistered
7
+ def add_registered_resource_query_field!(query_class, builder:, ns:, registered_resource_union:,
8
+ aa_by_graphql_type_name:)
9
+ query_class.class_eval do
10
+ field :registered_resource, registered_resource_union, null: true, camelize: false,
11
+ visibility: {kind: :query_registered_resource},
12
+ description: "Load any registered resource by GraphQL type name (+type_name+), " \
13
+ "mirroring singular resource queries. Use +path+ for nested belongs_to route params." do
14
+ argument :type_name, ::GraphQL::Types::String, required: true, camelize: false
15
+ argument :id, ::GraphQL::Types::ID, required: true, camelize: false
16
+ argument :path, [KeyValuePairInput], required: false, camelize: false,
17
+ description: "Parent route params as flat key/value pairs (same keys as nested REST segments)."
18
+ end
19
+
20
+ define_method(:registered_resource) do |type_name:, id:, path: nil|
21
+ aa_res = aa_by_graphql_type_name[type_name.to_s]
22
+ raise ::GraphQL::ExecutionError, "unknown resource type_name #{type_name.inspect}" unless aa_res
23
+
24
+ model = aa_res.resource_class
25
+ auth = context[:auth]
26
+ unless auth.authorized?(aa_res, ActiveAdmin::Authorization::READ, model)
27
+ raise ::GraphQL::ExecutionError, "not authorized to read #{model.name}"
28
+ end
29
+
30
+ graph_params = KeyValuePairs.to_hash(path)
31
+ proxy = ResourceQueryProxy.new(
32
+ aa_resource: aa_res,
33
+ user: auth.user,
34
+ namespace: ns,
35
+ graph_params: graph_params
36
+ )
37
+ record = builder.graphql_resolve_show(
38
+ aa_res,
39
+ proxy: proxy,
40
+ context: context,
41
+ id: id,
42
+ graph_params: graph_params
43
+ )
44
+ return nil unless record
45
+
46
+ unless auth.authorized?(aa_res, ActiveAdmin::Authorization::READ, record)
47
+ raise ::GraphQL::ExecutionError, "not authorized to read this record"
48
+ end
49
+
50
+ record
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAdmin
4
+ module GraphQL
5
+ class SchemaBuilder
6
+ module Resolvers
7
+ def mutation_extra_keyword_params(aa_res, kw)
8
+ skip =
9
+ if (btc = aa_res.belongs_to_config)
10
+ [btc.to_param.to_s]
11
+ else
12
+ []
13
+ end
14
+ kw.each_with_object({}) do |(key, val), h|
15
+ ks = key.to_s
16
+ next if skip.include?(ks)
17
+
18
+ h[ks] = val
19
+ end
20
+ end
21
+
22
+ def graphql_resolve_index(aa_res, proxy:, context:, graph_params:, **field_kwargs)
23
+ proc_ = aa_res.graphql_config.resolve_index_proc
24
+ base_kwargs = {graph_params: graph_params}.merge(field_kwargs)
25
+ if proc_
26
+ proc_.call(proxy: proxy, context: context, aa_resource: aa_res, auth: context[:auth], **base_kwargs)
27
+ else
28
+ proxy.relation_for_index
29
+ end
30
+ end
31
+
32
+ def graphql_resolve_show(aa_res, proxy:, context:, id:, graph_params:, **field_kwargs)
33
+ proc_ = aa_res.graphql_config.resolve_show_proc
34
+ base_kwargs = {id: id, graph_params: graph_params}.merge(field_kwargs)
35
+ if proc_
36
+ proc_.call(proxy: proxy, context: context, aa_resource: aa_res, auth: context[:auth], **base_kwargs)
37
+ else
38
+ proxy.find_member(id)
39
+ end
40
+ end
41
+
42
+ def graphql_resolve_batch_action(aa_res, proxy:, context:, batch_action:, ids:, inputs:)
43
+ inputs_h = coerce_action_param_map(inputs)
44
+ if (p = aa_res.graphql_config.batch_run_action.resolve_proc)
45
+ p.call(
46
+ proxy: proxy,
47
+ context: context,
48
+ aa_resource: aa_res,
49
+ auth: context[:auth],
50
+ batch_action: batch_action,
51
+ ids: ids,
52
+ inputs: inputs_h
53
+ )
54
+ else
55
+ proxy.run_batch_action(batch_action, ids, inputs: inputs_h)
56
+ end
57
+ end
58
+
59
+ def graphql_resolve_member_action(aa_res, proxy:, context:, action:, id:, params: nil, **kw)
60
+ extras = mutation_extra_keyword_params(aa_res, kw)
61
+ params_h = coerce_action_param_map(params).merge(extras.transform_keys(&:to_s))
62
+ per = aa_res.graphql_config.member_action_mutations[action.to_s]
63
+ resolve = per&.resolve_proc || aa_res.graphql_config.member_run_action.resolve_proc
64
+ if resolve
65
+ resolve.call(
66
+ proxy: proxy,
67
+ context: context,
68
+ aa_resource: aa_res,
69
+ auth: context[:auth],
70
+ action: action,
71
+ id: id,
72
+ params: params_h,
73
+ **extras
74
+ )
75
+ else
76
+ proxy.run_member_action(action, id, extra_params: params_h)
77
+ end
78
+ end
79
+
80
+ def graphql_resolve_collection_action(aa_res, proxy:, context:, action:, params: nil, **kw)
81
+ extras = mutation_extra_keyword_params(aa_res, kw)
82
+ params_h = coerce_action_param_map(params).merge(extras.transform_keys(&:to_s))
83
+ per = aa_res.graphql_config.collection_action_mutations[action.to_s]
84
+ resolve = per&.resolve_proc || aa_res.graphql_config.collection_run_action.resolve_proc
85
+ if resolve
86
+ resolve.call(
87
+ proxy: proxy,
88
+ context: context,
89
+ aa_resource: aa_res,
90
+ auth: context[:auth],
91
+ action: action,
92
+ params: params_h,
93
+ **extras
94
+ )
95
+ else
96
+ proxy.run_collection_action(action, extra_params: params_h)
97
+ end
98
+ end
99
+
100
+ def coerce_action_param_map(value)
101
+ return {} if value.nil?
102
+ return KeyValuePairs.to_hash(value) if value.is_a?(Array)
103
+
104
+ h = value.respond_to?(:to_unsafe_h) ? value.to_unsafe_h : value.to_h
105
+ h.stringify_keys
106
+ end
107
+
108
+ public :graphql_resolve_index,
109
+ :graphql_resolve_show,
110
+ :graphql_resolve_batch_action,
111
+ :graphql_resolve_member_action,
112
+ :graphql_resolve_collection_action
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAdmin
4
+ module GraphQL
5
+ class SchemaBuilder
6
+ module Resources
7
+ private
8
+
9
+ def namespace_resources
10
+ @namespace.resources.select { |r| r.is_a?(ActiveAdmin::Resource) }
11
+ end
12
+
13
+ def active_resources
14
+ namespace_resources.select do |r|
15
+ next false if r.graphql_config.disabled?
16
+ next false unless r.resource_class < ActiveRecord::Base
17
+
18
+ true
19
+ end
20
+ end
21
+
22
+ def graphql_type_name_for(aa_res)
23
+ aa_res.graphql_config.graphql_type_name.presence ||
24
+ aa_res.resource_class.name.delete_prefix("::").gsub("::", "__")
25
+ end
26
+
27
+ def attributes_for(aa_res)
28
+ aa_res.attributes_for_graphql
29
+ end
30
+
31
+ def configure_schema_plugins(schema)
32
+ if (c = @namespace.graphql_max_complexity)
33
+ schema.max_complexity(c)
34
+ end
35
+ if (d = @namespace.graphql_max_depth)
36
+ schema.max_depth(d)
37
+ end
38
+ if (ps = @namespace.graphql_default_page_size)
39
+ schema.default_page_size(ps)
40
+ end
41
+ if (mps = @namespace.graphql_default_max_page_size)
42
+ schema.default_max_page_size(mps)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end