activeadmin_favorites 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 (59) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +17 -0
  3. data/CODE_OF_CONDUCT.md +31 -0
  4. data/CONTRIBUTING.md +29 -0
  5. data/GOVERNANCE.md +29 -0
  6. data/LICENSE.md +21 -0
  7. data/README.md +146 -0
  8. data/SECURITY.md +27 -0
  9. data/activeadmin_favorites.gemspec +68 -0
  10. data/app/assets/controllers/activeadmin_favorites/personalization_controller.js +199 -0
  11. data/app/models/active_admin/favorites/application_record.rb +9 -0
  12. data/app/models/active_admin/favorites/combined_favorite.rb +9 -0
  13. data/app/models/active_admin/favorites/favorite/macro_builder.rb +43 -0
  14. data/app/models/active_admin/favorites/favorite/macro_catalog.rb +118 -0
  15. data/app/models/active_admin/favorites/favorite/macro_resolver.rb +86 -0
  16. data/app/models/active_admin/favorites/favorite/macro_validator.rb +87 -0
  17. data/app/models/active_admin/favorites/favorite/query_params.rb +41 -0
  18. data/app/models/active_admin/favorites/favorite/range_detector.rb +54 -0
  19. data/app/models/active_admin/favorites/favorite.rb +339 -0
  20. data/app/models/active_admin/favorites/filters_favorite.rb +8 -0
  21. data/app/models/active_admin/favorites/lens_favorite.rb +9 -0
  22. data/app/models/active_admin/favorites/view_lens/layout.rb +86 -0
  23. data/app/models/active_admin/favorites/view_lens_default.rb +40 -0
  24. data/app/models/active_admin/favorites.rb +6 -0
  25. data/app/views/active_admin/favorites/_lens_catalog_data.html.erb +9 -0
  26. data/app/views/active_admin/favorites/_macro_fields.html.erb +53 -0
  27. data/app/views/active_admin/favorites/_personalization_trigger.html.erb +69 -0
  28. data/app/views/active_admin/resource/index.html.arb +99 -0
  29. data/app/views/active_admin/resource/show.html.arb +13 -0
  30. data/config/locales/activeadmin_favorites.en.yml +60 -0
  31. data/config/locales/activeadmin_favorites.host.yml.example +17 -0
  32. data/config/polyrun_coverage.yml +8 -0
  33. data/db/migrate/20260709100000_create_active_admin_view_lens_defaults.rb +24 -0
  34. data/db/migrate/20260709130000_create_admin_favorites.rb +33 -0
  35. data/lib/activeadmin/favorites/configuration.rb +31 -0
  36. data/lib/activeadmin/favorites/engine.rb +75 -0
  37. data/lib/activeadmin/favorites/hash_coercion.rb +34 -0
  38. data/lib/activeadmin/favorites/install.rb +121 -0
  39. data/lib/activeadmin/favorites/migration_helpers.rb +34 -0
  40. data/lib/activeadmin/favorites/migration_support.rb +19 -0
  41. data/lib/activeadmin/favorites/register_favorites.rb +272 -0
  42. data/lib/activeadmin/favorites/register_view_lens_preferences.rb +98 -0
  43. data/lib/activeadmin/favorites/user_record.rb +26 -0
  44. data/lib/activeadmin/favorites/version.rb +7 -0
  45. data/lib/activeadmin/favorites/view_lens/applicator.rb +150 -0
  46. data/lib/activeadmin/favorites/view_lens/catalog.rb +108 -0
  47. data/lib/activeadmin/favorites/view_lens/current.rb +28 -0
  48. data/lib/activeadmin/favorites/view_lens/patches.rb +95 -0
  49. data/lib/activeadmin/favorites/view_lens/resource_dsl.rb +44 -0
  50. data/lib/activeadmin/favorites/view_lens.rb +8 -0
  51. data/lib/activeadmin/favorites.rb +30 -0
  52. data/lib/activeadmin_favorites.rb +5 -0
  53. data/lib/generators/activeadmin_favorites/install/templates/favorite_policy.rb +56 -0
  54. data/lib/generators/activeadmin_favorites/install/templates/initializer.rb +16 -0
  55. data/lib/generators/activeadmin_favorites/install/templates/locale.en.yml +60 -0
  56. data/lib/generators/activeadmin_favorites/install_generator.rb +37 -0
  57. data/lib/tasks/activeadmin_favorites_tasks.rake +39 -0
  58. data/sig/activeadmin/favorites.rbs +5 -0
  59. metadata +418 -0
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAdmin
4
+ module Favorites
5
+ module ViewLens
6
+ module TableForPatch
7
+ def column(*args, &block)
8
+ layout = Applicator.current_layout
9
+ resource_key = helpers.active_admin_config&.resource_name&.route_key
10
+ column_id = Applicator.extract_column_id(args, resource_key: resource_key)
11
+
12
+ if resource_key.present? && column_id.present?
13
+ label = Applicator.column_label_from_args(args, column_id: column_id)
14
+ Applicator.register_column(resource_key: resource_key, id: column_id, label: label)
15
+ end
16
+
17
+ return if layout&.hidden_column?(column_id)
18
+
19
+ super
20
+ end
21
+ end
22
+
23
+ module AttributesTablePatch
24
+ def row(*args, &block)
25
+ layout = Applicator.current_layout
26
+ title = args[0]
27
+ options = args.last.is_a?(Hash) ? args.last : {}
28
+ row_id = Applicator.extract_row_id(title, options)
29
+ resource_key = helpers.active_admin_config&.resource_name&.route_key
30
+
31
+ if resource_key.present? && row_id.present?
32
+ Applicator.register_row(resource_key: resource_key, id: row_id, label: title.to_s)
33
+ end
34
+
35
+ return if layout&.hidden_show_row?(row_id)
36
+
37
+ super
38
+ end
39
+ end
40
+
41
+ module PanelPatch
42
+ def build(title, attributes = {})
43
+ layout = Applicator.current_layout
44
+ panel_id = Applicator.extract_panel_id(title, attributes)
45
+ resource_key = helpers.active_admin_config&.resource_name&.route_key
46
+
47
+ if resource_key.present? && panel_id.present?
48
+ Applicator.register_panel(resource_key: resource_key, id: panel_id, label: title.to_s)
49
+ end
50
+
51
+ return if layout&.hidden_panel?(panel_id)
52
+
53
+ attributes = attributes.merge("data-lens-panel": panel_id)
54
+ super
55
+ end
56
+ end
57
+
58
+ module FiltersPatch
59
+ private
60
+
61
+ def filter_lookup
62
+ filters = super
63
+ layout = Applicator.current_layout
64
+ return filters if filters.nil?
65
+ return filters unless layout.respond_to?(:hidden_filter?)
66
+
67
+ filters.reject { |filter_key, _| layout.hidden_filter?(filter_key) }
68
+ end
69
+ end
70
+
71
+ module ActionItemsPatch
72
+ def action_items_for(action, render_context = nil)
73
+ items = super
74
+ layout = Applicator.current_layout
75
+ return items if items.nil?
76
+ return items unless layout.respond_to?(:hidden_action_item?)
77
+
78
+ items.reject { |item| layout.hidden_action_item?(item.name) }
79
+ end
80
+ end
81
+
82
+ module InstallPatches
83
+ module_function
84
+
85
+ def call
86
+ ActiveAdmin::Views::TableFor.prepend(TableForPatch) unless ActiveAdmin::Views::TableFor < TableForPatch
87
+ ActiveAdmin::Views::AttributesTable.prepend(AttributesTablePatch) unless ActiveAdmin::Views::AttributesTable < AttributesTablePatch
88
+ ActiveAdmin::Views::Panel.prepend(PanelPatch) unless ActiveAdmin::Views::Panel < PanelPatch
89
+ ActiveAdmin::Filters::ResourceExtension.prepend(FiltersPatch) unless ActiveAdmin::Filters::ResourceExtension < FiltersPatch
90
+ ActiveAdmin::Resource::ActionItems.prepend(ActionItemsPatch) unless ActiveAdmin::Resource::ActionItems < ActionItemsPatch
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAdmin
4
+ module Favorites
5
+ module ViewLens
6
+ module ControllerMethods
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ before_action :apply_active_admin_favorites_lens, only: [:index, :show]
11
+ after_action :clear_active_admin_favorites_lens, only: [:index, :show]
12
+
13
+ helper_method :active_admin_favorites_catalog, :active_admin_favorites_layout
14
+ end
15
+
16
+ def clear_active_admin_favorites_lens
17
+ ViewLens::Applicator.reset!
18
+ end
19
+
20
+ def apply_active_admin_favorites_lens
21
+ return if active_admin_config.resource_class == Favorite
22
+
23
+ ViewLens::Applicator.resolve_layout(controller: self)
24
+ end
25
+
26
+ def active_admin_favorites_catalog
27
+ context = ViewLens::Current.catalog_context
28
+ return ViewLens::Applicator.current_catalog if ViewLens::Applicator.current_catalog.present?
29
+ return [] unless context
30
+
31
+ ViewLens::Applicator.build_catalog(
32
+ active_admin_config: context[:active_admin_config],
33
+ action: context[:action],
34
+ render_context: context[:render_context]
35
+ )
36
+ end
37
+
38
+ def active_admin_favorites_layout
39
+ ViewLens::Applicator.current_layout
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAdmin
4
+ module Favorites
5
+ module ViewLens
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "activeadmin/favorites/version"
4
+ require "activeadmin/favorites/configuration"
5
+
6
+ module ActiveAdmin
7
+ module Favorites
8
+ class << self
9
+ def config
10
+ @config ||= Configuration.new
11
+ end
12
+
13
+ def configure
14
+ yield config
15
+ end
16
+
17
+ def install!
18
+ require "activeadmin/favorites/install"
19
+ Install.call
20
+ end
21
+
22
+ def register_resources!
23
+ require "activeadmin/favorites/register_favorites"
24
+ require "activeadmin/favorites/register_view_lens_preferences"
25
+ RegisterFavorites.call
26
+ RegisterViewLensPreferences.call
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "activeadmin/favorites/version"
4
+ require "activeadmin/favorites"
5
+ require "activeadmin/favorites/engine"
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAdmin
4
+ module Favorites
5
+ class FavoritePolicy < ApplicationPolicy
6
+ def index?
7
+ staff?
8
+ end
9
+
10
+ def show?
11
+ visible?
12
+ end
13
+
14
+ def create?
15
+ staff?
16
+ end
17
+
18
+ def new?
19
+ create?
20
+ end
21
+
22
+ def update?
23
+ owner?
24
+ end
25
+
26
+ def edit?
27
+ update?
28
+ end
29
+
30
+ def destroy?
31
+ owner?
32
+ end
33
+
34
+ default_scope do |relation|
35
+ return relation.none unless staff?
36
+
37
+ relation.visible_to(user)
38
+ end
39
+
40
+ private
41
+
42
+ def staff?
43
+ user.present?
44
+ end
45
+
46
+ def visible?
47
+ staff? && (owner? || record.published?)
48
+ end
49
+
50
+ def owner?
51
+ foreign_key = ActiveAdmin::Favorites.config.user_foreign_key
52
+ staff? && record.public_send(foreign_key) == user.id
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ ActiveAdmin::Favorites.configure do |config|
4
+ # Example for a custom staff model:
5
+ # config.user_class = "AdminUser"
6
+ # config.user_foreign_key = :admin_user_id
7
+ # config.user_association_name = :admin_user
8
+ # config.user_label_method = :email
9
+ # config.current_user_method = :current_admin_user
10
+
11
+ config.user_class = "User"
12
+ config.user_foreign_key = :user_id
13
+ config.user_association_name = :user
14
+ config.user_label_method = :email
15
+ config.current_user_method = :current_user
16
+ end
@@ -0,0 +1,60 @@
1
+ en:
2
+ active_admin:
3
+ favorites:
4
+ add_favorite: Add to favorites
5
+ advanced_macros: Advanced macros
6
+ created: Favorite saved
7
+ creator_column: Creator
8
+ edit_favorite: Edit favorite
9
+ filters_column: Filters
10
+ form_panel: Favorite details
11
+ macros_hint: Choose shortcut templates to keep date filters up to date when you open this favorite.
12
+ macros_json_hint: JSON array of macro objects. Use only when templates are not enough.
13
+ macros_json_invalid: Macros JSON is invalid
14
+ macros_panel: Dynamic filters
15
+ menu: Favorites
16
+ no_detected_ranges: No date range filters detected in the current view. Use advanced macros if needed.
17
+ open: Open
18
+ published: Published for everyone
19
+ updated: Favorite updated
20
+ use_advanced_macros: Edit macros as JSON
21
+ use_dynamic_range: Use dynamic range
22
+ visibility:
23
+ mine: Only me
24
+ published: "Published (%{time})"
25
+ visibility_column: Visibility
26
+ macro_templates:
27
+ previous_calendar_month: Previous calendar month
28
+ current_calendar_month: Current calendar month
29
+ previous_calendar_day: Previous day
30
+ current_calendar_day: Today
31
+ rolling_last_7_days: Last 7 days
32
+ rolling_last_30_days: Last 30 days
33
+ previous_fiscal_year: Previous fiscal year (Apr–Mar)
34
+ current_fiscal_year_to_date: Current fiscal year to date (Apr–Mar)
35
+ personalize_page: Personalize page
36
+ personalization_region: Page personalization
37
+ save_default: Save as my default
38
+ reset_default: Reset to defaults
39
+ save_lens_favorite: Save lens as favorite
40
+ include_layout: Include current page layout
41
+ lens_summary: Page layout
42
+ lens_created: Lens favorite saved
43
+ combined_created: Combined favorite saved
44
+ layout_saved: Page layout saved
45
+ layout_reset: Page layout reset
46
+ kind_column: Type
47
+ kind_navigation: Favorite types
48
+ no_personalization_items: No personalization options are available on this page.
49
+ kinds:
50
+ all: All
51
+ filters: Filters
52
+ lens: Lens
53
+ combined: Combined
54
+ groups:
55
+ columns: Columns
56
+ filters: Filters
57
+ action_items: Actions
58
+ panels: Panels
59
+ show_rows: Rows
60
+ block_column: Custom column %{position}
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/base"
4
+
5
+ module ActiveadminFavorites
6
+ class InstallGenerator < Rails::Generators::Base
7
+ source_root File.expand_path("install/templates", __dir__)
8
+
9
+ desc "Install activeadmin_favorites initializer, policy, and locale template"
10
+
11
+ def copy_initializer
12
+ template "initializer.rb", "config/initializers/activeadmin_favorites.rb"
13
+ end
14
+
15
+ def copy_policy
16
+ template "favorite_policy.rb", "app/policies/active_admin/favorites/favorite_policy.rb"
17
+ end
18
+
19
+ def copy_locale_template
20
+ template "locale.en.yml", "config/locales/activeadmin_favorites.en.yml"
21
+ end
22
+
23
+ def install_migrations
24
+ rake "activeadmin_favorites:install:migrations"
25
+ end
26
+
27
+ def show_page_policy_note
28
+ say <<~NOTE, :yellow
29
+
30
+ Add save_lens_favorite? and reset? to your ActiveAdmin::PagePolicy (or equivalent)
31
+ so view lens preference page actions authorize correctly.
32
+
33
+ Copy active_admin.favorites keys into your fi/sv locale files if needed.
34
+ NOTE
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :activeadmin_favorites do
4
+ namespace :install do
5
+ desc "Copy activeadmin_favorites migrations into db/migrate"
6
+ task migrations: :environment do
7
+ require "fileutils"
8
+ require "activeadmin/favorites/migration_helpers"
9
+
10
+ destination = Rails.root.join("db/migrate")
11
+ FileUtils.mkdir_p(destination)
12
+
13
+ copied = []
14
+ skipped = []
15
+
16
+ ActiveAdmin::Favorites::MigrationHelpers.engine_migration_paths.each do |source|
17
+ target = destination.join(File.basename(source))
18
+ version = File.basename(source).split("_", 2).first
19
+
20
+ if target.exist?
21
+ skipped << version
22
+ next
23
+ end
24
+
25
+ FileUtils.cp(source, target)
26
+ copied << version
27
+ end
28
+
29
+ if copied.any?
30
+ puts "Copied migrations: #{copied.join(", ")}"
31
+ else
32
+ puts "No new migrations copied."
33
+ end
34
+
35
+ puts "Skipped existing versions: #{skipped.join(", ")}" if skipped.any?
36
+ puts "Run bin/rails db:migrate when ready."
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,5 @@
1
+ module ActiveAdmin
2
+ module Favorites
3
+ VERSION: String
4
+ end
5
+ end