admin_suite 0.2.8 → 0.2.9
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 +6 -1
- data/CONTRIBUTING.md +2 -2
- data/README.md +10 -24
- data/app/controllers/admin_suite/resources_controller.rb +14 -4
- data/app/views/admin_suite/resources/index.html.erb +3 -1
- data/app/views/admin_suite/resources/show.html.erb +2 -2
- data/lib/admin/base/filter_builder.rb +2 -1
- data/lib/admin/base/resource.rb +14 -2
- data/lib/admin_suite/version.rb +1 -1
- data/test/controllers/resources_controller_test.rb +80 -0
- data/test/integration/read_only_resource_test.rb +143 -0
- data/test/lib/resource_observability_extensions_test.rb +53 -0
- metadata +5 -14
- data/docs/README.md +0 -26
- data/docs/actions.md +0 -98
- data/docs/configuration.md +0 -284
- data/docs/development.md +0 -64
- data/docs/docs_viewer.md +0 -79
- data/docs/fields.md +0 -188
- data/docs/installation.md +0 -80
- data/docs/portals.md +0 -140
- data/docs/releasing.md +0 -67
- data/docs/resources.md +0 -237
- data/docs/theming.md +0 -63
- data/docs/troubleshooting.md +0 -50
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f50f5a24977b7bc8284ee051fb3d9d1746b49a1ab8d9ade00d917bf0a8ff412b
|
|
4
|
+
data.tar.gz: 6e32362f8fe763980b83fa34910b21020d7bdca7918afb5d63084aae7e80d72e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b980579e952dc4ec9cd1cd56dab61219f9320e810df0697a97ad42031e08fa59793c893adfdcd00e7d0ab7449468ae7d02a886f2dd6a3406c65bf5c750a28c3f
|
|
7
|
+
data.tar.gz: 603fdf2522093c9aba2427286e6573515a75d3016367dde07d3a74661bc7e15ffe95b1b388258c3398cc4d6583b85ef7c9ace18cc55e118b116344835802dfdc
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.9] - 2026-07-14
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Read-only resource support, server-side filter defaults, and filtered-scope statistics.
|
|
15
|
+
|
|
10
16
|
## [0.2.8] - 2026-03-23
|
|
11
17
|
|
|
12
18
|
### Added
|
|
@@ -38,4 +44,3 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
38
44
|
- Initial extraction of the AdminSuite Rails engine.
|
|
39
45
|
- Resource/portal DSL, docs viewer, and theming primitives.
|
|
40
46
|
- Isolated gem test suite with a dummy Rails app.
|
|
41
|
-
|
data/CONTRIBUTING.md
CHANGED
|
@@ -12,7 +12,7 @@ Thank you for your interest in contributing to AdminSuite!
|
|
|
12
12
|
|
|
13
13
|
## Development
|
|
14
14
|
|
|
15
|
-
See
|
|
15
|
+
See `../_vault/products/admin_suite/docs/development.md` for detailed information on:
|
|
16
16
|
- Setting up your development environment
|
|
17
17
|
- Running tests
|
|
18
18
|
- Code style guidelines
|
|
@@ -36,7 +36,7 @@ The CI workflow runs automatically on every pull request.
|
|
|
36
36
|
|
|
37
37
|
## Releasing
|
|
38
38
|
|
|
39
|
-
See
|
|
39
|
+
See `../_vault/products/admin_suite/docs/releasing.md` for information on how releases are managed.
|
|
40
40
|
|
|
41
41
|
Releases are automated via GitHub Actions when changes are merged to `main` with a version bump.
|
|
42
42
|
|
data/README.md
CHANGED
|
@@ -15,21 +15,9 @@ across other products.
|
|
|
15
15
|
|
|
16
16
|
## Documentation
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
Read more:
|
|
23
|
-
|
|
24
|
-
- `docs/installation.md`
|
|
25
|
-
- `docs/configuration.md`
|
|
26
|
-
- `docs/portals.md`
|
|
27
|
-
- `docs/resources.md`
|
|
28
|
-
- `docs/fields.md`
|
|
29
|
-
- `docs/actions.md`
|
|
30
|
-
- `docs/theming.md`
|
|
31
|
-
- `docs/docs_viewer.md`
|
|
32
|
-
- `docs/troubleshooting.md`
|
|
18
|
+
Canonical AdminSuite documentation lives in the TechWright vault at
|
|
19
|
+
`../_vault/products/admin_suite/docs/`. This repo intentionally has no root
|
|
20
|
+
`docs/` tree or docs symlink.
|
|
33
21
|
|
|
34
22
|
## Quickstart
|
|
35
23
|
|
|
@@ -67,7 +55,7 @@ AdminSuite.configure do |config|
|
|
|
67
55
|
end
|
|
68
56
|
```
|
|
69
57
|
|
|
70
|
-
Read more:
|
|
58
|
+
Read more: `../_vault/products/admin_suite/docs/configuration.md`
|
|
71
59
|
|
|
72
60
|
### Add portals (navigation metadata)
|
|
73
61
|
|
|
@@ -80,7 +68,7 @@ AdminSuite.configure do |config|
|
|
|
80
68
|
end
|
|
81
69
|
```
|
|
82
70
|
|
|
83
|
-
Read more:
|
|
71
|
+
Read more: `../_vault/products/admin_suite/docs/portals.md`
|
|
84
72
|
|
|
85
73
|
### Add a resource
|
|
86
74
|
|
|
@@ -119,25 +107,23 @@ module Admin
|
|
|
119
107
|
end
|
|
120
108
|
```
|
|
121
109
|
|
|
122
|
-
Read more:
|
|
110
|
+
Read more: `../_vault/products/admin_suite/docs/resources.md` and `../_vault/products/admin_suite/docs/fields.md`
|
|
123
111
|
|
|
124
112
|
### Add docs (optional)
|
|
125
113
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
- `docs/*.md` (or set `config.docs_path`)
|
|
114
|
+
Set `config.docs_path` to an explicit documentation source. TechWright host apps point this at their canonical `_vault/products/<product>/docs/` directory; they do not create repo `docs/` trees.
|
|
129
115
|
|
|
130
116
|
Then visit:
|
|
131
117
|
|
|
132
118
|
- `/internal/admin/docs`
|
|
133
119
|
|
|
134
|
-
Read more:
|
|
120
|
+
Read more: `../_vault/products/admin_suite/docs/docs_viewer.md`
|
|
135
121
|
|
|
136
122
|
## Contributing
|
|
137
123
|
|
|
138
124
|
See:
|
|
139
125
|
|
|
140
126
|
- `CONTRIBUTING.md`
|
|
141
|
-
-
|
|
142
|
-
-
|
|
127
|
+
- `../_vault/products/admin_suite/docs/development.md`
|
|
128
|
+
- `../_vault/products/admin_suite/docs/releasing.md`
|
|
143
129
|
|
|
@@ -5,14 +5,16 @@ module AdminSuite
|
|
|
5
5
|
include Pagy::Backend
|
|
6
6
|
include Pagy::Frontend
|
|
7
7
|
|
|
8
|
+
before_action :enforce_read_only!, only: %i[new create edit update destroy]
|
|
8
9
|
before_action :set_resource, if: -> { params[:id].present? && !%w[index new create].include?(action_name) }
|
|
9
10
|
|
|
10
11
|
helper_method :resource_config, :resource_class, :resource, :collection, :current_portal, :resource_name
|
|
11
12
|
|
|
12
13
|
# GET /:portal/:resource_name
|
|
13
14
|
def index
|
|
14
|
-
|
|
15
|
-
@
|
|
15
|
+
scope = filtered_collection
|
|
16
|
+
@stats = calculate_stats(scope) if resource_config&.index_config&.stats_list&.any?
|
|
17
|
+
@pagy, @collection = paginate_collection(scope)
|
|
16
18
|
end
|
|
17
19
|
|
|
18
20
|
# GET /:portal/:resource_name/:id
|
|
@@ -181,11 +183,15 @@ module AdminSuite
|
|
|
181
183
|
pagy(scope, items: per_page)
|
|
182
184
|
end
|
|
183
185
|
|
|
184
|
-
def calculate_stats
|
|
186
|
+
def calculate_stats(scope)
|
|
185
187
|
resource_config.index_config.stats_list.map do |stat_def|
|
|
186
188
|
value =
|
|
187
189
|
begin
|
|
188
|
-
stat_def.calculator.
|
|
190
|
+
if stat_def.calculator.arity.zero?
|
|
191
|
+
stat_def.calculator.call
|
|
192
|
+
else
|
|
193
|
+
stat_def.calculator.call(scope)
|
|
194
|
+
end
|
|
189
195
|
rescue StandardError
|
|
190
196
|
"N/A"
|
|
191
197
|
end
|
|
@@ -193,6 +199,10 @@ module AdminSuite
|
|
|
193
199
|
end
|
|
194
200
|
end
|
|
195
201
|
|
|
202
|
+
def enforce_read_only!
|
|
203
|
+
head :not_found if resource_config&.read_only?
|
|
204
|
+
end
|
|
205
|
+
|
|
196
206
|
def find_action(name)
|
|
197
207
|
resource_config&.actions_config&.member_actions&.find { |a| a.name == name }
|
|
198
208
|
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<% content_for :title, "#{resource_config.human_name_plural} - Admin Suite" %>
|
|
2
2
|
|
|
3
3
|
<%
|
|
4
|
-
has_new_route = begin
|
|
4
|
+
has_new_route = !resource_config.read_only? && begin
|
|
5
5
|
url_for(action: :new)
|
|
6
6
|
true
|
|
7
7
|
rescue ActionController::UrlGenerationError
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
has_edit_route = ->(record) {
|
|
12
|
+
next false if resource_config.read_only?
|
|
13
|
+
|
|
12
14
|
begin
|
|
13
15
|
url_for(action: :edit, id: record.to_param)
|
|
14
16
|
true
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
<% content_for :title, "#{resource.respond_to?(:name) ? resource.name : "#{resource_config.human_name} ##{resource.id}"} - Admin Suite" %>
|
|
2
2
|
|
|
3
3
|
<%
|
|
4
|
-
has_edit_route = begin
|
|
4
|
+
has_edit_route = !resource_config.read_only? && begin
|
|
5
5
|
url_for(action: :edit, id: resource.to_param)
|
|
6
6
|
true
|
|
7
7
|
rescue ActionController::UrlGenerationError
|
|
8
8
|
false
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
has_destroy_route = begin
|
|
11
|
+
has_destroy_route = !resource_config.read_only? && begin
|
|
12
12
|
url_for(action: :destroy, id: resource.to_param)
|
|
13
13
|
true
|
|
14
14
|
rescue ActionController::UrlGenerationError
|
|
@@ -57,7 +57,8 @@ module Admin
|
|
|
57
57
|
# They are handled elsewhere (`apply_sort`) and must not be turned into SQL.
|
|
58
58
|
return scope if %i[sort sort_direction direction page search].include?(filter.name.to_sym)
|
|
59
59
|
|
|
60
|
-
value = params[filter.name]
|
|
60
|
+
value = params[filter.name].presence
|
|
61
|
+
value = filter.default if value.blank? && filter.respond_to?(:default)
|
|
61
62
|
return scope if value.blank?
|
|
62
63
|
|
|
63
64
|
if filter.respond_to?(:apply) && filter.apply.present?
|
data/lib/admin/base/resource.rb
CHANGED
|
@@ -165,6 +165,17 @@ module Admin
|
|
|
165
165
|
@export_formats = formats
|
|
166
166
|
end
|
|
167
167
|
|
|
168
|
+
# Declares whether the generic resource routes may mutate records.
|
|
169
|
+
# Read-only resources retain index/show access while the controller
|
|
170
|
+
# rejects direct requests to every built-in mutation endpoint.
|
|
171
|
+
def read_only(value = true)
|
|
172
|
+
@read_only = value
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def read_only?
|
|
176
|
+
@read_only == true
|
|
177
|
+
end
|
|
178
|
+
|
|
168
179
|
# Returns the resource name derived from class name
|
|
169
180
|
#
|
|
170
181
|
# @return [String]
|
|
@@ -328,12 +339,13 @@ module Admin
|
|
|
328
339
|
placeholder: options[:placeholder],
|
|
329
340
|
options: select_options,
|
|
330
341
|
field: options[:field] || name,
|
|
331
|
-
apply: options[:apply]
|
|
342
|
+
apply: options[:apply],
|
|
343
|
+
default: options[:default]
|
|
332
344
|
)
|
|
333
345
|
end
|
|
334
346
|
end
|
|
335
347
|
|
|
336
|
-
FilterDefinition = Struct.new(:name, :type, :label, :placeholder, :options, :field, :apply, keyword_init: true)
|
|
348
|
+
FilterDefinition = Struct.new(:name, :type, :label, :placeholder, :options, :field, :apply, :default, keyword_init: true)
|
|
337
349
|
|
|
338
350
|
class StatsBuilder
|
|
339
351
|
attr_reader :stats
|
data/lib/admin_suite/version.rb
CHANGED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
module AdminSuite
|
|
6
|
+
class ResourcesControllerTest < ActiveSupport::TestCase
|
|
7
|
+
class TestController < ResourcesController
|
|
8
|
+
attr_writer :test_resource_config
|
|
9
|
+
attr_reader :filter_calls, :paginated_scope
|
|
10
|
+
|
|
11
|
+
def initialize
|
|
12
|
+
super
|
|
13
|
+
@filter_calls = 0
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def resource_config
|
|
19
|
+
@test_resource_config
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def filtered_collection
|
|
23
|
+
@filter_calls += 1
|
|
24
|
+
{ total: 37 }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def paginate_collection(scope)
|
|
28
|
+
@paginated_scope = scope
|
|
29
|
+
[ Object.new, :paginated ]
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
class StatsResource < Admin::Base::Resource
|
|
34
|
+
index do
|
|
35
|
+
stats do
|
|
36
|
+
stat :legacy, -> { 11 }
|
|
37
|
+
stat :filtered, ->(scope) { scope.fetch(:total) }
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
class BrokenStatsResource < Admin::Base::Resource
|
|
43
|
+
index do
|
|
44
|
+
stats do
|
|
45
|
+
stat :broken, ->(_scope) { raise "boom" }
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
test "stats preserve zero arity calculators and pass the filtered scope to one arity calculators" do
|
|
51
|
+
controller = TestController.new
|
|
52
|
+
controller.test_resource_config = StatsResource
|
|
53
|
+
scope = { total: 37 }
|
|
54
|
+
|
|
55
|
+
stats = controller.send(:calculate_stats, scope)
|
|
56
|
+
|
|
57
|
+
assert_equal 11, stats.first[:value]
|
|
58
|
+
assert_equal 37, stats.second[:value]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
test "stats preserve the existing calculator rescue behavior" do
|
|
62
|
+
controller = TestController.new
|
|
63
|
+
controller.test_resource_config = BrokenStatsResource
|
|
64
|
+
|
|
65
|
+
assert_equal "N/A", controller.send(:calculate_stats, Object.new).first[:value]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
test "index reuses one filtered unpaginated scope for stats and pagination" do
|
|
69
|
+
controller = TestController.new
|
|
70
|
+
controller.test_resource_config = StatsResource
|
|
71
|
+
|
|
72
|
+
controller.index
|
|
73
|
+
|
|
74
|
+
assert_equal 1, controller.filter_calls
|
|
75
|
+
assert_equal({ total: 37 }, controller.paginated_scope)
|
|
76
|
+
assert_equal 37, controller.instance_variable_get(:@stats).second[:value]
|
|
77
|
+
assert_equal :paginated, controller.instance_variable_get(:@collection)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
# The dummy app is intentionally database-free, while the generic controller
|
|
6
|
+
# supports Active Record hosts. Supply only the exception type its lookup path
|
|
7
|
+
# rescues so show-page behavior can be exercised with an in-memory fixture.
|
|
8
|
+
unless defined?(ActiveRecord::RecordNotFound)
|
|
9
|
+
module ActiveRecord
|
|
10
|
+
class RecordNotFound < StandardError; end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
module TurboFrameTestHelper
|
|
15
|
+
def turbo_frame_tag(name, **options, &block)
|
|
16
|
+
content_tag(:turbo_frame, capture(&block), id: name, **options)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
ActionView::Base.include(TurboFrameTestHelper)
|
|
21
|
+
|
|
22
|
+
module ReadOnlyResourceFixtures
|
|
23
|
+
class Relation
|
|
24
|
+
include Enumerable
|
|
25
|
+
|
|
26
|
+
def initialize(records)
|
|
27
|
+
@records = records
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def each(&block)
|
|
31
|
+
@records.each(&block)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def count(*)
|
|
35
|
+
@records.count
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def offset(*)
|
|
39
|
+
self
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def limit(*)
|
|
43
|
+
self
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
class Widget
|
|
48
|
+
extend ActiveModel::Naming
|
|
49
|
+
|
|
50
|
+
attr_reader :id, :name
|
|
51
|
+
|
|
52
|
+
def initialize(id: 1, name: "Observed widget")
|
|
53
|
+
@id = id
|
|
54
|
+
@name = name
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def self.all
|
|
58
|
+
Relation.new([ new ])
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def self.column_names
|
|
62
|
+
%w[id name]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def self.primary_key
|
|
66
|
+
"id"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def self.columns_hash
|
|
70
|
+
{ "id" => Struct.new(:type).new(:integer) }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def self.find(id)
|
|
74
|
+
raise ActiveRecord::RecordNotFound unless id.to_s == "1"
|
|
75
|
+
|
|
76
|
+
new
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def to_param
|
|
80
|
+
id.to_s
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def attributes
|
|
84
|
+
{ "id" => id, "name" => name }
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
module Admin
|
|
90
|
+
module Resources
|
|
91
|
+
class ReadOnlyWidgetResource < Admin::Base::Resource
|
|
92
|
+
model ReadOnlyResourceFixtures::Widget
|
|
93
|
+
portal :ops
|
|
94
|
+
section :observability
|
|
95
|
+
read_only
|
|
96
|
+
|
|
97
|
+
index do
|
|
98
|
+
columns do
|
|
99
|
+
column :name
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
module AdminSuite
|
|
107
|
+
class ReadOnlyResourceTest < ActionDispatch::IntegrationTest
|
|
108
|
+
BASE_PATH = "/internal/admin_suite/ops/read_only_widgets"
|
|
109
|
+
|
|
110
|
+
test "direct built in mutation endpoints are rejected" do
|
|
111
|
+
get "#{BASE_PATH}/new"
|
|
112
|
+
assert_response :not_found
|
|
113
|
+
|
|
114
|
+
post BASE_PATH, params: { read_only_resource_fixtures_widget: { name: "changed" } }
|
|
115
|
+
assert_response :not_found
|
|
116
|
+
|
|
117
|
+
get "#{BASE_PATH}/1/edit"
|
|
118
|
+
assert_response :not_found
|
|
119
|
+
|
|
120
|
+
patch "#{BASE_PATH}/1", params: { read_only_resource_fixtures_widget: { name: "changed" } }
|
|
121
|
+
assert_response :not_found
|
|
122
|
+
|
|
123
|
+
delete "#{BASE_PATH}/1"
|
|
124
|
+
assert_response :not_found
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
test "index hides create and edit controls" do
|
|
128
|
+
get BASE_PATH
|
|
129
|
+
|
|
130
|
+
assert_response :success
|
|
131
|
+
assert_includes response.body, "Observed widget"
|
|
132
|
+
refute_includes response.body, "New Widget"
|
|
133
|
+
refute_match(/>\s*Edit\s*</, response.body)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
test "show mutation controls are conditional on write access" do
|
|
137
|
+
template = AdminSuite::Engine.root.join("app/views/admin_suite/resources/show.html.erb").read
|
|
138
|
+
|
|
139
|
+
assert_includes template, "has_edit_route = !resource_config.read_only?"
|
|
140
|
+
assert_includes template, "has_destroy_route = !resource_config.read_only?"
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
module AdminSuite
|
|
6
|
+
class ResourceObservabilityExtensionsTest < ActiveSupport::TestCase
|
|
7
|
+
FakeScope = Struct.new(:filters) do
|
|
8
|
+
def where(*arguments)
|
|
9
|
+
self.class.new(filters + [ arguments ])
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class FilteredResource < Admin::Base::Resource
|
|
14
|
+
index do
|
|
15
|
+
filters do
|
|
16
|
+
filter :window, type: :select, default: "24h",
|
|
17
|
+
apply: ->(scope, value) { scope.where(window: value) }
|
|
18
|
+
filter :status, type: :select
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class ReadOnlyResource < Admin::Base::Resource
|
|
24
|
+
read_only
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
test "resources are writable by default and may be declared read only" do
|
|
28
|
+
refute Admin::Base::Resource.read_only?
|
|
29
|
+
assert ReadOnlyResource.read_only?
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
test "filter defaults apply when the parameter is absent" do
|
|
33
|
+
scope = Admin::Base::FilterBuilder.new(FilteredResource, ActionController::Parameters.new)
|
|
34
|
+
.apply(FakeScope.new([]))
|
|
35
|
+
|
|
36
|
+
assert_equal [ [ { window: "24h" } ] ], scope.filters
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
test "filter defaults apply when the parameter is blank and compose with explicit filters" do
|
|
40
|
+
params = ActionController::Parameters.new(window: "", status: "failed")
|
|
41
|
+
scope = Admin::Base::FilterBuilder.new(FilteredResource, params).apply(FakeScope.new([]))
|
|
42
|
+
|
|
43
|
+
assert_equal [ [ { window: "24h" } ], [ { status: "failed" } ] ], scope.filters
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
test "an explicit filter overrides its default" do
|
|
47
|
+
params = ActionController::Parameters.new(window: "7d")
|
|
48
|
+
scope = Admin::Base::FilterBuilder.new(FilteredResource, params).apply(FakeScope.new([]))
|
|
49
|
+
|
|
50
|
+
assert_equal [ [ { window: "7d" } ] ], scope.filters
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: admin_suite
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.9
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- TechWright Labs
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-07-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -173,18 +173,6 @@ files:
|
|
|
173
173
|
- app/views/layouts/admin_suite/application.html.erb
|
|
174
174
|
- config/importmap.rb
|
|
175
175
|
- config/routes.rb
|
|
176
|
-
- docs/README.md
|
|
177
|
-
- docs/actions.md
|
|
178
|
-
- docs/configuration.md
|
|
179
|
-
- docs/development.md
|
|
180
|
-
- docs/docs_viewer.md
|
|
181
|
-
- docs/fields.md
|
|
182
|
-
- docs/installation.md
|
|
183
|
-
- docs/portals.md
|
|
184
|
-
- docs/releasing.md
|
|
185
|
-
- docs/resources.md
|
|
186
|
-
- docs/theming.md
|
|
187
|
-
- docs/troubleshooting.md
|
|
188
176
|
- lib/admin/base/action_executor.rb
|
|
189
177
|
- lib/admin/base/action_handler.rb
|
|
190
178
|
- lib/admin/base/filter_builder.rb
|
|
@@ -209,6 +197,7 @@ files:
|
|
|
209
197
|
- lib/generators/admin_suite/scaffold/scaffold_generator.rb
|
|
210
198
|
- lib/tasks/admin_suite_tailwind.rake
|
|
211
199
|
- lib/tasks/admin_suite_test.rake
|
|
200
|
+
- test/controllers/resources_controller_test.rb
|
|
212
201
|
- test/dummy/Gemfile
|
|
213
202
|
- test/dummy/README.md
|
|
214
203
|
- test/dummy/Rakefile
|
|
@@ -253,9 +242,11 @@ files:
|
|
|
253
242
|
- test/fixtures/docs/progress/PROGRESS_REPORT.md
|
|
254
243
|
- test/integration/dashboard_test.rb
|
|
255
244
|
- test/integration/docs_test.rb
|
|
245
|
+
- test/integration/read_only_resource_test.rb
|
|
256
246
|
- test/integration/theme_test.rb
|
|
257
247
|
- test/lib/action_executor_test.rb
|
|
258
248
|
- test/lib/markdown_renderer_test.rb
|
|
249
|
+
- test/lib/resource_observability_extensions_test.rb
|
|
259
250
|
- test/lib/theme_palette_test.rb
|
|
260
251
|
- test/lib/zeitwerk_integration_test.rb
|
|
261
252
|
- test/test_helper.rb
|
data/docs/README.md
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
# AdminSuite Documentation
|
|
2
|
-
|
|
3
|
-
AdminSuite is a mountable Rails engine that provides:
|
|
4
|
-
|
|
5
|
-
- A **resource DSL** for CRUD + search/sort/filter + show/form configuration
|
|
6
|
-
- A **portal system** (navigation + optional portal dashboards)
|
|
7
|
-
- A built-in **docs viewer** (renders Markdown from your host app filesystem)
|
|
8
|
-
- A small baseline **UI layer** (Tailwind optional)
|
|
9
|
-
|
|
10
|
-
## Getting started
|
|
11
|
-
|
|
12
|
-
- [Installation](installation.md)
|
|
13
|
-
- [Configuration](configuration.md)
|
|
14
|
-
- [Portals & dashboards](portals.md)
|
|
15
|
-
- [Resources](resources.md)
|
|
16
|
-
- [Fields](fields.md)
|
|
17
|
-
- [Actions](actions.md)
|
|
18
|
-
- [Theming & assets](theming.md)
|
|
19
|
-
- [Docs viewer](docs_viewer.md)
|
|
20
|
-
- [Troubleshooting](troubleshooting.md)
|
|
21
|
-
|
|
22
|
-
## Contributing / maintainers
|
|
23
|
-
|
|
24
|
-
- [Development](development.md)
|
|
25
|
-
- [Releasing](releasing.md)
|
|
26
|
-
|