activeadmin-graphql 0.3.3 → 0.4.1
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 +4 -4
- data/CHANGELOG.md +31 -3
- data/README.md +2 -2
- data/docs/changelogs/20260818111600_rfc-0002-problems.md +1 -1
- data/docs/changelogs/20260818114000_rfc-as-built-coverage.md +1 -1
- data/docs/changelogs/20260818114700_rfc-design-not-version.md +1 -1
- data/docs/changelogs/20260907141000_advisory-lockfile-refresh.md +1 -1
- data/docs/changelogs/20260921103000_mutation-attribute-allowlists.md +1 -1
- data/docs/changelogs/20260921111000_release-0-3-0.md +1 -1
- data/docs/changelogs/20260921112800_drop-standard-rubocop-direct.md +1 -1
- data/docs/changelogs/20260921120000_graphql-floor-and-activeadmin-4.md +1 -1
- data/docs/changelogs/20261010091415_graphql-resource-enabled-default.md +1 -1
- data/docs/changelogs/20261010103800_release-0-3-1.md +1 -1
- data/docs/changelogs/20261010110600_graphql-identity-dsl.md +4 -0
- data/docs/changelogs/20261010111800_release-0-3-2.md +1 -1
- data/docs/changelogs/20261012050051_graphql-surface-toggles.md +1 -1
- data/docs/changelogs/20261012064500_graphql-capabilities.md +1 -1
- data/docs/changelogs/20261012113500_release-0-3-3.md +1 -1
- data/docs/changelogs/20261012200000_host-customization-hooks.md +31 -0
- data/docs/changelogs/20261013080000_release-0-4-0.md +48 -0
- data/docs/changelogs/20261103145000_release-0-4-1.md +26 -0
- data/docs/graphql-api.md +154 -102
- data/docs/issues/20260907141000_engineering-and-dependency-audit.md +1 -1
- data/docs/issues/20260917123253_ci-drop-automatic-dependency-audit.md +1 -1
- data/docs/issues/20260921103000_mutation-attribute-allowlists.md +1 -1
- data/docs/issues/20260921104400_convention-alignment-audit.md +7 -7
- data/docs/issues/20260921114500_dependency-audit.md +13 -13
- data/docs/issues/20260921115500_file-length-metrics.md +1 -1
- data/docs/issues/20261010111159_rails-composite-primary-keys-alignment.md +13 -13
- data/docs/issues/20261010111300_partitions-and-shards-support.md +3 -3
- data/docs/issues/20261010111500_graphql-federation-support.md +3 -3
- data/docs/issues/20261012200000_host-customization-gaps.md +30 -0
- data/docs/issues/20261103145000_engineering-prose-release-audit.md +39 -0
- data/docs/plan/20260817201500_rfc-starter.md +1 -1
- data/docs/reviews/20261028_engineering-audit-release-preparation.md +24 -0
- data/docs/reviews/20261103145000_engineering-prose-release-audit.md +38 -0
- data/lib/active_admin/graphql/capability_definition_dsl.rb +9 -4
- data/lib/active_admin/graphql/collection_query_policy.rb +103 -0
- data/lib/active_admin/graphql/integration.rb +24 -0
- data/lib/active_admin/graphql/member_action_capability.rb +14 -0
- data/lib/active_admin/graphql/member_action_dry_run.rb +121 -0
- data/lib/active_admin/graphql/resource_capability_dsl.rb +4 -3
- data/lib/active_admin/graphql/resource_capability_maps.rb +20 -2
- data/lib/active_admin/graphql/resource_definition_dsl.rb +17 -6
- data/lib/active_admin/graphql/run_action_capability.rb +62 -0
- data/lib/active_admin/graphql/run_action_control_extension.rb +72 -0
- data/lib/active_admin/graphql/run_action_field.rb +103 -0
- data/lib/active_admin/graphql/run_action_mutation_config.rb +8 -0
- data/lib/active_admin/graphql/run_action_mutation_dsl.rb +18 -0
- data/lib/active_admin/graphql/run_action_preview.rb +20 -0
- data/lib/active_admin/graphql/schema_builder/mutation_batch.rb +2 -2
- data/lib/active_admin/graphql/schema_builder/mutation_collection.rb +4 -3
- data/lib/active_admin/graphql/schema_builder/mutation_create.rb +20 -20
- data/lib/active_admin/graphql/schema_builder/mutation_member.rb +8 -3
- data/lib/active_admin/graphql/schema_builder/mutation_update_destroy.rb +2 -4
- data/lib/active_admin/graphql/schema_builder/query_type_member.rb +1 -4
- data/lib/active_admin/graphql/schema_builder/query_type_registered.rb +1 -4
- data/lib/active_admin/graphql/schema_builder/resolvers.rb +46 -7
- data/lib/active_admin/graphql/schema_builder/types_object.rb +2 -1
- data/lib/active_admin/graphql/schema_builder/wire.rb +2 -1
- data/lib/active_admin/graphql/schema_field.rb +85 -10
- data/lib/active_admin/graphql/version.rb +1 -1
- data/lib/active_admin/graphql.rb +7 -0
- metadata +15 -1
|
@@ -2,12 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
module ActiveAdmin
|
|
4
4
|
module GraphQL
|
|
5
|
-
# Capability → member maps on {ResourceConfig} (RFC 0008).
|
|
5
|
+
# Capability → member maps on {ResourceConfig} (RFC 0008, RFC 0009).
|
|
6
6
|
module ResourceCapabilityMaps
|
|
7
7
|
def field_capabilities
|
|
8
8
|
@field_capabilities ||= {}
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
+
def field_capability_modes
|
|
12
|
+
@field_capability_modes ||= {}
|
|
13
|
+
end
|
|
14
|
+
|
|
11
15
|
def query_capabilities
|
|
12
16
|
@query_capabilities ||= {}
|
|
13
17
|
end
|
|
@@ -20,6 +24,10 @@ module ActiveAdmin
|
|
|
20
24
|
field_capabilities[name.to_sym]
|
|
21
25
|
end
|
|
22
26
|
|
|
27
|
+
def capability_mode_for_field(name)
|
|
28
|
+
field_capability_modes[name.to_sym]
|
|
29
|
+
end
|
|
30
|
+
|
|
23
31
|
def capability_for_query(kind)
|
|
24
32
|
query_capabilities[kind.to_sym]
|
|
25
33
|
end
|
|
@@ -28,8 +36,18 @@ module ActiveAdmin
|
|
|
28
36
|
mutation_capabilities[kind.to_sym]
|
|
29
37
|
end
|
|
30
38
|
|
|
31
|
-
def assign_field_capabilities!(capability_name, names)
|
|
39
|
+
def assign_field_capabilities!(capability_name, names, mode: nil)
|
|
32
40
|
assign_capabilities!(field_capabilities, capability_name, names, label: "field")
|
|
41
|
+
return if mode.nil?
|
|
42
|
+
|
|
43
|
+
resolved = mode.to_sym
|
|
44
|
+
unless %i[omit null].include?(resolved)
|
|
45
|
+
raise ArgumentError, "capability mode must be :omit or :null, got #{mode.inspect}"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
names.flatten.each do |name|
|
|
49
|
+
field_capability_modes[name.to_sym] = resolved
|
|
50
|
+
end
|
|
33
51
|
end
|
|
34
52
|
|
|
35
53
|
def assign_query_capabilities!(capability_name, names)
|
|
@@ -103,21 +103,32 @@ module ActiveAdmin
|
|
|
103
103
|
end
|
|
104
104
|
|
|
105
105
|
# Pair +type+ and +resolve+ for +posts_batch_action+ (graphql-ruby-style block).
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
# +capability:+ overrides the resource map / namespace default for the aggregate field (RFC 0012).
|
|
107
|
+
def batch_action_mutation(capability: nil, &block)
|
|
108
|
+
cfg = @config.batch_run_action
|
|
109
|
+
cfg.capability = capability.to_sym if capability
|
|
110
|
+
RunActionMutationDSL.new(cfg).instance_exec(&block) if block
|
|
111
|
+
cfg
|
|
108
112
|
end
|
|
109
113
|
|
|
110
114
|
# With no name: configures the aggregate +posts_member_action(action: …)+ field.
|
|
111
115
|
# With a symbol/string: configures +posts_member_<action>+ (one field per +member_action+), so each
|
|
112
116
|
# action can use its own +type+, +resolve+, +arguments+, and GraphQL inputs.
|
|
113
|
-
|
|
117
|
+
# +dry_run:+ / +capability:+ apply to named per-action fields (RFC 0010, RFC 0012).
|
|
118
|
+
def member_action_mutation(name = nil, dry_run: false, capability: nil, &block)
|
|
114
119
|
cfg = name.nil? ? @config.member_run_action : @config.member_action_mutation_for(name)
|
|
115
|
-
|
|
120
|
+
cfg.dry_run = true if dry_run
|
|
121
|
+
cfg.capability = capability.to_sym if capability
|
|
122
|
+
RunActionMutationDSL.new(cfg).instance_exec(&block) if block
|
|
123
|
+
cfg
|
|
116
124
|
end
|
|
117
125
|
|
|
118
|
-
def collection_action_mutation(name = nil, &block)
|
|
126
|
+
def collection_action_mutation(name = nil, dry_run: false, capability: nil, &block)
|
|
119
127
|
cfg = name.nil? ? @config.collection_run_action : @config.collection_action_mutation_for(name)
|
|
120
|
-
|
|
128
|
+
cfg.dry_run = true if dry_run
|
|
129
|
+
cfg.capability = capability.to_sym if capability
|
|
130
|
+
RunActionMutationDSL.new(cfg).instance_exec(&block) if block
|
|
131
|
+
cfg
|
|
121
132
|
end
|
|
122
133
|
|
|
123
134
|
# Default return type for all run-action fields unless a kind-specific +type+ is set inside
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveAdmin
|
|
4
|
+
module GraphQL
|
|
5
|
+
# Resolves the capability for batch / member / collection run-action fields (RFC 0012).
|
|
6
|
+
module RunActionCapability
|
|
7
|
+
KINDS = %i[batch member collection].freeze
|
|
8
|
+
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
# @param kind [Symbol] +:batch+, +:member+, or +:collection+
|
|
12
|
+
# @param action_name [String, Symbol, nil] per-action name; +nil+ for the aggregate field
|
|
13
|
+
def resolve(namespace, aa_res, kind, action_name = nil)
|
|
14
|
+
kind = kind.to_sym
|
|
15
|
+
unless KINDS.include?(kind)
|
|
16
|
+
raise ArgumentError, "run-action kind must be one of #{KINDS.inspect}, got #{kind.inspect}"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
if action_name
|
|
20
|
+
per_capability = per_action_config_capability(aa_res, kind, action_name)
|
|
21
|
+
return per_capability if per_capability
|
|
22
|
+
|
|
23
|
+
mapped = aa_res.graphql_config.capability_for_mutation(
|
|
24
|
+
:"#{kind}_#{action_name.to_s.tr("-", "_")}"
|
|
25
|
+
)
|
|
26
|
+
else
|
|
27
|
+
aggregate_capability = aggregate_config_capability(aa_res, kind)
|
|
28
|
+
return aggregate_capability if aggregate_capability
|
|
29
|
+
|
|
30
|
+
mapped = aa_res.graphql_config.capability_for_mutation(:"#{kind}_action")
|
|
31
|
+
end
|
|
32
|
+
return mapped if mapped
|
|
33
|
+
|
|
34
|
+
namespace.public_send(:"graphql_#{kind}_action_capability_default")&.to_sym
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def per_action_config_capability(aa_res, kind, action_name)
|
|
38
|
+
map =
|
|
39
|
+
case kind
|
|
40
|
+
when :member then aa_res.graphql_config.member_action_mutations
|
|
41
|
+
when :collection then aa_res.graphql_config.collection_action_mutations
|
|
42
|
+
when :batch then nil
|
|
43
|
+
end
|
|
44
|
+
return nil unless map
|
|
45
|
+
|
|
46
|
+
map[action_name.to_s]&.capability
|
|
47
|
+
end
|
|
48
|
+
module_function :per_action_config_capability
|
|
49
|
+
|
|
50
|
+
def aggregate_config_capability(aa_res, kind)
|
|
51
|
+
cfg =
|
|
52
|
+
case kind
|
|
53
|
+
when :member then aa_res.graphql_config.member_run_action
|
|
54
|
+
when :batch then aa_res.graphql_config.batch_run_action
|
|
55
|
+
when :collection then aa_res.graphql_config.collection_run_action
|
|
56
|
+
end
|
|
57
|
+
cfg&.capability
|
|
58
|
+
end
|
|
59
|
+
module_function :aggregate_config_capability
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveAdmin
|
|
4
|
+
module GraphQL
|
|
5
|
+
# Confirm gate and optional preview short-circuit for custom run-action fields (RFC 0013).
|
|
6
|
+
class RunActionControlExtension < ::GraphQL::Schema::FieldExtension
|
|
7
|
+
def resolve(arguments:, context:, object: nil, objects: nil)
|
|
8
|
+
if options[:confirm] && !arguments[:dry_run] && !arguments[:confirm]
|
|
9
|
+
result = RunActionPayload::Result.new(
|
|
10
|
+
ok: false, status: 422, location: nil, body: "confirm_required"
|
|
11
|
+
)
|
|
12
|
+
audit!(context, result, dry_run: false)
|
|
13
|
+
return result
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
preview_proc = options[:preview]
|
|
17
|
+
if preview_proc
|
|
18
|
+
preview_result = run_preview(preview_proc, arguments, context)
|
|
19
|
+
|
|
20
|
+
if arguments[:dry_run]
|
|
21
|
+
result = preview_result || default_dry_run_result
|
|
22
|
+
audit!(context, result, dry_run: true)
|
|
23
|
+
return result
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
if preview_result&.respond_to?(:ok) && !preview_result.ok
|
|
27
|
+
audit!(context, preview_result, dry_run: false)
|
|
28
|
+
return preview_result
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
yield(object.nil? ? objects : object, arguments, nil)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def run_preview(preview_proc, arguments, context)
|
|
38
|
+
kwargs = arguments.respond_to?(:to_h) ? arguments.to_h : arguments
|
|
39
|
+
kwargs = kwargs.merge(context: context)
|
|
40
|
+
RunActionPreview.call(preview_proc, **kwargs.transform_keys(&:to_sym))
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def default_dry_run_result
|
|
44
|
+
RunActionPayload::Result.new(
|
|
45
|
+
ok: true,
|
|
46
|
+
status: 200,
|
|
47
|
+
location: nil,
|
|
48
|
+
body: "would_run:#{field.graphql_name}"
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def audit!(context, result, dry_run:)
|
|
53
|
+
hook = context[:namespace]&.graphql_run_action_audit
|
|
54
|
+
return if hook.nil?
|
|
55
|
+
|
|
56
|
+
hook.call(
|
|
57
|
+
context,
|
|
58
|
+
{
|
|
59
|
+
action: field.graphql_name,
|
|
60
|
+
id: nil,
|
|
61
|
+
status: result.respond_to?(:status) ? result.status : nil,
|
|
62
|
+
dry_run: dry_run,
|
|
63
|
+
ok: result.respond_to?(:ok) ? result.ok : nil,
|
|
64
|
+
reason: result.respond_to?(:body) ? result.body : nil
|
|
65
|
+
}
|
|
66
|
+
)
|
|
67
|
+
rescue
|
|
68
|
+
nil
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveAdmin
|
|
4
|
+
module GraphQL
|
|
5
|
+
# DSL sugar for custom run-action mutation fields (RFC 0013).
|
|
6
|
+
#
|
|
7
|
+
# ActiveAdmin::GraphQL::RunActionField.define(schema.mutation, :create_batch,
|
|
8
|
+
# dry_run: true, confirm: true, capability: :support_act) do
|
|
9
|
+
# argument :q, GraphQL::Types::JSON, required: true, camelize: false
|
|
10
|
+
# preview { |**| ok("would_run:create_batch") }
|
|
11
|
+
# resolve { |q:, **| HostService.call(q: q) }
|
|
12
|
+
# end
|
|
13
|
+
module RunActionField
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
def define(
|
|
17
|
+
owner,
|
|
18
|
+
name,
|
|
19
|
+
type: RunActionPayload,
|
|
20
|
+
null: false,
|
|
21
|
+
camelize: false,
|
|
22
|
+
dry_run: false,
|
|
23
|
+
confirm: false,
|
|
24
|
+
capability: nil,
|
|
25
|
+
authorize: false,
|
|
26
|
+
visibility: nil,
|
|
27
|
+
**kwargs,
|
|
28
|
+
&block
|
|
29
|
+
)
|
|
30
|
+
definition = Definition.new
|
|
31
|
+
definition.instance_exec(&block) if block
|
|
32
|
+
unless definition.resolve_proc
|
|
33
|
+
raise ArgumentError, "run_action_field #{name.inspect} requires a resolve block"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
owner.field(
|
|
37
|
+
name,
|
|
38
|
+
type,
|
|
39
|
+
null: null,
|
|
40
|
+
camelize: camelize,
|
|
41
|
+
dry_run: dry_run,
|
|
42
|
+
confirm: confirm,
|
|
43
|
+
preview: definition.preview_proc,
|
|
44
|
+
capability: capability,
|
|
45
|
+
authorize: authorize,
|
|
46
|
+
visibility: visibility,
|
|
47
|
+
**kwargs,
|
|
48
|
+
&definition.field_configuration_block
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
resolve_proc = definition.resolve_proc
|
|
52
|
+
owner.define_method(name) do |**args|
|
|
53
|
+
resolve_proc.call(**args)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Block body for {RunActionField.define}.
|
|
58
|
+
class Definition
|
|
59
|
+
attr_reader :preview_proc, :resolve_proc
|
|
60
|
+
|
|
61
|
+
def initialize
|
|
62
|
+
@argument_calls = []
|
|
63
|
+
@arguments_proc = nil
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def argument(*args, **kwargs)
|
|
67
|
+
@argument_calls << [args, kwargs]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def arguments(&block)
|
|
71
|
+
@arguments_proc = block
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def preview(&block)
|
|
75
|
+
@preview_proc = block
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def resolve(&block)
|
|
79
|
+
@resolve_proc = block
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def ok(body, status: 200)
|
|
83
|
+
RunActionPreview.new.ok(body, status: status)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def refuse(body, status: 422)
|
|
87
|
+
RunActionPreview.new.refuse(body, status: status)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def field_configuration_block
|
|
91
|
+
argument_calls = @argument_calls
|
|
92
|
+
arguments_proc = @arguments_proc
|
|
93
|
+
proc do
|
|
94
|
+
instance_exec(&arguments_proc) if arguments_proc
|
|
95
|
+
argument_calls.each do |args, kwargs|
|
|
96
|
+
argument(*args, **kwargs)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -14,6 +14,14 @@ module ActiveAdmin
|
|
|
14
14
|
attr_accessor :arguments_proc
|
|
15
15
|
# Optional authorization toggle for this mutation configuration. +nil+ means namespace default.
|
|
16
16
|
attr_accessor :authorize
|
|
17
|
+
# When true, inject +dry_run+ and honor +preview+ (RFC 0010).
|
|
18
|
+
attr_accessor :dry_run
|
|
19
|
+
# @return [Proc, nil] preview block for dry-run / refuse-before-run
|
|
20
|
+
attr_accessor :preview_proc
|
|
21
|
+
# @return [Array<String>, nil] allowlisted extra GraphQL kwargs forwarded into AA params
|
|
22
|
+
attr_accessor :extra_param_keys
|
|
23
|
+
# Optional capability override for this action field (RFC 0012).
|
|
24
|
+
attr_accessor :capability
|
|
17
25
|
|
|
18
26
|
def self.ensure_graphql_object_subclass!(type)
|
|
19
27
|
unless type.is_a?(Class) && type < ::GraphQL::Schema::Object
|
|
@@ -31,6 +31,24 @@ module ActiveAdmin
|
|
|
31
31
|
def authorize(value = true)
|
|
32
32
|
@mutation_config.authorize = !!value
|
|
33
33
|
end
|
|
34
|
+
|
|
35
|
+
# Preview before run / dry-run short-circuit (RFC 0010). Block receives the member record.
|
|
36
|
+
def preview(&block)
|
|
37
|
+
@mutation_config.preview_proc = block
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Allowlisted GraphQL kwargs forwarded into the ActiveAdmin request (never +id+).
|
|
41
|
+
def extra_params(*names)
|
|
42
|
+
@mutation_config.extra_param_keys = names.flatten.map(&:to_s)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def ok(body, status: 200)
|
|
46
|
+
RunActionPreview.new.ok(body, status: status)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def refuse(body, status: 422)
|
|
50
|
+
RunActionPreview.new.refuse(body, status: status)
|
|
51
|
+
end
|
|
34
52
|
end
|
|
35
53
|
end
|
|
36
54
|
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveAdmin
|
|
4
|
+
module GraphQL
|
|
5
|
+
# Shared +ok+ / +refuse+ helpers for run-action preview blocks (RFC 0010, RFC 0013).
|
|
6
|
+
class RunActionPreview
|
|
7
|
+
def ok(body, status: 200)
|
|
8
|
+
RunActionPayload::Result.new(ok: true, status: status, location: nil, body: body.to_s)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def refuse(body, status: 422)
|
|
12
|
+
RunActionPayload::Result.new(ok: false, status: status, location: nil, body: body.to_s)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.call(preview_proc, **kwargs)
|
|
16
|
+
new.instance_exec(**kwargs, &preview_proc)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -15,7 +15,7 @@ module ActiveAdmin
|
|
|
15
15
|
proc do
|
|
16
16
|
field fname.to_sym, batch_payload_t, null: false, camelize: false,
|
|
17
17
|
visibility: {kind: :mutation_batch_action, graphql_type_name: sb.send(:graphql_type_name_for, aa_res), resource: aa_res, field_name: fname},
|
|
18
|
-
capability:
|
|
18
|
+
capability: RunActionCapability.resolve(ns, aa_res, :batch),
|
|
19
19
|
description: "Run a batch action registered on #{aa_res.resource_name} (+batch_action+ DSL), " \
|
|
20
20
|
"same as the index batch UI (+collection_selection+ / +batch_action_inputs+)." do
|
|
21
21
|
argument :batch_action, ::GraphQL::Types::String, required: true, camelize: false
|
|
@@ -42,7 +42,7 @@ module ActiveAdmin
|
|
|
42
42
|
end
|
|
43
43
|
unless Capability.granted?(
|
|
44
44
|
context,
|
|
45
|
-
|
|
45
|
+
RunActionCapability.resolve(ns, aa_res, :batch, ba)
|
|
46
46
|
)
|
|
47
47
|
raise ::GraphQL::ExecutionError, "not authorized for batch action #{ba}"
|
|
48
48
|
end
|
|
@@ -21,7 +21,7 @@ module ActiveAdmin
|
|
|
21
21
|
proc do
|
|
22
22
|
field fname.to_sym, collection_payload_t, null: false, camelize: false,
|
|
23
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
|
-
capability:
|
|
24
|
+
capability: RunActionCapability.resolve(ns, aa_res, :collection),
|
|
25
25
|
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
|
|
26
26
|
argument :action, ::GraphQL::Types::String, required: true, camelize: false
|
|
27
27
|
argument :params, [KeyValuePairInput], required: false, camelize: false,
|
|
@@ -46,7 +46,7 @@ module ActiveAdmin
|
|
|
46
46
|
end
|
|
47
47
|
unless Capability.granted?(
|
|
48
48
|
context,
|
|
49
|
-
|
|
49
|
+
RunActionCapability.resolve(ns, aa_res, :collection, act)
|
|
50
50
|
)
|
|
51
51
|
raise ::GraphQL::ExecutionError, "not authorized for collection action #{act}"
|
|
52
52
|
end
|
|
@@ -76,6 +76,7 @@ module ActiveAdmin
|
|
|
76
76
|
fname = field_name.to_sym
|
|
77
77
|
per_cfg = aa_res.graphql_config.collection_action_mutations[action_name]
|
|
78
78
|
collection_payload_t = collection_action_return_type(aa_res, action_name)
|
|
79
|
+
capability = RunActionCapability.resolve(ns, aa_res, :collection, action_name)
|
|
79
80
|
|
|
80
81
|
proc do
|
|
81
82
|
field fname, collection_payload_t, null: false, camelize: false,
|
|
@@ -86,7 +87,7 @@ module ActiveAdmin
|
|
|
86
87
|
field_name: field_name,
|
|
87
88
|
collection_action: action_name
|
|
88
89
|
},
|
|
89
|
-
capability:
|
|
90
|
+
capability: capability,
|
|
90
91
|
description: "Invoke +collection_action+ #{action_name} on #{aa_res.resource_name} (same scoping as REST +index+)." do
|
|
91
92
|
argument :params, [KeyValuePairInput], required: false, camelize: false,
|
|
92
93
|
description: "Extra request params as flat key/value pairs."
|
|
@@ -35,32 +35,32 @@ module ActiveAdmin
|
|
|
35
35
|
input,
|
|
36
36
|
attribute_names: aa_res.graphql_create_attribute_names
|
|
37
37
|
)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
38
|
+
if (hook = aa_res.graphql_config.resolve_create_proc)
|
|
39
|
+
ActiveRecord::Base.transaction(requires_new: true) do
|
|
40
|
+
record = hook.call(
|
|
41
|
+
proxy: proxy,
|
|
42
|
+
input: input,
|
|
43
|
+
attributes: attrs,
|
|
44
|
+
context: context,
|
|
45
|
+
auth: auth,
|
|
46
|
+
aa_resource: aa_res
|
|
47
|
+
)
|
|
48
|
+
unless auth.authorized?(aa_res, ActiveAdmin::Authorization::CREATE, record)
|
|
49
|
+
raise ::GraphQL::ExecutionError, "not authorized to create this record"
|
|
50
|
+
end
|
|
51
|
+
record
|
|
52
|
+
end
|
|
47
53
|
else
|
|
48
|
-
|
|
49
|
-
unless auth.authorized?(aa_res, ActiveAdmin::Authorization::CREATE,
|
|
54
|
+
record = proxy.build_new(attrs)
|
|
55
|
+
unless auth.authorized?(aa_res, ActiveAdmin::Authorization::CREATE, record)
|
|
50
56
|
raise ::GraphQL::ExecutionError, "not authorized to create this record"
|
|
51
57
|
end
|
|
52
58
|
|
|
53
|
-
unless
|
|
54
|
-
raise MutationExecutionError.validation(
|
|
59
|
+
unless record.save
|
|
60
|
+
raise MutationExecutionError.validation(record)
|
|
55
61
|
end
|
|
56
|
-
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
unless auth.authorized?(aa_res, ActiveAdmin::Authorization::CREATE, record)
|
|
60
|
-
raise ::GraphQL::ExecutionError, "not authorized to create this record"
|
|
62
|
+
record
|
|
61
63
|
end
|
|
62
|
-
|
|
63
|
-
record
|
|
64
64
|
end
|
|
65
65
|
end
|
|
66
66
|
end
|
|
@@ -21,7 +21,7 @@ module ActiveAdmin
|
|
|
21
21
|
proc do
|
|
22
22
|
field fname.to_sym, member_payload_t, null: false, camelize: false,
|
|
23
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
|
-
capability:
|
|
24
|
+
capability: MemberActionCapability.resolve(ns, aa_res),
|
|
25
25
|
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
|
|
26
26
|
argument :action, ::GraphQL::Types::String, required: true, camelize: false
|
|
27
27
|
argument :id, ::GraphQL::Types::ID, required: true, camelize: false
|
|
@@ -47,7 +47,7 @@ module ActiveAdmin
|
|
|
47
47
|
end
|
|
48
48
|
unless Capability.granted?(
|
|
49
49
|
context,
|
|
50
|
-
|
|
50
|
+
MemberActionCapability.resolve(ns, aa_res, act)
|
|
51
51
|
)
|
|
52
52
|
raise ::GraphQL::ExecutionError, "not authorized for member action #{act}"
|
|
53
53
|
end
|
|
@@ -78,6 +78,7 @@ module ActiveAdmin
|
|
|
78
78
|
fname = field_name.to_sym
|
|
79
79
|
per_cfg = aa_res.graphql_config.member_action_mutations[action_name]
|
|
80
80
|
member_payload_t = member_action_return_type(aa_res, action_name)
|
|
81
|
+
capability = MemberActionCapability.resolve(ns, aa_res, action_name)
|
|
81
82
|
|
|
82
83
|
proc do
|
|
83
84
|
field fname, member_payload_t, null: false, camelize: false,
|
|
@@ -88,11 +89,15 @@ module ActiveAdmin
|
|
|
88
89
|
field_name: field_name,
|
|
89
90
|
member_action: action_name
|
|
90
91
|
},
|
|
91
|
-
capability:
|
|
92
|
+
capability: capability,
|
|
92
93
|
description: "Invoke +member_action+ #{action_name} on #{aa_res.resource_name} (same scoping as REST +show+)." do
|
|
93
94
|
argument :id, ::GraphQL::Types::ID, required: true, camelize: false
|
|
94
95
|
argument :params, [KeyValuePairInput], required: false, camelize: false,
|
|
95
96
|
description: "Extra request params as flat key/value pairs."
|
|
97
|
+
if per_cfg&.dry_run
|
|
98
|
+
argument :dry_run, ::GraphQL::Types::Boolean, required: false, default_value: false, camelize: false,
|
|
99
|
+
description: "When true, preview without performing the member action."
|
|
100
|
+
end
|
|
96
101
|
if (btc = aa_res.belongs_to_config)
|
|
97
102
|
argument btc.to_param.to_sym, ::GraphQL::Types::ID, required: btc.required?, camelize: false
|
|
98
103
|
end
|
|
@@ -38,9 +38,8 @@ module ActiveAdmin
|
|
|
38
38
|
)
|
|
39
39
|
record = proxy.find_member(rid)
|
|
40
40
|
raise ::GraphQL::ExecutionError, "not found" unless record
|
|
41
|
-
|
|
42
41
|
unless auth.authorized?(aa_res, ActiveAdmin::Authorization::UPDATE, record)
|
|
43
|
-
raise ::GraphQL::ExecutionError, "not
|
|
42
|
+
raise ::GraphQL::ExecutionError, "not found"
|
|
44
43
|
end
|
|
45
44
|
|
|
46
45
|
attrs = sb.assignable_slice_from_input(
|
|
@@ -105,9 +104,8 @@ module ActiveAdmin
|
|
|
105
104
|
)
|
|
106
105
|
record = proxy.find_member(rid)
|
|
107
106
|
raise ::GraphQL::ExecutionError, "not found" unless record
|
|
108
|
-
|
|
109
107
|
unless auth.authorized?(aa_res, ActiveAdmin::Authorization::DESTROY, record)
|
|
110
|
-
raise ::GraphQL::ExecutionError, "not
|
|
108
|
+
raise ::GraphQL::ExecutionError, "not found"
|
|
111
109
|
end
|
|
112
110
|
|
|
113
111
|
if (hook = aa_res.graphql_config.resolve_destroy_proc)
|
|
@@ -79,10 +79,7 @@ module ActiveAdmin
|
|
|
79
79
|
id_argument: id
|
|
80
80
|
)
|
|
81
81
|
return nil unless record
|
|
82
|
-
|
|
83
|
-
unless auth.authorized?(aa_res, ActiveAdmin::Authorization::READ, record)
|
|
84
|
-
raise ::GraphQL::ExecutionError, "not authorized to read this record"
|
|
85
|
-
end
|
|
82
|
+
return nil unless auth.authorized?(aa_res, ActiveAdmin::Authorization::READ, record)
|
|
86
83
|
|
|
87
84
|
record
|
|
88
85
|
end
|
|
@@ -42,10 +42,7 @@ module ActiveAdmin
|
|
|
42
42
|
graph_params: graph_params
|
|
43
43
|
)
|
|
44
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
|
|
45
|
+
return nil unless auth.authorized?(aa_res, ActiveAdmin::Authorization::READ, record)
|
|
49
46
|
|
|
50
47
|
record
|
|
51
48
|
end
|