admin_suite 0.4.0 → 0.6.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.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +7 -0
  3. data/CHANGELOG.md +203 -18
  4. data/CONTRIBUTING.md +9 -4
  5. data/README.md +21 -5
  6. data/app/assets/vendor/chart.umd.min.js +14 -0
  7. data/app/controllers/admin_suite/application_controller.rb +12 -11
  8. data/app/controllers/admin_suite/mcp_controller.rb +36 -0
  9. data/app/controllers/admin_suite/resources_controller.rb +71 -17
  10. data/app/helpers/admin_suite/base_helper.rb +187 -10
  11. data/app/javascript/admin_suite_application.js +3 -0
  12. data/app/javascript/controllers/admin_suite/chart_controller.js +173 -0
  13. data/app/views/admin_suite/panels/_chart.html.erb +158 -18
  14. data/app/views/admin_suite/resources/index.html.erb +25 -5
  15. data/app/views/admin_suite/shared/_sidebar.html.erb +14 -8
  16. data/app/views/layouts/admin_suite/application.html.erb +6 -0
  17. data/config/routes.rb +8 -0
  18. data/lib/admin/base/filter_builder.rb +48 -5
  19. data/lib/admin/base/resource.rb +46 -32
  20. data/lib/admin_suite/auth/host_user.rb +42 -0
  21. data/lib/admin_suite/auth/strategy.rb +1 -1
  22. data/lib/admin_suite/auth.rb +15 -0
  23. data/lib/admin_suite/authorization_context.rb +28 -0
  24. data/lib/admin_suite/configuration.rb +66 -8
  25. data/lib/admin_suite/engine.rb +1 -1
  26. data/lib/admin_suite/legacy_custom_renderer_procs.rb +1 -1
  27. data/lib/admin_suite/mcp/authorization.rb +68 -0
  28. data/lib/admin_suite/mcp/serializer.rb +138 -0
  29. data/lib/admin_suite/mcp/tools/aggregate.rb +55 -0
  30. data/lib/admin_suite/mcp/tools/describe_resources.rb +63 -0
  31. data/lib/admin_suite/mcp/tools/get_record.rb +41 -0
  32. data/lib/admin_suite/mcp/tools/list_records.rb +67 -0
  33. data/lib/admin_suite/mcp.rb +53 -0
  34. data/lib/admin_suite/query.rb +71 -0
  35. data/lib/admin_suite/renderer_registry.rb +11 -0
  36. data/lib/admin_suite/ui/dashboard_definition.rb +6 -0
  37. data/lib/admin_suite/ui/show_value_formatter.rb +2 -2
  38. data/lib/admin_suite/version.rb +1 -1
  39. data/lib/admin_suite.rb +23 -7
  40. data/lib/generators/admin_suite/install/templates/admin_suite.rb +5 -2
  41. data/test/controllers/resources_controller_test.rb +19 -13
  42. data/test/integration/association_linking_test.rb +292 -0
  43. data/test/integration/authentication_test.rb +10 -0
  44. data/test/integration/authorization_test.rb +20 -3
  45. data/test/integration/chart_panel_test.rb +491 -0
  46. data/test/integration/dashboard_test.rb +9 -2
  47. data/test/integration/index_query_characterization_test.rb +229 -0
  48. data/test/integration/index_table_test.rb +289 -0
  49. data/test/integration/mcp_aggregate_test.rb +44 -0
  50. data/test/integration/mcp_authorization_test.rb +102 -0
  51. data/test/integration/mcp_endpoint_test.rb +89 -0
  52. data/test/integration/mcp_get_record_test.rb +62 -0
  53. data/test/integration/mcp_instrumentation_test.rb +107 -0
  54. data/test/integration/mcp_list_records_test.rb +75 -0
  55. data/test/integration/mcp_parity_test.rb +155 -0
  56. data/test/integration/mcp_release_test.rb +131 -0
  57. data/test/integration/navigation_sections_test.rb +21 -0
  58. data/test/integration/read_only_resource_test.rb +128 -2
  59. data/test/integration/searchable_select_search_test.rb +369 -0
  60. data/test/integration/show_hide_blank_test.rb +195 -0
  61. data/test/integration/toggle_test.rb +106 -0
  62. data/test/lib/auth_host_user_test.rb +52 -0
  63. data/test/lib/auth_http_basic_test.rb +10 -0
  64. data/test/lib/auth_test.rb +20 -3
  65. data/test/lib/authorization_context_test.rb +127 -0
  66. data/test/lib/definition_loader_test.rb +12 -0
  67. data/test/lib/engine_defaults_test.rb +1 -2
  68. data/test/lib/form_field_renderer_test.rb +83 -11
  69. data/test/lib/format_table_cell_test.rb +49 -0
  70. data/test/lib/index_includes_test.rb +223 -0
  71. data/test/lib/mcp_serializer_test.rb +107 -0
  72. data/test/lib/query_test.rb +91 -0
  73. data/test/lib/removed_deprecations_test.rb +16 -0
  74. data/test/lib/renderer_test.rb +23 -7
  75. data/test/publish_workflow_test.rb +63 -0
  76. data/test/test_helper.rb +73 -8
  77. metadata +76 -9
  78. data/lib/admin_suite/renderers/legacy_gleania.rb +0 -230
  79. data/test/lib/legacy_renderer_deprecation_test.rb +0 -35
  80. data/test/lib/resource_exportable_deprecation_test.rb +0 -39
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "minitest/autorun"
4
+ require "yaml"
5
+
6
+ class PublishWorkflowTest < Minitest::Test
7
+ WORKFLOW_PATH = File.expand_path("../.github/workflows/publish.yml", __dir__)
8
+
9
+ def setup
10
+ @source = File.read(WORKFLOW_PATH)
11
+ @workflow = YAML.load_file(WORKFLOW_PATH)
12
+ @job = @workflow.fetch("jobs").fetch("publish")
13
+ @steps = @job.fetch("steps")
14
+ end
15
+
16
+ def test_publishing_uses_the_release_environment_and_job_scoped_oidc
17
+ assert_equal "release", @job["environment"]
18
+ assert_equal "write", @job.fetch("permissions")["id-token"]
19
+ assert_equal "write", @job.fetch("permissions")["contents"]
20
+ refute @workflow.fetch("permissions", {}).key?("id-token")
21
+ end
22
+
23
+ def test_automatic_publishing_requires_successful_main_push_ci
24
+ condition = @job.fetch("if")
25
+ assert_includes condition, "github.repository == 'techwright-lab/admin_suite'"
26
+ assert_includes condition, "github.event_name == 'workflow_run'"
27
+ assert_includes condition, "github.event.workflow_run.conclusion == 'success'"
28
+ assert_includes condition, "github.event.workflow_run.event == 'push'"
29
+ assert_includes condition, "github.event.workflow_run.head_branch == 'main'"
30
+ assert_includes condition, "github.event.workflow_run.head_repository.full_name == github.repository"
31
+ assert_includes condition, "github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main'"
32
+ end
33
+
34
+ def test_credentials_are_pinned_conditional_and_before_the_push
35
+ credentials_index = @steps.index { |step| step["name"] == "Configure RubyGems trusted publishing" }
36
+ refute_nil credentials_index
37
+ credentials = @steps.fetch(credentials_index)
38
+ push_index = @steps.index { |step| step["name"] == "Publish to RubyGems" }
39
+ push = @steps.fetch(push_index)
40
+
41
+ assert_equal "rubygems/configure-rubygems-credentials@dc5a8d8553e6ee01fc26761a49e99e733d17954a", credentials["uses"]
42
+ assert_equal "steps.version_check.outputs.should_publish == 'true'", credentials["if"]
43
+ assert_equal credentials["if"], push["if"]
44
+ assert_operator credentials_index, :<, push_index
45
+ refute_includes @source, "secrets.RUBYGEMS_API_KEY"
46
+ refute_includes @source, "GEM_HOST_API_KEY:"
47
+ refute_includes @source, "~/.gem/credentials"
48
+ assert_includes push.fetch("run"), "gem push"
49
+ assert_includes push.fetch("run"), "--host https://rubygems.org"
50
+ end
51
+
52
+ def test_releases_preserve_tested_sha_and_serialization
53
+ checkout = @steps.find { |step| step.fetch("uses", "").start_with?("actions/checkout@") }
54
+ assert_equal "${{ github.event.workflow_run.head_sha || github.sha }}", checkout.fetch("with")["ref"]
55
+ assert_equal false, @workflow.fetch("concurrency")["cancel-in-progress"]
56
+ assert_includes @source, "different contents. Bump the gem version before publishing."
57
+ push_index = @steps.index { |step| step["name"] == "Publish to RubyGems" }
58
+ tag_index = @steps.index { |step| step["name"] == "Create Git tag" }
59
+ release_index = @steps.index { |step| step["name"] == "Create GitHub Release" }
60
+ assert_operator push_index, :<, tag_index
61
+ assert_operator tag_index, :<, release_index
62
+ end
63
+ end
data/test/test_helper.rb CHANGED
@@ -16,6 +16,15 @@ if ENV["COVERAGE"]
16
16
  end
17
17
 
18
18
  require_relative "dummy/config/environment"
19
+
20
+ # Auth strategies are process-global. Tests that register temporary strategies
21
+ # can restore a snapshot with this helper to avoid cross-file registry leakage.
22
+ def with_auth_registry_snapshot
23
+ registry = AdminSuite::Auth.instance_variable_get(:@registry).dup
24
+ yield
25
+ ensure
26
+ AdminSuite::Auth.instance_variable_set(:@registry, registry)
27
+ end
19
28
  require "minitest/autorun"
20
29
  require "active_support/test_case"
21
30
  require "action_dispatch/testing/integration"
@@ -23,6 +32,14 @@ require "action_dispatch/testing/integration"
23
32
  # Ensure the engine is loaded (and its initializers run).
24
33
  require "admin_suite"
25
34
 
35
+ def with_authorize(hook)
36
+ previous = AdminSuite.config.authorize
37
+ AdminSuite.config.authorize = hook
38
+ yield
39
+ ensure
40
+ AdminSuite.config.authorize = previous
41
+ end
42
+
26
43
  # The dummy app is intentionally database-free, while the generic controller
27
44
  # supports Active Record hosts. Supply only the exception type its lookup path
28
45
  # rescues so show-page behavior can be exercised with an in-memory fixture.
@@ -43,14 +60,6 @@ unless defined?(ActiveRecord::Base)
43
60
  end
44
61
  end
45
62
 
46
- module TurboFrameTestHelper
47
- def turbo_frame_tag(name, **options, &block)
48
- content_tag(:turbo_frame, capture(&block), id: name, **options)
49
- end
50
- end
51
-
52
- ActionView::Base.include(TurboFrameTestHelper)
53
-
54
63
  module ReadOnlyResourceFixtures
55
64
  class Relation
56
65
  include Enumerable
@@ -142,3 +151,59 @@ module Admin
142
151
  end
143
152
  end
144
153
  end
154
+
155
+ # The dummy app is database-free and `ActiveRecord::Base` above is a bare
156
+ # stub class, so every other fixture in this suite is a PORO and
157
+ # `active_record_base?` is false for all of them -- every AR-dependent path
158
+ # (`auto_admin_suite_path_for`, `format_table_cell`'s AR branch) is
159
+ # otherwise unreachable by tests. Subclassing the stub gives a fixture that
160
+ # genuinely satisfies `active_record_base?`, with no database and no
161
+ # railtie required. Established here (Task 3, phase 2b) because it's
162
+ # reused by later tasks (4, 7); those tasks' own test files each `require
163
+ # "test_helper"`, so this is visible to them even when a single test file
164
+ # is run in isolation via `rake test TEST=...` -- unlike a fixture defined
165
+ # only in association_linking_test.rb, which would not be.
166
+ module LinkingFixtures
167
+ class Company < ActiveRecord::Base
168
+ extend ActiveModel::Naming
169
+
170
+ attr_reader :id, :name
171
+
172
+ def initialize(id: 7, name: "Acme Corp")
173
+ @id = id
174
+ @name = name
175
+ end
176
+
177
+ def self.all = ReadOnlyResourceFixtures::Relation.new([ new ])
178
+ def self.column_names = %w[id name]
179
+ def self.primary_key = "id"
180
+ def self.columns_hash = { "id" => Struct.new(:type).new(:integer) }
181
+ def self.find(_id) = new
182
+ def to_param = id.to_s
183
+ def attributes = { "id" => id, "name" => name }
184
+ end
185
+ end
186
+
187
+ module Admin
188
+ module Resources
189
+ # Registered so `auto_admin_suite_path_for` can resolve a real path for
190
+ # `LinkingFixtures::Company` instances (it looks up `registered_resources`
191
+ # by `model_class`). Never call `Admin::Base::Resource.reset_registry!` in
192
+ # a test: `inherited` fires only on class creation, so a reset-then-reload
193
+ # would permanently empty the registry for the rest of the run.
194
+ class LinkingCompanyResource < Admin::Base::Resource
195
+ model LinkingFixtures::Company
196
+ portal :ops
197
+ section :observability
198
+ end
199
+ end
200
+ end
201
+
202
+ # MCP never permits anonymous callers, including in the development escape hatch.
203
+ class McpIntegrationTest < ActionDispatch::IntegrationTest
204
+ setup do
205
+ @previous_mcp_actor_resolver = AdminSuite.config.current_actor
206
+ AdminSuite.config.current_actor = ->(_) { "test-operator" }
207
+ end
208
+ teardown { AdminSuite.config.current_actor = @previous_mcp_actor_resolver }
209
+ 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.4.0
4
+ version: 0.6.1
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-08-01 00:00:00.000000000 Z
11
+ date: 2026-09-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -36,20 +36,54 @@ dependencies:
36
36
  requirements:
37
37
  - - ">="
38
38
  - !ruby/object:Gem::Version
39
- version: '6.0'
39
+ version: '9.0'
40
+ - - "<"
41
+ - !ruby/object:Gem::Version
42
+ version: '10'
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '9.0'
50
+ - - "<"
51
+ - !ruby/object:Gem::Version
52
+ version: '10'
53
+ - !ruby/object:Gem::Dependency
54
+ name: mcp
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: '1.1'
60
+ type: :runtime
61
+ prerelease: false
62
+ version_requirements: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - "~>"
65
+ - !ruby/object:Gem::Version
66
+ version: '1.1'
67
+ - !ruby/object:Gem::Dependency
68
+ name: turbo-rails
69
+ requirement: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '2.0'
40
74
  - - "<"
41
75
  - !ruby/object:Gem::Version
42
- version: '11.0'
76
+ version: '3.0'
43
77
  type: :runtime
44
78
  prerelease: false
45
79
  version_requirements: !ruby/object:Gem::Requirement
46
80
  requirements:
47
81
  - - ">="
48
82
  - !ruby/object:Gem::Version
49
- version: '6.0'
83
+ version: '2.0'
50
84
  - - "<"
51
85
  - !ruby/object:Gem::Version
52
- version: '11.0'
86
+ version: '3.0'
53
87
  - !ruby/object:Gem::Dependency
54
88
  name: lucide-rails
55
89
  requirement: !ruby/object:Gem::Requirement
@@ -126,11 +160,13 @@ files:
126
160
  - app/assets/builds/admin_suite_tailwind.css
127
161
  - app/assets/rouge.css
128
162
  - app/assets/tailwind/admin_suite.css
163
+ - app/assets/vendor/chart.umd.min.js
129
164
  - app/assets/vendor/easymde.min.css
130
165
  - app/assets/vendor/easymde.min.js
131
166
  - app/controllers/admin_suite/application_controller.rb
132
167
  - app/controllers/admin_suite/dashboard_controller.rb
133
168
  - app/controllers/admin_suite/docs_controller.rb
169
+ - app/controllers/admin_suite/mcp_controller.rb
134
170
  - app/controllers/admin_suite/portals_controller.rb
135
171
  - app/controllers/admin_suite/resources_controller.rb
136
172
  - app/helpers/admin_suite/base_helper.rb
@@ -139,6 +175,7 @@ files:
139
175
  - app/helpers/admin_suite/resources_helper.rb
140
176
  - app/helpers/admin_suite/theme_helper.rb
141
177
  - app/javascript/admin_suite_application.js
178
+ - app/javascript/controllers/admin_suite/chart_controller.js
142
179
  - app/javascript/controllers/admin_suite/click_actions_controller.js
143
180
  - app/javascript/controllers/admin_suite/clipboard_controller.js
144
181
  - app/javascript/controllers/admin_suite/code_editor_controller.js
@@ -183,8 +220,10 @@ files:
183
220
  - lib/admin_suite.rb
184
221
  - lib/admin_suite/auth.rb
185
222
  - lib/admin_suite/auth/host_hook.rb
223
+ - lib/admin_suite/auth/host_user.rb
186
224
  - lib/admin_suite/auth/http_basic.rb
187
225
  - lib/admin_suite/auth/strategy.rb
226
+ - lib/admin_suite/authorization_context.rb
188
227
  - lib/admin_suite/configuration.rb
189
228
  - lib/admin_suite/definition_loader.rb
190
229
  - lib/admin_suite/deprecation.rb
@@ -192,14 +231,21 @@ files:
192
231
  - lib/admin_suite/host_autoload_policy.rb
193
232
  - lib/admin_suite/legacy_custom_renderer_procs.rb
194
233
  - lib/admin_suite/markdown_renderer.rb
234
+ - lib/admin_suite/mcp.rb
235
+ - lib/admin_suite/mcp/authorization.rb
236
+ - lib/admin_suite/mcp/serializer.rb
237
+ - lib/admin_suite/mcp/tools/aggregate.rb
238
+ - lib/admin_suite/mcp/tools/describe_resources.rb
239
+ - lib/admin_suite/mcp/tools/get_record.rb
240
+ - lib/admin_suite/mcp/tools/list_records.rb
195
241
  - lib/admin_suite/portal_definition.rb
196
242
  - lib/admin_suite/portal_registry.rb
243
+ - lib/admin_suite/query.rb
197
244
  - lib/admin_suite/renderer.rb
198
245
  - lib/admin_suite/renderer_registry.rb
199
246
  - lib/admin_suite/renderers/code_renderer.rb
200
247
  - lib/admin_suite/renderers/json_renderer.rb
201
248
  - lib/admin_suite/renderers/key_value_renderer.rb
202
- - lib/admin_suite/renderers/legacy_gleania.rb
203
249
  - lib/admin_suite/renderers/table_from_renderer.rb
204
250
  - lib/admin_suite/section_definition.rb
205
251
  - lib/admin_suite/theme_palette.rb
@@ -260,32 +306,53 @@ files:
260
306
  - test/dummy/public/robots.txt
261
307
  - test/dummy/test/test_helper.rb
262
308
  - test/fixtures/docs/progress/PROGRESS_REPORT.md
309
+ - test/integration/association_linking_test.rb
263
310
  - test/integration/authentication_test.rb
264
311
  - test/integration/authorization_test.rb
312
+ - test/integration/chart_panel_test.rb
265
313
  - test/integration/dashboard_test.rb
266
314
  - test/integration/docs_test.rb
315
+ - test/integration/index_query_characterization_test.rb
316
+ - test/integration/index_table_test.rb
267
317
  - test/integration/layout_assets_test.rb
318
+ - test/integration/mcp_aggregate_test.rb
319
+ - test/integration/mcp_authorization_test.rb
320
+ - test/integration/mcp_endpoint_test.rb
321
+ - test/integration/mcp_get_record_test.rb
322
+ - test/integration/mcp_instrumentation_test.rb
323
+ - test/integration/mcp_list_records_test.rb
324
+ - test/integration/mcp_parity_test.rb
325
+ - test/integration/mcp_release_test.rb
268
326
  - test/integration/navigation_sections_test.rb
269
327
  - test/integration/pagination_and_stats_test.rb
270
328
  - test/integration/read_only_resource_test.rb
329
+ - test/integration/searchable_select_search_test.rb
330
+ - test/integration/show_hide_blank_test.rb
271
331
  - test/integration/theme_test.rb
332
+ - test/integration/toggle_test.rb
272
333
  - test/lib/action_executor_redirect_test.rb
273
334
  - test/lib/action_executor_test.rb
335
+ - test/lib/auth_host_user_test.rb
274
336
  - test/lib/auth_http_basic_test.rb
275
337
  - test/lib/auth_resolution_test.rb
276
338
  - test/lib/auth_test.rb
339
+ - test/lib/authorization_context_test.rb
277
340
  - test/lib/builtin_renderers_test.rb
278
341
  - test/lib/definition_loader_test.rb
279
342
  - test/lib/engine_defaults_test.rb
280
343
  - test/lib/form_field_renderer_test.rb
281
- - test/lib/legacy_renderer_deprecation_test.rb
344
+ - test/lib/format_table_cell_test.rb
345
+ - test/lib/index_includes_test.rb
282
346
  - test/lib/markdown_renderer_test.rb
347
+ - test/lib/mcp_serializer_test.rb
348
+ - test/lib/query_test.rb
349
+ - test/lib/removed_deprecations_test.rb
283
350
  - test/lib/renderer_test.rb
284
- - test/lib/resource_exportable_deprecation_test.rb
285
351
  - test/lib/resource_observability_extensions_test.rb
286
352
  - test/lib/show_value_formatter_test.rb
287
353
  - test/lib/theme_palette_test.rb
288
354
  - test/lib/zeitwerk_integration_test.rb
355
+ - test/publish_workflow_test.rb
289
356
  - test/test_helper.rb
290
357
  homepage: https://github.com/techwright-lab/admin_suite
291
358
  licenses:
@@ -1,230 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module AdminSuite
4
- module Renderers
5
- # The four Gleania-specific LLM chat-transcript renderers, moved verbatim
6
- # out of `AdminSuite::BaseHelper` (role-coloured message bubbles, tool-call
7
- # panels, prompt-template mustache highlighting).
8
- #
9
- # These are deprecated: they still work in 0.4.0 (logging a warning once
10
- # per key per process the first time each is used), and are DELETED in
11
- # 0.5.0 (Phase 2b). Gleania is expected to migrate to host-side renderer
12
- # classes under `app/admin/renderers/*.rb` before then.
13
- #
14
- # The renderer bodies below are intentionally byte-equivalent to the
15
- # BaseHelper methods they replace, other than `resource` -> `record` and
16
- # routing BaseHelper method calls (`render_json_block`, `simple_format`,
17
- # `concat`) through `view.` — this class is not `included` into the view,
18
- # so those calls can't resolve as bare method sends the way they did
19
- # inside the helper module. Do not "improve" this markup: a byte-for-byte
20
- # move keeps gleania's Assistant/AI portal pages pixel-identical, and
21
- # makes the 0.5.0 deletion a clean removal.
22
- module LegacyGleania
23
- extend AdminSuite::Deprecation
24
-
25
- DEPRECATION_MESSAGE_FORMAT =
26
- "AdminSuite: the :%<key>s renderer is deprecated and will be removed in 0.5.0. " \
27
- "Move it to app/admin/renderers in your app."
28
-
29
- class << self
30
- # Fires the deprecation sink at most once per `key` per process.
31
- # `warn_once_sink` and `reset_deprecation_notices!` come from
32
- # `AdminSuite::Deprecation`, extended above.
33
- #
34
- # @param key [Symbol]
35
- # @return [void]
36
- def warn_once(key)
37
- super(key, format(DEPRECATION_MESSAGE_FORMAT, key: key))
38
- end
39
- end
40
-
41
- # Verbatim from `BaseHelper#render_prompt_template`.
42
- class PromptTemplateRenderer < Renderer
43
- def render
44
- LegacyGleania.warn_once(:prompt_template_preview)
45
-
46
- template = record.respond_to?(:prompt_template) ? record.prompt_template : nil
47
- return content_tag(:p, "No template defined", class: "text-slate-500 italic") if template.blank?
48
-
49
- highlighted_template = h(template).gsub(/\{\{(\w+)\}\}/) do
50
- "<span class=\"text-amber-400 bg-amber-900/30 px-1 rounded\">{{#{$1}}}</span>"
51
- end
52
-
53
- content_tag(:div, class: "relative group") do
54
- view.concat(content_tag(:div, class: "absolute top-2 right-2 flex items-center gap-2") do
55
- view.concat(content_tag(:span, "TEMPLATE", class: "text-xs font-medium text-slate-400 uppercase tracking-wider"))
56
- view.concat(content_tag(:button,
57
- '<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/></svg>'.html_safe,
58
- type: "button",
59
- class: "p-1 text-slate-400 hover:text-slate-600 opacity-0 group-hover:opacity-100 transition-opacity",
60
- data: { controller: "admin-suite--clipboard", action: "click->admin-suite--clipboard#copy", "admin-suite--clipboard-text-value": template },
61
- title: "Copy to clipboard"))
62
- end)
63
-
64
- view.concat(content_tag(:pre, class: "bg-slate-900 text-slate-100 p-4 rounded-lg overflow-x-auto text-sm font-mono max-h-[600px] overflow-y-auto whitespace-pre-wrap leading-relaxed") do
65
- highlighted_template.html_safe
66
- end)
67
-
68
- variables = template.scan(/\{\{(\w+)\}\}/).flatten.uniq
69
- if variables.any?
70
- view.concat(content_tag(:div, class: "mt-3 pt-3 border-t border-slate-700") do
71
- view.concat(content_tag(:span, "Variables: ", class: "text-sm text-slate-400"))
72
- view.concat(content_tag(:div, class: "inline-flex flex-wrap gap-1 mt-1") do
73
- variables.each do |var|
74
- view.concat(content_tag(:code, "{{#{var}}}", class: "text-xs px-2 py-0.5 bg-amber-900/30 text-amber-400 rounded"))
75
- end
76
- end)
77
- end)
78
- end
79
- end
80
- end
81
- end
82
-
83
- # Verbatim from `BaseHelper#render_messages_preview`.
84
- class MessagesPreviewRenderer < Renderer
85
- def render
86
- LegacyGleania.warn_once(:messages_preview)
87
-
88
- messages = record.respond_to?(:messages) ? record.messages : []
89
- if messages.respond_to?(:chronological)
90
- messages = messages.chronological
91
- end
92
- messages = messages.limit(50) if messages.respond_to?(:limit)
93
- messages = Array.wrap(messages)
94
-
95
- return content_tag(:p, "No messages", class: "text-slate-500 italic") if messages.blank?
96
-
97
- content_tag(:div, class: "space-y-4 max-h-[600px] overflow-y-auto -mx-6 -mb-6 p-6 pt-0") do
98
- messages.each_with_index do |msg, idx|
99
- if msg.respond_to?(:role)
100
- role = msg.role
101
- content = msg.content
102
- created_at = msg.respond_to?(:created_at) ? msg.created_at : nil
103
- else
104
- role = msg["role"] || msg[:role] || "unknown"
105
- content = msg["content"] || msg[:content] || ""
106
- created_at = msg["created_at"] || msg[:created_at]
107
- end
108
-
109
- role_class = case role.to_s
110
- when "user" then "bg-blue-50 border-blue-200"
111
- when "assistant" then "bg-emerald-50 border-emerald-200"
112
- when "tool" then "bg-amber-50 border-amber-200"
113
- when "system" then "bg-slate-50 border-slate-200"
114
- else "bg-slate-50 border-slate-200"
115
- end
116
-
117
- role_icon = case role.to_s
118
- when "user"
119
- '<svg class="w-4 h-4 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/></svg>'.html_safe
120
- when "assistant"
121
- '<svg class="w-4 h-4 text-emerald-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z"/></svg>'.html_safe
122
- when "tool"
123
- '<svg class="w-4 h-4 text-amber-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/></svg>'.html_safe
124
- else
125
- '<svg class="w-4 h-4 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/></svg>'.html_safe
126
- end
127
-
128
- view.concat(content_tag(:div, class: "rounded-lg border p-4 #{role_class}") do
129
- view.concat(content_tag(:div, class: "flex items-center justify-between mb-3") do
130
- view.concat(content_tag(:div, class: "flex items-center gap-2") do
131
- view.concat(role_icon)
132
- view.concat(content_tag(:span, role.to_s.capitalize, class: "text-sm font-medium text-slate-700"))
133
- end)
134
- view.concat(content_tag(:div, class: "flex items-center gap-2 text-xs text-slate-400") do
135
- view.concat(content_tag(:span, created_at.strftime("%H:%M:%S"))) if created_at.respond_to?(:strftime)
136
- view.concat(content_tag(:span, "##{idx + 1}"))
137
- end)
138
- end)
139
-
140
- content_str = content.to_s
141
- if role.to_s == "tool" && content_str.start_with?("{", "[")
142
- begin
143
- parsed = JSON.parse(content_str)
144
- view.concat(view.render_json_block(parsed))
145
- rescue JSON::ParserError
146
- view.concat(content_tag(:div, view.simple_format(h(content_str)), class: "prose prose-sm max-w-none"))
147
- end
148
- else
149
- view.concat(content_tag(:div, view.simple_format(h(content_str)), class: "prose prose-sm max-w-none"))
150
- end
151
- end)
152
- end
153
- end
154
- end
155
- end
156
-
157
- # Verbatim from `BaseHelper#render_tool_args_preview`.
158
- class ToolArgsRenderer < Renderer
159
- def render
160
- LegacyGleania.warn_once(:tool_args_preview)
161
-
162
- args = record.respond_to?(:args) ? record.args : (record.respond_to?(:arguments) ? record.arguments : {})
163
- result = record.respond_to?(:result) ? record.result : nil
164
- error = record.respond_to?(:error) ? record.error : nil
165
-
166
- content_tag(:div, class: "space-y-6") do
167
- view.concat(content_tag(:div) do
168
- view.concat(content_tag(:h4, "Arguments", class: "text-sm font-medium text-slate-500 mb-2"))
169
- if args.present? && args != {}
170
- view.concat(view.render_json_block(args))
171
- else
172
- view.concat(content_tag(:p, "No arguments", class: "text-slate-400 italic text-sm"))
173
- end
174
- end)
175
-
176
- if result.present? && result != {}
177
- view.concat(content_tag(:div, class: "pt-4 border-t border-slate-200") do
178
- view.concat(content_tag(:h4, "Result", class: "text-sm font-medium text-slate-500 mb-2"))
179
- view.concat(view.render_json_block(result))
180
- end)
181
- end
182
-
183
- if error.present?
184
- view.concat(content_tag(:div, class: "pt-4 border-t border-slate-200") do
185
- view.concat(content_tag(:h4, "Error", class: "text-sm font-medium text-red-500 mb-2"))
186
- view.concat(content_tag(:div, class: "bg-red-50 border border-red-200 rounded-lg p-4") do
187
- content_tag(:pre, h(error.to_s), class: "text-sm text-red-700 whitespace-pre-wrap font-mono")
188
- end)
189
- end)
190
- end
191
- end
192
- end
193
- end
194
-
195
- # Verbatim from `BaseHelper#render_turn_messages_preview`.
196
- class TurnMessagesRenderer < Renderer
197
- def render
198
- LegacyGleania.warn_once(:turn_messages_preview)
199
-
200
- user_msg = record.respond_to?(:user_message) ? record.user_message : nil
201
- asst_msg = record.respond_to?(:assistant_message) ? record.assistant_message : nil
202
-
203
- content_tag(:div, class: "space-y-4") do
204
- if user_msg
205
- view.concat(content_tag(:div, class: "rounded-lg border p-4 bg-blue-50 border-blue-200") do
206
- view.concat(content_tag(:div, class: "flex items-center gap-2 mb-2") do
207
- view.concat('<svg class="w-4 h-4 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/></svg>'.html_safe)
208
- view.concat(content_tag(:span, "User", class: "text-sm font-medium text-slate-700"))
209
- end)
210
- view.concat(content_tag(:div, view.simple_format(h(user_msg.respond_to?(:content) ? user_msg.content.to_s : user_msg.to_s)), class: "prose prose-sm max-w-none"))
211
- end)
212
- end
213
-
214
- if asst_msg
215
- view.concat(content_tag(:div, class: "rounded-lg border p-4 bg-emerald-50 border-emerald-200") do
216
- view.concat(content_tag(:div, class: "flex items-center gap-2 mb-2") do
217
- view.concat('<svg class="w-4 h-4 text-emerald-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z"/></svg>'.html_safe)
218
- view.concat(content_tag(:span, "Assistant", class: "text-sm font-medium text-slate-700"))
219
- end)
220
- view.concat(content_tag(:div, view.simple_format(h(asst_msg.respond_to?(:content) ? asst_msg.content.to_s : asst_msg.to_s)), class: "prose prose-sm max-w-none"))
221
- end)
222
- end
223
-
224
- view.concat(content_tag(:p, "No messages found", class: "text-slate-400 italic text-sm")) unless user_msg || asst_msg
225
- end
226
- end
227
- end
228
- end
229
- end
230
- end
@@ -1,35 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "test_helper"
4
-
5
- module AdminSuite
6
- class LegacyRendererDeprecationTest < ActionView::TestCase
7
- include ::ERB::Util
8
- include AdminSuite::BaseHelper
9
-
10
- Prompt = Struct.new(:prompt_template)
11
-
12
- setup { AdminSuite::Renderers::LegacyGleania.reset_deprecation_notices! }
13
-
14
- test "the legacy prompt renderer still renders its template" do
15
- html = render_custom_section(Prompt.new("Hello {{name}}"), :prompt_template_preview)
16
- assert_includes html, "Hello"
17
- assert_includes html, "name"
18
- end
19
-
20
- test "using a legacy renderer logs a deprecation once per key" do
21
- logged = []
22
- AdminSuite::Renderers::LegacyGleania.stub(:warn_once_sink, ->(msg) { logged << msg }) do
23
- 2.times { render_custom_section(Prompt.new("x"), :prompt_template_preview) }
24
- end
25
- assert_equal 1, logged.size
26
- assert_includes logged.first, "deprecated"
27
- assert_includes logged.first, "0.5.0"
28
- end
29
-
30
- test "the gem no longer includes the host CustomRenderersHelper constant" do
31
- source = AdminSuite::Engine.root.join("app/helpers/admin_suite/base_helper.rb").read
32
- refute_includes source, "Internal::Developer::CustomRenderersHelper"
33
- end
34
- end
35
- end
@@ -1,39 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "test_helper"
4
-
5
- module AdminSuite
6
- class ResourceExportableDeprecationTest < ActiveSupport::TestCase
7
- # Regression fixture for Finding 1 of the whole-branch review: Task 8
8
- # removed `exportable` after grepping the *gem* for readers, but host
9
- # apps are *callers* of this DSL (gleania: 30 resource files;
10
- # trust_growth: 1). A real removal raises `NoMethodError` at
11
- # definition-load time -- and in production `DefinitionLoader` logs and
12
- # swallows that, so the resource silently vanishes from the admin.
13
- class LegacyExportableResource < Admin::Base::Resource
14
- exportable :json, :csv
15
- end
16
-
17
- setup { LegacyExportableResource.reset_deprecation_notices! }
18
-
19
- test "calling exportable in a resource body is harmless" do
20
- assert_nothing_raised { LegacyExportableResource.exportable(:json, :csv) }
21
- end
22
-
23
- test "exportable does not restore any export-format reader" do
24
- refute LegacyExportableResource.respond_to?(:export_formats)
25
- refute LegacyExportableResource.instance_variable_defined?(:@export_formats)
26
- end
27
-
28
- test "exportable warns once per resource class, naming the class and the 0.5.0 removal" do
29
- logged = []
30
- LegacyExportableResource.stub(:warn_once_sink, ->(msg) { logged << msg }) do
31
- 2.times { LegacyExportableResource.exportable(:json, :csv) }
32
- end
33
-
34
- assert_equal 1, logged.size
35
- assert_includes logged.first, "LegacyExportableResource"
36
- assert_includes logged.first, "0.5.0"
37
- end
38
- end
39
- end