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,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAdmin
4
+ module GraphQL
5
+ class SchemaBuilder
6
+ module Build
7
+ def build
8
+ unless defined?(ActiveRecord::Base)
9
+ raise ActiveAdmin::DependencyError, "ActiveAdmin::GraphQL requires ActiveRecord."
10
+ end
11
+
12
+ @aa_by_model = {}
13
+ @object_types = {}
14
+ @enum_types = {}
15
+ @create_input_types = {}
16
+ @update_input_types = {}
17
+ @list_filter_input_types = {}
18
+ @find_input_types = {}
19
+ @aa_by_graphql_type_name = {}
20
+
21
+ active_resources.each do |aa_res|
22
+ model = aa_res.resource_class
23
+ @aa_by_model[model] = aa_res
24
+ @object_types[model] = build_object_type(aa_res)
25
+ @aa_by_graphql_type_name[graphql_type_name_for(aa_res)] = aa_res
26
+ end
27
+
28
+ wire_belongs_to_associations
29
+
30
+ active_resources.each do |aa_res|
31
+ model = aa_res.resource_class
32
+ @create_input_types[model] = build_create_input_type(aa_res)
33
+ @update_input_types[model] = build_update_input_type(aa_res)
34
+ @list_filter_input_types[model] = build_list_filter_input_type(aa_res)
35
+ @find_input_types[model] = build_find_input_type(aa_res)
36
+ end
37
+
38
+ union_members = @object_types.values.uniq
39
+ registered_resource_union =
40
+ if union_members.any?
41
+ u = Class.new(::GraphQL::Schema::Union) do
42
+ graphql_name "ActiveAdminRegisteredResource"
43
+ description "Any resource object type registered for GraphQL in this namespace."
44
+ possible_types(*union_members)
45
+ end
46
+ attach_registered_resource_union_visibility!(u)
47
+ u
48
+ end
49
+
50
+ query_type = build_query_type(registered_resource_union: registered_resource_union)
51
+ mutation_type = build_mutation_type
52
+
53
+ model_to_type = @object_types
54
+
55
+ schema = Class.new(::GraphQL::Schema)
56
+ schema.query(query_type)
57
+ schema.mutation(mutation_type) if mutation_type
58
+ dataloader_plugin = @namespace.graphql_dataloader || ::GraphQL::Dataloader
59
+ schema.use(dataloader_plugin)
60
+ apply_graphql_visibility!(schema)
61
+ if registered_resource_union
62
+ schema.define_singleton_method(:resolve_type) do |abstract_type, obj, ctx|
63
+ if abstract_type == registered_resource_union
64
+ typ = model_to_type[obj.class]
65
+ unless typ
66
+ raise ::GraphQL::ExecutionError,
67
+ "ActiveAdminRegisteredResource could not resolve type for #{obj.class.name}"
68
+ end
69
+
70
+ typ
71
+ else
72
+ super(abstract_type, obj, ctx)
73
+ end
74
+ end
75
+ end
76
+ configure_schema_plugins(schema)
77
+ hook = @namespace.graphql_configure_schema
78
+ hook.call(schema) if hook.respond_to?(:call)
79
+ schema
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAdmin
4
+ module GraphQL
5
+ class SchemaBuilder
6
+ module GraphParams
7
+ def graph_params_from_field_kwargs(aa_res, scope: nil, q: nil, order: nil, **kw)
8
+ h = {}
9
+ h[:scope] = scope if scope.present?
10
+ h[:q] = q if !q.nil?
11
+ h[:order] = order if order.present?
12
+ merge_belongs_to_kw!(aa_res, kw, h)
13
+ end
14
+
15
+ def graph_params_for_mutation(aa_res, kw)
16
+ merge_belongs_to_kw!(aa_res, kw, {})
17
+ end
18
+
19
+ def graph_params_from_input(aa_res, input)
20
+ h = {}
21
+ return h if input.nil?
22
+
23
+ blob = input.to_h.stringify_keys
24
+ if (btc = aa_res.belongs_to_config)
25
+ k = btc.to_param.to_s
26
+ h[k] = blob[k] if blob.key?(k) && blob[k].present?
27
+ end
28
+ h
29
+ end
30
+
31
+ def assignable_slice_from_input(aa_res, input)
32
+ blob = input.to_h.stringify_keys
33
+ names = aa_res.graphql_assignable_attribute_names.map(&:to_s)
34
+ if (btc = aa_res.belongs_to_config)
35
+ names -= [btc.to_param.to_s]
36
+ end
37
+ blob.slice(*names)
38
+ end
39
+
40
+ def list_graph_params(aa_res, filter:, scope:, q:, order:, **kw)
41
+ h = graph_params_from_field_kwargs(aa_res, scope: scope, q: q, order: order, **kw)
42
+ return h unless filter
43
+
44
+ fh = filter.to_h.stringify_keys
45
+ h["scope"] = fh["scope"] if fh.key?("scope")
46
+ h["order"] = fh["order"] if fh.key?("order")
47
+ h["q"] = fh["q"] if fh.key?("q")
48
+ if (btc = aa_res.belongs_to_config)
49
+ pk = btc.to_param.to_s
50
+ h[pk] = fh[pk] if fh.key?(pk)
51
+ end
52
+ h
53
+ end
54
+
55
+ def graph_params_from_find_blob(aa_res, blob)
56
+ h = {}
57
+ if (btc = aa_res.belongs_to_config)
58
+ k = btc.to_param.to_s
59
+ h[k] = blob[k] if blob.key?(k)
60
+ end
61
+ h
62
+ end
63
+
64
+ def merge_belongs_to_kw!(aa_res, kw, h)
65
+ if (btc = aa_res.belongs_to_config)
66
+ key = btc.to_param
67
+ val = kw[key] || kw[key.to_s] || kw[key.to_sym]
68
+ h[key] = val if val.present?
69
+ end
70
+ h
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAdmin
4
+ module GraphQL
5
+ class SchemaBuilder
6
+ module MutationActionTypes
7
+ private
8
+
9
+ def defined_actions_for(aa_res)
10
+ aa_res.controller.instance_methods.map(&:to_sym) & ActiveAdmin::ResourceController::ACTIVE_ADMIN_ACTIONS
11
+ end
12
+
13
+ def run_action_return_type(aa_res, kind)
14
+ cfg = aa_res.graphql_config
15
+ specific = case kind
16
+ when :batch then cfg.batch_run_action.payload_type
17
+ when :member then cfg.member_run_action.payload_type
18
+ when :collection then cfg.collection_run_action.payload_type
19
+ else
20
+ raise ArgumentError, "unknown run-action kind #{kind.inspect}"
21
+ end
22
+ ty = specific || cfg.run_action_payload_type || RunActionPayload
23
+ ensure_run_action_graphql_object!(aa_res, ty)
24
+ ty
25
+ end
26
+
27
+ def member_action_return_type(aa_res, action_name)
28
+ per = aa_res.graphql_config.member_action_mutations[action_name.to_s]
29
+ ty = per&.payload_type || aa_res.graphql_config.member_run_action.payload_type ||
30
+ aa_res.graphql_config.run_action_payload_type || RunActionPayload
31
+ ensure_run_action_graphql_object!(aa_res, ty)
32
+ ty
33
+ end
34
+
35
+ def collection_action_return_type(aa_res, action_name)
36
+ per = aa_res.graphql_config.collection_action_mutations[action_name.to_s]
37
+ ty = per&.payload_type || aa_res.graphql_config.collection_run_action.payload_type ||
38
+ aa_res.graphql_config.run_action_payload_type || RunActionPayload
39
+ ensure_run_action_graphql_object!(aa_res, ty)
40
+ ty
41
+ end
42
+
43
+ def ensure_run_action_graphql_object!(aa_res, ty)
44
+ unless ty.is_a?(Class) && ty < ::GraphQL::Schema::Object
45
+ raise ActiveAdmin::DependencyError,
46
+ "#{aa_res.resource_name} graphql run_action payload type must be a GraphQL::Schema::Object subclass, got #{ty.inspect}"
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAdmin
4
+ module GraphQL
5
+ class SchemaBuilder
6
+ module MutationBatch
7
+ private
8
+
9
+ def mutation_batch_field(sb, ns, aa_res)
10
+ plural = aa_res.resource_name.route_key.tr("-", "_")
11
+ fname = "#{plural}_batch_action"
12
+ allowed = aa_res.batch_actions.map { |a| a.sym.to_s }.freeze
13
+ batch_payload_t = run_action_return_type(aa_res, :batch)
14
+
15
+ proc do
16
+ field fname.to_sym, batch_payload_t, null: false, camelize: false,
17
+ visibility: {kind: :mutation_batch_action, graphql_type_name: sb.send(:graphql_type_name_for, aa_res), resource: aa_res, field_name: fname},
18
+ description: "Run a batch action registered on #{aa_res.resource_name} (+batch_action+ DSL), " \
19
+ "same as the index batch UI (+collection_selection+ / +batch_action_inputs+)." do
20
+ argument :batch_action, ::GraphQL::Types::String, required: true, camelize: false
21
+ argument :ids, [::GraphQL::Types::ID], required: true, camelize: false
22
+ argument :inputs, [KeyValuePairInput], required: false, camelize: false,
23
+ description: "Batch action form fields as key/value pairs (passed as +batch_action_inputs+)."
24
+ if (btc = aa_res.belongs_to_config)
25
+ argument btc.to_param.to_sym, ::GraphQL::Types::ID, required: btc.required?, camelize: false
26
+ end
27
+ end
28
+
29
+ define_method(fname.to_sym) do |batch_action:, ids:, inputs: nil, **kw|
30
+ auth = context[:auth]
31
+ mdl = aa_res.resource_class
32
+ unless auth.authorized?(aa_res, ActiveAdmin::Authorization::READ, mdl)
33
+ raise ::GraphQL::ExecutionError, "not authorized to read #{mdl.name}"
34
+ end
35
+
36
+ ba = batch_action.to_s
37
+ unless allowed.include?(ba)
38
+ raise ::GraphQL::ExecutionError, "Unknown batch_action #{ba.inspect} for #{plural}"
39
+ end
40
+
41
+ proxy = ResourceQueryProxy.new(
42
+ aa_resource: aa_res,
43
+ user: context[:auth].user,
44
+ namespace: ns,
45
+ graph_params: sb.graph_params_for_mutation(aa_res, kw)
46
+ )
47
+ sb.graphql_resolve_batch_action(
48
+ aa_res,
49
+ proxy: proxy,
50
+ context: context,
51
+ batch_action: ba,
52
+ ids: ids,
53
+ inputs: inputs
54
+ )
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAdmin
4
+ module GraphQL
5
+ class SchemaBuilder
6
+ module MutationCollection
7
+ private
8
+
9
+ def mutation_collection_fields(sb, ns, aa_res)
10
+ [mutation_collection_aggregate_field(sb, ns, aa_res)] + aa_res.collection_actions.map do |ca|
11
+ mutation_single_collection_action_field(sb, ns, aa_res, ca)
12
+ end
13
+ end
14
+
15
+ def mutation_collection_aggregate_field(sb, ns, aa_res)
16
+ plural = aa_res.resource_name.route_key.tr("-", "_")
17
+ fname = "#{plural}_collection_action"
18
+ allowed = aa_res.collection_actions.map { |a| a.name.to_s }.freeze
19
+ collection_payload_t = run_action_return_type(aa_res, :collection)
20
+
21
+ proc do
22
+ field fname.to_sym, collection_payload_t, null: false, camelize: false,
23
+ visibility: {kind: :mutation_collection_action, graphql_type_name: sb.send(:graphql_type_name_for, aa_res), resource: aa_res, field_name: fname, aggregate: true},
24
+ description: "Invoke a +collection_action+ on #{aa_res.resource_name} by name (same scoping as REST +index+). Prefer per-action fields +#{plural}_collection_<action>+ for distinct inputs or return types." do
25
+ argument :action, ::GraphQL::Types::String, required: true, camelize: false
26
+ argument :params, [KeyValuePairInput], required: false, camelize: false,
27
+ description: "Extra request params as flat key/value pairs."
28
+ if (btc = aa_res.belongs_to_config)
29
+ argument btc.to_param.to_sym, ::GraphQL::Types::ID, required: btc.required?, camelize: false
30
+ end
31
+ end
32
+
33
+ define_method(fname.to_sym) do |action:, params: nil, **kw|
34
+ auth = context[:auth]
35
+ mdl = aa_res.resource_class
36
+ unless auth.authorized?(aa_res, ActiveAdmin::Authorization::READ, mdl)
37
+ raise ::GraphQL::ExecutionError, "not authorized to read #{mdl.name}"
38
+ end
39
+
40
+ act = action.to_s
41
+ unless allowed.include?(act)
42
+ raise ::GraphQL::ExecutionError, "Unknown collection action #{act.inspect} for #{plural}"
43
+ end
44
+
45
+ proxy = ResourceQueryProxy.new(
46
+ aa_resource: aa_res,
47
+ user: context[:auth].user,
48
+ namespace: ns,
49
+ graph_params: sb.graph_params_for_mutation(aa_res, kw)
50
+ )
51
+ sb.graphql_resolve_collection_action(
52
+ aa_res,
53
+ proxy: proxy,
54
+ context: context,
55
+ action: act,
56
+ params: params,
57
+ **kw
58
+ )
59
+ end
60
+ end
61
+ end
62
+
63
+ def mutation_single_collection_action_field(sb, ns, aa_res, collection_action_def)
64
+ plural = aa_res.resource_name.route_key.tr("-", "_")
65
+ action_name = collection_action_def.name.to_s
66
+ field_name = "#{plural}_collection_#{action_name.tr("-", "_")}"
67
+ fname = field_name.to_sym
68
+ per_cfg = aa_res.graphql_config.collection_action_mutations[action_name]
69
+ collection_payload_t = collection_action_return_type(aa_res, action_name)
70
+
71
+ proc do
72
+ field fname, collection_payload_t, null: false, camelize: false,
73
+ visibility: {
74
+ kind: :mutation_collection_action,
75
+ graphql_type_name: sb.send(:graphql_type_name_for, aa_res),
76
+ resource: aa_res,
77
+ field_name: field_name,
78
+ collection_action: action_name
79
+ },
80
+ description: "Invoke +collection_action+ #{action_name} on #{aa_res.resource_name} (same scoping as REST +index+)." do
81
+ argument :params, [KeyValuePairInput], required: false, camelize: false,
82
+ description: "Extra request params as flat key/value pairs."
83
+ if (btc = aa_res.belongs_to_config)
84
+ argument btc.to_param.to_sym, ::GraphQL::Types::ID, required: btc.required?, camelize: false
85
+ end
86
+ if per_cfg&.arguments_proc
87
+ instance_exec(&per_cfg.arguments_proc)
88
+ end
89
+ end
90
+
91
+ define_method(fname) do |params: nil, **kw|
92
+ auth = context[:auth]
93
+ mdl = aa_res.resource_class
94
+ unless auth.authorized?(aa_res, ActiveAdmin::Authorization::READ, mdl)
95
+ raise ::GraphQL::ExecutionError, "not authorized to read #{mdl.name}"
96
+ end
97
+
98
+ proxy = ResourceQueryProxy.new(
99
+ aa_resource: aa_res,
100
+ user: context[:auth].user,
101
+ namespace: ns,
102
+ graph_params: sb.graph_params_for_mutation(aa_res, kw)
103
+ )
104
+ sb.graphql_resolve_collection_action(
105
+ aa_res,
106
+ proxy: proxy,
107
+ context: context,
108
+ action: action_name,
109
+ params: params,
110
+ **kw
111
+ )
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAdmin
4
+ module GraphQL
5
+ class SchemaBuilder
6
+ module MutationCreate
7
+ private
8
+
9
+ def mutation_create_field(sb, ns, aa_res, model, type_c)
10
+ fname = "create_#{aa_res.resource_name.route_key.singularize.tr("-", "_")}"
11
+ create_input = @create_input_types[model]
12
+
13
+ proc do
14
+ field fname.to_sym, type_c, null: true, camelize: false,
15
+ visibility: {kind: :mutation_create, graphql_type_name: sb.send(:graphql_type_name_for, aa_res), resource: aa_res},
16
+ description: "Create #{model.name}" do
17
+ argument :input, create_input, required: true, camelize: false
18
+ end
19
+
20
+ define_method(fname.to_sym) do |input:, **|
21
+ auth = context[:auth]
22
+ unless auth.authorized?(aa_res, ActiveAdmin::Authorization::CREATE, model)
23
+ raise ::GraphQL::ExecutionError, "not authorized to create #{model.name}"
24
+ end
25
+
26
+ proxy = ResourceQueryProxy.new(
27
+ aa_resource: aa_res,
28
+ user: auth.user,
29
+ namespace: ns,
30
+ graph_params: sb.graph_params_from_input(aa_res, input)
31
+ )
32
+ attrs = sb.assignable_slice_from_input(aa_res, input)
33
+ record = if (hook = aa_res.graphql_config.resolve_create_proc)
34
+ hook.call(
35
+ proxy: proxy,
36
+ input: input,
37
+ attributes: attrs,
38
+ context: context,
39
+ auth: auth,
40
+ aa_resource: aa_res
41
+ )
42
+ else
43
+ r = proxy.build_new(attrs)
44
+ unless auth.authorized?(aa_res, ActiveAdmin::Authorization::CREATE, r)
45
+ raise ::GraphQL::ExecutionError, "not authorized to create this record"
46
+ end
47
+
48
+ unless r.save
49
+ raise ::GraphQL::ExecutionError, r.errors.full_messages.to_sentence
50
+ end
51
+ r
52
+ end
53
+
54
+ unless auth.authorized?(aa_res, ActiveAdmin::Authorization::CREATE, record)
55
+ raise ::GraphQL::ExecutionError, "not authorized to create this record"
56
+ end
57
+
58
+ record
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAdmin
4
+ module GraphQL
5
+ class SchemaBuilder
6
+ module MutationMember
7
+ private
8
+
9
+ def mutation_member_fields(sb, ns, aa_res)
10
+ [mutation_member_aggregate_field(sb, ns, aa_res)] + aa_res.member_actions.map do |ma|
11
+ mutation_single_member_action_field(sb, ns, aa_res, ma)
12
+ end
13
+ end
14
+
15
+ def mutation_member_aggregate_field(sb, ns, aa_res)
16
+ plural = aa_res.resource_name.route_key.tr("-", "_")
17
+ fname = "#{plural}_member_action"
18
+ allowed = aa_res.member_actions.map { |a| a.name.to_s }.freeze
19
+ member_payload_t = run_action_return_type(aa_res, :member)
20
+
21
+ proc do
22
+ field fname.to_sym, member_payload_t, null: false, camelize: false,
23
+ visibility: {kind: :mutation_member_action, graphql_type_name: sb.send(:graphql_type_name_for, aa_res), resource: aa_res, field_name: fname, aggregate: true},
24
+ description: "Invoke a +member_action+ on #{aa_res.resource_name} by name (same scoping as REST +show+). Prefer per-action fields +#{plural}_member_<action>+ for distinct inputs or return types." do
25
+ argument :action, ::GraphQL::Types::String, required: true, camelize: false
26
+ argument :id, ::GraphQL::Types::ID, required: true, camelize: false
27
+ argument :params, [KeyValuePairInput], required: false, camelize: false,
28
+ description: "Extra request params as flat key/value pairs."
29
+ if (btc = aa_res.belongs_to_config)
30
+ argument btc.to_param.to_sym, ::GraphQL::Types::ID, required: btc.required?, camelize: false
31
+ end
32
+ end
33
+
34
+ define_method(fname.to_sym) do |action:, id:, params: nil, **kw|
35
+ auth = context[:auth]
36
+ mdl = aa_res.resource_class
37
+ unless auth.authorized?(aa_res, ActiveAdmin::Authorization::READ, mdl)
38
+ raise ::GraphQL::ExecutionError, "not authorized to read #{mdl.name}"
39
+ end
40
+
41
+ act = action.to_s
42
+ unless allowed.include?(act)
43
+ raise ::GraphQL::ExecutionError, "Unknown member action #{act.inspect} for #{plural}"
44
+ end
45
+
46
+ proxy = ResourceQueryProxy.new(
47
+ aa_resource: aa_res,
48
+ user: context[:auth].user,
49
+ namespace: ns,
50
+ graph_params: sb.graph_params_for_mutation(aa_res, kw)
51
+ )
52
+ sb.graphql_resolve_member_action(
53
+ aa_res,
54
+ proxy: proxy,
55
+ context: context,
56
+ action: act,
57
+ id: id,
58
+ params: params,
59
+ **kw
60
+ )
61
+ end
62
+ end
63
+ end
64
+
65
+ def mutation_single_member_action_field(sb, ns, aa_res, member_action_def)
66
+ plural = aa_res.resource_name.route_key.tr("-", "_")
67
+ action_name = member_action_def.name.to_s
68
+ field_name = "#{plural}_member_#{action_name.tr("-", "_")}"
69
+ fname = field_name.to_sym
70
+ per_cfg = aa_res.graphql_config.member_action_mutations[action_name]
71
+ member_payload_t = member_action_return_type(aa_res, action_name)
72
+
73
+ proc do
74
+ field fname, member_payload_t, null: false, camelize: false,
75
+ visibility: {
76
+ kind: :mutation_member_action,
77
+ graphql_type_name: sb.send(:graphql_type_name_for, aa_res),
78
+ resource: aa_res,
79
+ field_name: field_name,
80
+ member_action: action_name
81
+ },
82
+ description: "Invoke +member_action+ #{action_name} on #{aa_res.resource_name} (same scoping as REST +show+)." do
83
+ argument :id, ::GraphQL::Types::ID, required: true, camelize: false
84
+ argument :params, [KeyValuePairInput], required: false, camelize: false,
85
+ description: "Extra request params as flat key/value pairs."
86
+ if (btc = aa_res.belongs_to_config)
87
+ argument btc.to_param.to_sym, ::GraphQL::Types::ID, required: btc.required?, camelize: false
88
+ end
89
+ if per_cfg&.arguments_proc
90
+ instance_exec(&per_cfg.arguments_proc)
91
+ end
92
+ end
93
+
94
+ define_method(fname) do |id:, params: nil, **kw|
95
+ auth = context[:auth]
96
+ mdl = aa_res.resource_class
97
+ unless auth.authorized?(aa_res, ActiveAdmin::Authorization::READ, mdl)
98
+ raise ::GraphQL::ExecutionError, "not authorized to read #{mdl.name}"
99
+ end
100
+
101
+ proxy = ResourceQueryProxy.new(
102
+ aa_resource: aa_res,
103
+ user: context[:auth].user,
104
+ namespace: ns,
105
+ graph_params: sb.graph_params_for_mutation(aa_res, kw)
106
+ )
107
+ sb.graphql_resolve_member_action(
108
+ aa_res,
109
+ proxy: proxy,
110
+ context: context,
111
+ action: action_name,
112
+ id: id,
113
+ params: params,
114
+ **kw
115
+ )
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAdmin
4
+ module GraphQL
5
+ class SchemaBuilder
6
+ module MutationTypeBuilder
7
+ private
8
+
9
+ def build_mutation_type
10
+ mutations = []
11
+ ns = @namespace
12
+ sb = self
13
+
14
+ active_resources.each do |aa_res|
15
+ model = aa_res.resource_class
16
+ type_c = @object_types[model]
17
+ next unless type_c
18
+
19
+ actions = defined_actions_for(aa_res)
20
+
21
+ if actions.include?(:create)
22
+ mutations << mutation_create_field(sb, ns, aa_res, model, type_c)
23
+ end
24
+ if actions.include?(:update)
25
+ mutations << mutation_update_field(sb, ns, aa_res, model, type_c)
26
+ end
27
+ if actions.include?(:destroy)
28
+ mutations << mutation_destroy_field(sb, ns, aa_res, model)
29
+ end
30
+
31
+ if aa_res.batch_actions_enabled? && aa_res.batch_actions.any?
32
+ mutations << mutation_batch_field(sb, ns, aa_res)
33
+ end
34
+ mutations.concat(mutation_member_fields(sb, ns, aa_res)) if aa_res.member_actions.any?
35
+ mutations.concat(mutation_collection_fields(sb, ns, aa_res)) if aa_res.collection_actions.any?
36
+ end
37
+
38
+ return nil if mutations.empty?
39
+
40
+ Class.new(::GraphQL::Schema::Object) do
41
+ field_class ::ActiveAdmin::GraphQL::SchemaField
42
+
43
+ graphql_name "Mutation"
44
+ description "ActiveAdmin GraphQL mutations (#{ns.name})"
45
+
46
+ mutations.each { |m| class_eval(&m) }
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end