has_helpers 5.0.2

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 (748) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +38 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +10 -0
  5. data/.ruby-gemset +1 -0
  6. data/.ruby-version +1 -0
  7. data/.tekton/Chart.yaml +14 -0
  8. data/.tekton/templates/pipelinerun.yaml +44 -0
  9. data/.tekton/values-main.yaml +53 -0
  10. data/.tekton/values-pr.yaml +40 -0
  11. data/.tekton/values.yaml +17 -0
  12. data/.tool-versions +1 -0
  13. data/CHANGELOG.md +204 -0
  14. data/Gemfile +25 -0
  15. data/README.md +76 -0
  16. data/Rakefile +46 -0
  17. data/app/api/has_helpers/rack_api.rb +116 -0
  18. data/app/api/has_helpers/search_api.rb +173 -0
  19. data/app/assets/stylesheets/has_helpers/base.scss +5 -0
  20. data/app/assets/stylesheets/has_helpers/color_palette.scss +33 -0
  21. data/app/assets/stylesheets/has_helpers/color_palettes/default.scss +11 -0
  22. data/app/assets/stylesheets/has_helpers/components/dataset.scss +43 -0
  23. data/app/assets/stylesheets/has_helpers/mixins.scss +149 -0
  24. data/app/commands/has_helpers/cards/available_cards_command.rb +98 -0
  25. data/app/commands/has_helpers/cards/card_data_command.rb +1062 -0
  26. data/app/commands/reports/base_options.rb +79 -0
  27. data/app/commands/reports/belongs_to_options.rb +178 -0
  28. data/app/commands/reports/constant_options.rb +66 -0
  29. data/app/commands/reports/external_graphql_client.rb +116 -0
  30. data/app/commands/reports/fill_dates.rb +65 -0
  31. data/app/commands/reports/filter_option_generator.rb +186 -0
  32. data/app/commands/reports/generator.rb +310 -0
  33. data/app/commands/reports/has_many_options.rb +105 -0
  34. data/app/commands/reports/importer.rb +132 -0
  35. data/app/commands/reports/nested_attribute_options.rb +134 -0
  36. data/app/constants/color.rb +9 -0
  37. data/app/constants/file_format.rb +12 -0
  38. data/app/constants/has_helpers/cards/card_custom_field.rb +12 -0
  39. data/app/constants/has_helpers/cards/card_data_type.rb +11 -0
  40. data/app/constants/has_helpers/cards/card_identifier.rb +10 -0
  41. data/app/constants/has_helpers/cards/card_layout_type.rb +11 -0
  42. data/app/constants/has_helpers/cards/card_variant_type.rb +12 -0
  43. data/app/constants/has_helpers/link_type.rb +10 -0
  44. data/app/constants/has_helpers/notification_alert_status.rb +14 -0
  45. data/app/constants/has_helpers/notification_alert_type.rb +10 -0
  46. data/app/constants/has_helpers/operation.rb +11 -0
  47. data/app/constants/has_helpers/resource_type.rb +25 -0
  48. data/app/constants/icon.rb +22 -0
  49. data/app/constants/logical_operator.rb +7 -0
  50. data/app/constants/option.rb +10 -0
  51. data/app/constants/time_zone.rb +12 -0
  52. data/app/constructors/has_helpers/base_constructor.rb +62 -0
  53. data/app/controllers/concerns/has_helpers/service_trust/authenticated_as_service.rb +51 -0
  54. data/app/controllers/concerns/has_helpers/service_trust/graphql_authentication.rb +57 -0
  55. data/app/controllers/has_helpers/controller.rb +63 -0
  56. data/app/controllers/has_helpers/factory_controller.rb +261 -0
  57. data/app/controllers/has_helpers/graphql_extensions.rb +83 -0
  58. data/app/controllers/has_helpers/health_check_controller.rb +46 -0
  59. data/app/controllers/has_helpers/search_extensions.rb +92 -0
  60. data/app/controllers/has_helpers/service_trust/jwks_controller.rb +20 -0
  61. data/app/controllers/has_helpers/xlsx_export.rb +115 -0
  62. data/app/controllers/search_extensions.rb +5 -0
  63. data/app/graphql/has_helpers/base_type.rb +13 -0
  64. data/app/graphql/has_helpers/base_union.rb +9 -0
  65. data/app/graphql/has_helpers/enums/resource_enum.rb +24 -0
  66. data/app/graphql/has_helpers/middleware/application_access.rb +26 -0
  67. data/app/graphql/has_helpers/mutations/base.rb +27 -0
  68. data/app/graphql/has_helpers/mutations/external_data_sync.rb +72 -0
  69. data/app/graphql/has_helpers/mutations/mark_individual_alert_read.rb +31 -0
  70. data/app/graphql/has_helpers/mutations/mark_notifications_read.rb +50 -0
  71. data/app/graphql/has_helpers/mutations/toggle_favorite.rb +39 -0
  72. data/app/graphql/has_helpers/resolvers/app_nav.rb +107 -0
  73. data/app/graphql/has_helpers/resolvers/base.rb +27 -0
  74. data/app/graphql/has_helpers/resolvers/cards/available_cards.rb +31 -0
  75. data/app/graphql/has_helpers/resolvers/cards/card_data.rb +74 -0
  76. data/app/graphql/has_helpers/resolvers/cards/hover_card_data.rb +153 -0
  77. data/app/graphql/has_helpers/resolvers/favorite.rb +62 -0
  78. data/app/graphql/has_helpers/resolvers/internal/roles.rb +54 -0
  79. data/app/graphql/has_helpers/resolvers/is_favorite.rb +24 -0
  80. data/app/graphql/has_helpers/resolvers/list_notification_alerts.rb +47 -0
  81. data/app/graphql/has_helpers/resolvers/lookup.rb +36 -0
  82. data/app/graphql/has_helpers/resolvers/recently_viewed.rb +54 -0
  83. data/app/graphql/has_helpers/resolvers/resource_tab.rb +22 -0
  84. data/app/graphql/has_helpers/resolvers/search.rb +18 -0
  85. data/app/graphql/has_helpers/resolvers/search_query.rb +38 -0
  86. data/app/graphql/has_helpers/resolvers/user_application_order.rb +35 -0
  87. data/app/graphql/has_helpers/resources/organization_option_resource.rb +28 -0
  88. data/app/graphql/has_helpers/resources/organization_resource_resource.rb +18 -0
  89. data/app/graphql/has_helpers/resources/resource_group_resource.rb +10 -0
  90. data/app/graphql/has_helpers/resources/resource_resource.rb +21 -0
  91. data/app/graphql/has_helpers/resources/user_role_resource.rb +9 -0
  92. data/app/graphql/has_helpers/types/application_order_type.rb +18 -0
  93. data/app/graphql/has_helpers/types/cards/available_card_type.rb +20 -0
  94. data/app/graphql/has_helpers/types/cards/card_badge_type.rb +12 -0
  95. data/app/graphql/has_helpers/types/cards/card_config_type.rb +18 -0
  96. data/app/graphql/has_helpers/types/cards/card_data_type.rb +13 -0
  97. data/app/graphql/has_helpers/types/cards/card_entry_field_type.rb +21 -0
  98. data/app/graphql/has_helpers/types/cards/card_entry_type.rb +12 -0
  99. data/app/graphql/has_helpers/types/cards/card_field_config_type.rb +17 -0
  100. data/app/graphql/has_helpers/types/layouts/app_nav/link_section_type.rb +17 -0
  101. data/app/graphql/has_helpers/types/layouts/app_nav/link_type.rb +20 -0
  102. data/app/graphql/has_helpers/types/layouts/app_nav/sub_link_type.rb +17 -0
  103. data/app/graphql/has_helpers/types/layouts/app_nav/type.rb +16 -0
  104. data/app/graphql/has_helpers/types/lookup.rb +16 -0
  105. data/app/graphql/has_helpers/types/resource_tab/type.rb +15 -0
  106. data/app/graphql/has_helpers/types/role_type.rb +13 -0
  107. data/app/graphql/has_helpers/types/search/detail_badge_type.rb +15 -0
  108. data/app/graphql/has_helpers/types/search/detail_item_type.rb +14 -0
  109. data/app/graphql/has_helpers/types/search/detail_type.rb +16 -0
  110. data/app/graphql/has_helpers/types/search/filter_operation_enum.rb +19 -0
  111. data/app/graphql/has_helpers/types/search/input_type.rb +16 -0
  112. data/app/graphql/has_helpers/types/search/result_type.rb +72 -0
  113. data/app/graphql/has_helpers/types/search/search_filter_input_type.rb +17 -0
  114. data/app/graphql/has_helpers/types/search/type.rb +17 -0
  115. data/app/importers/has_helpers/cards/importer.rb +1223 -0
  116. data/app/lib/has_helpers/application_helper.rb +21 -0
  117. data/app/lib/has_helpers/dn/processor.rb +55 -0
  118. data/app/lib/has_helpers/dn/refresh.rb +13 -0
  119. data/app/lib/has_helpers/dn/utils/backfill_utils.rb +80 -0
  120. data/app/lib/has_helpers/dn/utils/query_utils.rb +216 -0
  121. data/app/lib/has_helpers/feature.rb +88 -0
  122. data/app/lib/has_helpers/hq_exception.rb +18 -0
  123. data/app/lib/has_helpers/index.rb +890 -0
  124. data/app/lib/has_helpers/organization_merge.rb +128 -0
  125. data/app/lib/has_helpers/query/elasticsearch.rb +20 -0
  126. data/app/lib/has_helpers/query/provider.rb +105 -0
  127. data/app/lib/has_helpers/query/sql.rb +133 -0
  128. data/app/lib/has_helpers/query.rb +14 -0
  129. data/app/lib/has_helpers/search/elasticsearch.rb +41 -0
  130. data/app/lib/has_helpers/search/elasticsearch_shared.rb +70 -0
  131. data/app/lib/has_helpers/search/provider.rb +149 -0
  132. data/app/lib/has_helpers/search/search_utils.rb +74 -0
  133. data/app/lib/has_helpers/search/sql_search.rb +219 -0
  134. data/app/lib/has_helpers/search.rb +46 -0
  135. data/app/models/ability.rb +233 -0
  136. data/app/models/has_helpers/application.rb +47 -0
  137. data/app/models/has_helpers/application_record.rb +7 -0
  138. data/app/models/has_helpers/cards/card_builder/base.rb +74 -0
  139. data/app/models/has_helpers/cards/card_builder/card_definition_builder.rb +486 -0
  140. data/app/models/has_helpers/cards/card_builder/definition_context.rb +82 -0
  141. data/app/models/has_helpers/cards/card_builder/field_builder.rb +142 -0
  142. data/app/models/has_helpers/cards/card_builder/resource_builder.rb +34 -0
  143. data/app/models/has_helpers/cards/card_definition.rb +41 -0
  144. data/app/models/has_helpers/cards/card_definition_resource.rb +19 -0
  145. data/app/models/has_helpers/cards/card_field.rb +39 -0
  146. data/app/models/has_helpers/cards/registry.rb +115 -0
  147. data/app/models/has_helpers/cards/role_card_field.rb +28 -0
  148. data/app/models/has_helpers/cards/role_resource_card.rb +41 -0
  149. data/app/models/has_helpers/dn_utils.rb +61 -0
  150. data/app/models/has_helpers/favorite.rb +20 -0
  151. data/app/models/has_helpers/master_record.rb +18 -0
  152. data/app/models/has_helpers/notification_alert.rb +52 -0
  153. data/app/models/has_helpers/organization.rb +16 -0
  154. data/app/models/has_helpers/organization_link.rb +92 -0
  155. data/app/models/has_helpers/organization_methods.rb +62 -0
  156. data/app/models/has_helpers/organization_option.rb +31 -0
  157. data/app/models/has_helpers/organization_resource.rb +45 -0
  158. data/app/models/has_helpers/pending_db_operation.rb +31 -0
  159. data/app/models/has_helpers/recently_viewed.rb +34 -0
  160. data/app/models/has_helpers/reindex_queue.rb +43 -0
  161. data/app/models/has_helpers/resource.rb +38 -0
  162. data/app/models/has_helpers/resource_group.rb +15 -0
  163. data/app/models/has_helpers/restriction.rb +15 -0
  164. data/app/models/has_helpers/restriction_operation.rb +13 -0
  165. data/app/models/has_helpers/role.rb +20 -0
  166. data/app/models/has_helpers/service_trust/audit_log.rb +20 -0
  167. data/app/models/has_helpers/service_trust/key.rb +56 -0
  168. data/app/models/has_helpers/stat.rb +13 -0
  169. data/app/models/has_helpers/user.rb +18 -0
  170. data/app/models/has_helpers/user_methods.rb +184 -0
  171. data/app/models/has_helpers/user_organization.rb +33 -0
  172. data/app/models/has_helpers/user_role.rb +27 -0
  173. data/app/models/has_helpers/user_session.rb +19 -0
  174. data/app/presenters/has_helpers/barcode_base_presenter.rb +113 -0
  175. data/app/presenters/has_helpers/base_presenter.rb +100 -0
  176. data/app/presenters/has_helpers/cacheable_item.rb +28 -0
  177. data/app/presenters/has_helpers/item_presenter.rb +52 -0
  178. data/app/presenters/has_helpers/items_presenter.rb +33 -0
  179. data/app/presenters/has_helpers/resource/action_presenter.rb +8 -0
  180. data/app/presenters/has_helpers/resource/cell_presenter.rb +52 -0
  181. data/app/presenters/has_helpers/resource/email_presenter.rb +34 -0
  182. data/app/presenters/has_helpers/resource/form_presenter.rb +76 -0
  183. data/app/presenters/has_helpers/resource/info_collection_presenter.rb +33 -0
  184. data/app/presenters/has_helpers/resource/info_presenter.rb +34 -0
  185. data/app/presenters/has_helpers/resource/list_presenter.rb +120 -0
  186. data/app/presenters/has_helpers/resource/note_presenter.rb +14 -0
  187. data/app/presenters/has_helpers/resource/row_presenter.rb +25 -0
  188. data/app/presenters/has_helpers/resource/tag_presenter.rb +31 -0
  189. data/app/presenters/has_helpers/search_presenter.rb +230 -0
  190. data/app/triggers/has_helpers/erase_denormalized_belongs_to_column_trigger.rb +85 -0
  191. data/app/triggers/has_helpers/limit_favorites.rb +97 -0
  192. data/app/utils/has_helpers/redis_configurable.rb +79 -0
  193. data/app/utils/has_helpers/sync_utils.rb +234 -0
  194. data/app/views/has_helpers/actions/_action.html.erb +31 -0
  195. data/app/views/has_helpers/alerts/_alert.html.erb +3 -0
  196. data/app/views/has_helpers/barcodes.html.erb +36 -0
  197. data/app/views/has_helpers/containers/_container.html.erb +3 -0
  198. data/app/views/has_helpers/email_notifier/system.html.erb +8 -0
  199. data/app/views/has_helpers/email_notifier/system.text.erb +7 -0
  200. data/app/views/has_helpers/enumerables/_enumerable.html.erb +1 -0
  201. data/app/views/has_helpers/icons/_icon.html.erb +1 -0
  202. data/app/views/has_helpers/render_objects/_render_object.html.erb +1 -0
  203. data/app/views/has_helpers/services/_service.html.erb +1 -0
  204. data/app/views/has_helpers/text_nodes/_text_node.html.erb +4 -0
  205. data/app/views/has_helpers/ui_attributes/_html_attrs.html.erb +1 -0
  206. data/app/workers/elasticsearch_process_queue.rb +11 -0
  207. data/app/workers/elasticsearch_reindex.rb +11 -0
  208. data/app/workers/elasticsearch_reindex_scheduler.rb +35 -0
  209. data/app/workers/good_job/configurable_backfill.rb +64 -0
  210. data/app/workers/good_job/cron_denormalization_stats_manager.rb +39 -0
  211. data/app/workers/good_job/denormalization_stats_worker.rb +57 -0
  212. data/app/workers/good_job/dn_update_worker.rb +30 -0
  213. data/app/workers/good_job/dn_worker.rb +47 -0
  214. data/app/workers/good_job/elasticsearch_old_index_cleaner_worker.rb +113 -0
  215. data/app/workers/good_job/elasticsearch_on_finish_reindex.rb +26 -0
  216. data/app/workers/good_job/elasticsearch_process_batch.rb +78 -0
  217. data/app/workers/good_job/elasticsearch_process_change_logs_master_worker.rb +27 -0
  218. data/app/workers/good_job/elasticsearch_process_change_logs_worker.rb +155 -0
  219. data/app/workers/good_job/elasticsearch_process_queue.rb +50 -0
  220. data/app/workers/good_job/elasticsearch_reindex.rb +31 -0
  221. data/app/workers/has_helpers/cards/orphan_card_sweeper.rb +28 -0
  222. data/app/workers/has_helpers/db_operations/clean_stats_worker.rb +42 -0
  223. data/app/workers/has_helpers/db_operations/helper_worker.rb +131 -0
  224. data/app/workers/has_helpers/db_operations/main_worker.rb +164 -0
  225. data/app/workers/has_helpers/db_operations/operations.rb +134 -0
  226. data/app/workers/has_helpers/db_operations/update_operations_stats_resume_worker.rb +35 -0
  227. data/app/workers/has_helpers/default_worker.rb +17 -0
  228. data/app/workers/has_helpers/good_job/notification_alert_cleanup_worker.rb +43 -0
  229. data/app/workers/has_helpers/service_trust/audit_log_cleanup_worker.rb +19 -0
  230. data/app/workers/sidekiq/elasticsearch_old_index_cleaner_worker.rb +112 -0
  231. data/app/workers/sidekiq/elasticsearch_process_batch.rb +87 -0
  232. data/app/workers/sidekiq/elasticsearch_process_queue.rb +40 -0
  233. data/app/workers/sidekiq/elasticsearch_reindex.rb +53 -0
  234. data/config/initializers/action_view.rb +7 -0
  235. data/config/initializers/inflections.rb +24 -0
  236. data/config/initializers/marginalia.rb +8 -0
  237. data/config/initializers/mime_types.rb +6 -0
  238. data/config/initializers/opentelemetry.rb +12 -0
  239. data/config/initializers/redis_conf.rb +40 -0
  240. data/config/initializers/searchkick.rb +9 -0
  241. data/config/initializers/will_paginate.rb +39 -0
  242. data/config/redis.yml +12 -0
  243. data/config/routes.rb +27 -0
  244. data/config/service_trust_routes.rb +5 -0
  245. data/config.ru +9 -0
  246. data/db/migrate/20230127194725_create_organization_table.rb +25 -0
  247. data/db/migrate/20230127194726_validate_organization_foreign_keys.rb +7 -0
  248. data/db/migrate/20230206141413_create_security_models.rb +42 -0
  249. data/db/migrate/20230206141513_create_restriction_model.rb +20 -0
  250. data/db/migrate/20230206142613_add_security_indexes.rb +14 -0
  251. data/db/migrate/20230206143513_security_model_constants_seed.rb +8 -0
  252. data/db/migrate/20230206161513_add_organization_to_security_models.rb +13 -0
  253. data/db/migrate/20230209121019_create_search_group_models.rb +36 -0
  254. data/db/migrate/20230209122919_add_search_group_indexes.rb +21 -0
  255. data/db/migrate/20230209181832_add_search_group_resources_type_constraint_trigger.rb +61 -0
  256. data/db/migrate/20230313220337_create_table_user_sessions.rb +31 -0
  257. data/db/migrate/20230517141106_update_resource_types.rb +12 -0
  258. data/db/migrate/20230518194611_add_resources_contraints.rb +17 -0
  259. data/db/migrate/20230518194642_create_nested_resource_model.rb +21 -0
  260. data/db/migrate/20230518194645_validate_security_models_constraints.rb +23 -0
  261. data/db/migrate/20230608125037_add_inactive_field_to_user_sessions.rb +7 -0
  262. data/db/migrate/20230919191525_add_parent_id_to_resources.rb +8 -0
  263. data/db/migrate/20230919191535_validate_parent_id_fk_resources.rb +7 -0
  264. data/db/migrate/20230919210456_drop_nested_resources.rb +7 -0
  265. data/db/migrate/20231222135620_add_user_role.rb +19 -0
  266. data/db/migrate/20240108172021_create_favorites_table.rb +20 -0
  267. data/db/migrate/20240108190209_create_organization_link_table_if_doesnt_exists.rb +31 -0
  268. data/db/migrate/20240109211445_validate_fks_org_links.rb +9 -0
  269. data/db/migrate/20240110135440_create_recently_vieweds.rb +20 -0
  270. data/db/migrate/20240112185312_link_type_options_seed.rb +7 -0
  271. data/db/migrate/20240116173201_organization_link_resource_tab_constraints.rb +19 -0
  272. data/db/migrate/20240131160949_add_user_organizations_table.rb +32 -0
  273. data/db/migrate/20240212132519_add_organization_in_user_sessions.rb +13 -0
  274. data/db/migrate/20240227135052_add_user_organization_composite_fks.rb +32 -0
  275. data/db/migrate/20240312193501_update_organization_links.rb +11 -0
  276. data/db/migrate/20240426185902_add_user_organizations_current_organization_column.rb +12 -0
  277. data/db/migrate/20240429214601_add_primary_trigger.rb +10 -0
  278. data/db/migrate/20241112131819_add_json_file_format_seed.rb +7 -0
  279. data/db/migrate/20250520133417_create_restriction_operations.rb +18 -0
  280. data/db/migrate/20250520133420_validate_create_restriction_operations.rb +8 -0
  281. data/db/migrate/20250520140642_add_indexes_to_restrictions.rb +9 -0
  282. data/db/migrate/20250529130741_create_notification_alerts_table.rb +28 -0
  283. data/db/migrate/20250530223332_create_data_migrations_table.rb +19 -0
  284. data/db/migrate/20250605121622_seed_notification_alert_status.rb +7 -0
  285. data/db/migrate/20250605131439_seed_notification_alert_types.rb +7 -0
  286. data/db/migrate/20250606173128_update_notification_alert_length_constraint.rb +18 -0
  287. data/db/migrate/20250606174043_validate_notification_alert_length_constraints.rb +8 -0
  288. data/db/migrate/20250624183000_add_data_import_alert_type.rb +11 -0
  289. data/db/migrate/20250722183935_create_organization_options_table.rb +38 -0
  290. data/db/migrate/20250722192724_validate_organization_options_fks_and_constraints.rb +32 -0
  291. data/db/migrate/20250722192936_add_indexes_to_organization_options_table.rb +15 -0
  292. data/db/migrate/20250915132448_add_column_restrictions_hash.rb +7 -0
  293. data/db/migrate/20250917143211_add_column_permissions_hash.rb +7 -0
  294. data/db/migrate/20251001145950_add_trust_this_device.rb +7 -0
  295. data/db/migrate/20251021130835_add_backend_urls_to_applications_table.rb +13 -0
  296. data/db/migrate/20251021172715_validate_backend_urls_constraints_in_applications.rb +9 -0
  297. data/db/migrate/20251030200020_create_card_definitions.rb +23 -0
  298. data/db/migrate/20251030201012_create_card_definition_resources.rb +24 -0
  299. data/db/migrate/20251030201236_create_card_fields.rb +34 -0
  300. data/db/migrate/20251103194317_create_role_resource_cards.rb +37 -0
  301. data/db/migrate/20251103194426_create_role_card_fields.rb +34 -0
  302. data/db/migrate/20251107173218_validate_columns_from_cards.rb +17 -0
  303. data/db/migrate/20251111173212_add_column_application_array.rb +7 -0
  304. data/db/migrate/20251112203326_add_card_types.rb +10 -0
  305. data/db/migrate/20251115120000_add_virtual_cards_support.rb +28 -0
  306. data/db/migrate/20251202231256_add_org_scoped_fk_role_resource_cards_to_roles.rb +14 -0
  307. data/db/migrate/20260105134504_add_accessor_field_resource_type.rb +7 -0
  308. data/db/migrate/20260115123000_add_default_visibility_to_card_definitions.rb +9 -0
  309. data/db/migrate/20260120175037_is_new_admin.rb +9 -0
  310. data/db/migrate/20260122392008_add_stats_table.rb +17 -0
  311. data/db/migrate/20260414204718_add_index_to_stats_table.rb +10 -0
  312. data/db/migrate/20260509120000_create_service_trust_keys.rb +25 -0
  313. data/db/migrate/20260509120001_create_service_trust_audit_logs.rb +26 -0
  314. data/db/migrate/20260511120000_add_direction_to_service_trust_audit_logs.rb +17 -0
  315. data/db/migrate/20260511121000_remove_role_fk_from_role_resource_cards.rb +19 -0
  316. data/db/migrate/20260511121001_add_disabled_at_to_role_resource_cards.rb +10 -0
  317. data/db/migrate/20260605120100_seed_hover_card_options.rb +8 -0
  318. data/db/migrate/20260608120000_seed_badge_list_card_data_type.rb +7 -0
  319. data/db/migrate/20260727000000_add_two_factor_trust_id_to_user_sessions.rb +11 -0
  320. data/db/seeds.rb +14 -0
  321. data/docs/Elasticsearch.md +23 -0
  322. data/docs/Search_API.md +126 -0
  323. data/docs/cards_role_proxy.md +107 -0
  324. data/docs/factory_api.md +135 -0
  325. data/has-helpers-pr-289-pipeline.yaml +16 -0
  326. data/has-helpers-pr-298-pipeline.yaml +15 -0
  327. data/has_helpers.gemspec +68 -0
  328. data/lib/active_record/migration/active_migration_patch.rb +123 -0
  329. data/lib/generators/has_helpers/install/USAGE +8 -0
  330. data/lib/generators/has_helpers/install/install_generator.rb +22 -0
  331. data/lib/generators/has_helpers/install/templates/migrate/has_helpers_initial_setup.rb +26 -0
  332. data/lib/has_helpers/active_record/base.rb +50 -0
  333. data/lib/has_helpers/active_record/date_range.rb +228 -0
  334. data/lib/has_helpers/active_record/nested_resource.rb +58 -0
  335. data/lib/has_helpers/active_record/validation.rb +63 -0
  336. data/lib/has_helpers/active_record.rb +520 -0
  337. data/lib/has_helpers/app/command.rb +107 -0
  338. data/lib/has_helpers/app.rb +10 -0
  339. data/lib/has_helpers/assets_precompile_helper.rb +20 -0
  340. data/lib/has_helpers/attributes.rb +196 -0
  341. data/lib/has_helpers/aws.rb +67 -0
  342. data/lib/has_helpers/cards/cache.rb +49 -0
  343. data/lib/has_helpers/cards/config.rb +42 -0
  344. data/lib/has_helpers/cards/remote_role.rb +27 -0
  345. data/lib/has_helpers/cards/remote_roles.rb +75 -0
  346. data/lib/has_helpers/cards/roles.rb +55 -0
  347. data/lib/has_helpers/cards.rb +34 -0
  348. data/lib/has_helpers/concerns/hydrate_concern.rb +76 -0
  349. data/lib/has_helpers/concerns/importable_accessors.rb +25 -0
  350. data/lib/has_helpers/concerns/restrictions_parseable.rb +51 -0
  351. data/lib/has_helpers/config.rb +11 -0
  352. data/lib/has_helpers/constant.rb +578 -0
  353. data/lib/has_helpers/constraints.rb +10 -0
  354. data/lib/has_helpers/core_ext/array.rb +28 -0
  355. data/lib/has_helpers/core_ext/date/infinity.rb +25 -0
  356. data/lib/has_helpers/core_ext/date.rb +39 -0
  357. data/lib/has_helpers/core_ext/enumerable.rb +20 -0
  358. data/lib/has_helpers/core_ext/false_class.rb +14 -0
  359. data/lib/has_helpers/core_ext/float.rb +18 -0
  360. data/lib/has_helpers/core_ext/hash.rb +71 -0
  361. data/lib/has_helpers/core_ext/integer.rb +18 -0
  362. data/lib/has_helpers/core_ext/nil_class.rb +30 -0
  363. data/lib/has_helpers/core_ext/object.rb +60 -0
  364. data/lib/has_helpers/core_ext/proc.rb +16 -0
  365. data/lib/has_helpers/core_ext/string.rb +81 -0
  366. data/lib/has_helpers/core_ext/symbol.rb +17 -0
  367. data/lib/has_helpers/core_ext/true_class.rb +14 -0
  368. data/lib/has_helpers/cursor.rb +46 -0
  369. data/lib/has_helpers/data_migration/base.rb +293 -0
  370. data/lib/has_helpers/data_migration/large_worker.rb +20 -0
  371. data/lib/has_helpers/data_migration/master_worker.rb +33 -0
  372. data/lib/has_helpers/data_migration/migrator.rb +83 -0
  373. data/lib/has_helpers/data_migration/model.rb +18 -0
  374. data/lib/has_helpers/data_migration/small_worker.rb +20 -0
  375. data/lib/has_helpers/data_migration.rb +8 -0
  376. data/lib/has_helpers/engine.rb +47 -0
  377. data/lib/has_helpers/extensions.rb +17 -0
  378. data/lib/has_helpers/factory_api.rb +47 -0
  379. data/lib/has_helpers/factory_scenario/definition.rb +33 -0
  380. data/lib/has_helpers/factory_scenario/dsl.rb +80 -0
  381. data/lib/has_helpers/factory_scenario/factory_bot_override.rb +53 -0
  382. data/lib/has_helpers/factory_scenario/registry.rb +31 -0
  383. data/lib/has_helpers/factory_scenario/runner.rb +285 -0
  384. data/lib/has_helpers/factory_scenario.rb +27 -0
  385. data/lib/has_helpers/fixed_expiration_time_cache_session_store.rb +20 -0
  386. data/lib/has_helpers/flattener/association.rb +140 -0
  387. data/lib/has_helpers/flattener/attribute.rb +42 -0
  388. data/lib/has_helpers/flattener/base.rb +428 -0
  389. data/lib/has_helpers/flattener/column/types.rb +71 -0
  390. data/lib/has_helpers/flattener/column.rb +97 -0
  391. data/lib/has_helpers/flattener/pivot.rb +49 -0
  392. data/lib/has_helpers/flattener/reset.rb +33 -0
  393. data/lib/has_helpers/flattener/resource.rb +114 -0
  394. data/lib/has_helpers/flattener/resource_generator.rb +21 -0
  395. data/lib/has_helpers/flattener/utils.rb +14 -0
  396. data/lib/has_helpers/flattener.rb +53 -0
  397. data/lib/has_helpers/hash_accessors.rb +62 -0
  398. data/lib/has_helpers/helpers/application.rb +291 -0
  399. data/lib/has_helpers/index/config.rb +15 -0
  400. data/lib/has_helpers/lazy_config.rb +61 -0
  401. data/lib/has_helpers/resource_importer.rb +260 -0
  402. data/lib/has_helpers/resources.rb +127 -0
  403. data/lib/has_helpers/rspec/constants.rb +61 -0
  404. data/lib/has_helpers/rspec/factories.rb +11 -0
  405. data/lib/has_helpers/rspec.rb +22 -0
  406. data/lib/has_helpers/sentry/graphql_trace.rb +37 -0
  407. data/lib/has_helpers/serializer.rb +41 -0
  408. data/lib/has_helpers/service_trust/audit.rb +45 -0
  409. data/lib/has_helpers/service_trust/authorization.rb +34 -0
  410. data/lib/has_helpers/service_trust/bearer_token.rb +23 -0
  411. data/lib/has_helpers/service_trust/capability.rb +32 -0
  412. data/lib/has_helpers/service_trust/client/response.rb +13 -0
  413. data/lib/has_helpers/service_trust/client/retry.rb +35 -0
  414. data/lib/has_helpers/service_trust/client.rb +128 -0
  415. data/lib/has_helpers/service_trust/configuration.rb +76 -0
  416. data/lib/has_helpers/service_trust/engine.rb +10 -0
  417. data/lib/has_helpers/service_trust/errors.rb +32 -0
  418. data/lib/has_helpers/service_trust/graphql/authorizable.rb +72 -0
  419. data/lib/has_helpers/service_trust/graphql_client.rb +54 -0
  420. data/lib/has_helpers/service_trust/jwks_client.rb +133 -0
  421. data/lib/has_helpers/service_trust/jwt_codec.rb +45 -0
  422. data/lib/has_helpers/service_trust/key_manager.rb +160 -0
  423. data/lib/has_helpers/service_trust/rate_limiter.rb +27 -0
  424. data/lib/has_helpers/service_trust/replay_cache.rb +26 -0
  425. data/lib/has_helpers/service_trust/signer.rb +47 -0
  426. data/lib/has_helpers/service_trust/test.rb +60 -0
  427. data/lib/has_helpers/service_trust/verifier.rb +85 -0
  428. data/lib/has_helpers/service_trust/verify_middleware.rb +55 -0
  429. data/lib/has_helpers/service_trust.rb +51 -0
  430. data/lib/has_helpers/test_helpers/factory_data/unit_addresses.rb +42 -0
  431. data/lib/has_helpers/test_helpers/factory_data/unit_advisors.rb +272 -0
  432. data/lib/has_helpers/test_helpers/factory_data/unit_emails.rb +37 -0
  433. data/lib/has_helpers/test_helpers/factory_data/unit_organizations.rb +48 -0
  434. data/lib/has_helpers/test_helpers/factory_data/unit_people.rb +34 -0
  435. data/lib/has_helpers/test_helpers/factory_data/unit_phones.rb +41 -0
  436. data/lib/has_helpers/test_helpers/factory_data/unit_states.rb +26 -0
  437. data/lib/has_helpers/test_helpers/factory_data/unit_webs.rb +31 -0
  438. data/lib/has_helpers/test_helpers/shoulda_helpers.rb +249 -0
  439. data/lib/has_helpers/ui_attributes.rb +39 -0
  440. data/lib/has_helpers/utils/action.rb +87 -0
  441. data/lib/has_helpers/utils/active_record_utils.rb +40 -0
  442. data/lib/has_helpers/utils/alert.rb +42 -0
  443. data/lib/has_helpers/utils/container.rb +46 -0
  444. data/lib/has_helpers/utils/dataset.rb +34 -0
  445. data/lib/has_helpers/utils/email_notifier.rb +15 -0
  446. data/lib/has_helpers/utils/icon.rb +66 -0
  447. data/lib/has_helpers/utils/input.rb +45 -0
  448. data/lib/has_helpers/utils/log_notifier.rb +79 -0
  449. data/lib/has_helpers/utils/render_object.rb +32 -0
  450. data/lib/has_helpers/utils/s3_utils.rb +37 -0
  451. data/lib/has_helpers/utils/service.rb +87 -0
  452. data/lib/has_helpers/utils/slack_conf.rb +31 -0
  453. data/lib/has_helpers/utils/slack_notifier.rb +42 -0
  454. data/lib/has_helpers/utils/text_node.rb +34 -0
  455. data/lib/has_helpers/utils/user_restrictions.rb +266 -0
  456. data/lib/has_helpers/utils.rb +19 -0
  457. data/lib/has_helpers/version.rb +6 -0
  458. data/lib/has_helpers/web_socket_publisher.rb +20 -0
  459. data/lib/has_helpers/webhook.rb +17 -0
  460. data/lib/has_helpers.rb +63 -0
  461. data/lib/hq/resource.rb +55 -0
  462. data/lib/hq/structure/inserts.rb +176 -0
  463. data/lib/hq/structure.rb +115 -0
  464. data/lib/tasks/has_helpers/service_trust.rake +39 -0
  465. data/script/rails +10 -0
  466. data/sorbet/config +5 -0
  467. data/sorbet/rbi/gems/actionpack.rbi +1361 -0
  468. data/sorbet/rbi/gems/activemodel.rbi +755 -0
  469. data/sorbet/rbi/gems/activerecord.rbi +4713 -0
  470. data/sorbet/rbi/gems/activesupport.rbi +1970 -0
  471. data/sorbet/rbi/gems/barby.rbi +129 -0
  472. data/sorbet/rbi/gems/better_errors.rbi +155 -0
  473. data/sorbet/rbi/gems/binding_of_caller.rbi +22 -0
  474. data/sorbet/rbi/gems/byebug.rbi +19 -0
  475. data/sorbet/rbi/gems/chronic.rbi +480 -0
  476. data/sorbet/rbi/gems/chunky_png.rbi +510 -0
  477. data/sorbet/rbi/gems/climate_control.rbi +45 -0
  478. data/sorbet/rbi/gems/coderay.rbi +91 -0
  479. data/sorbet/rbi/gems/concurrent-ruby.rbi +1587 -0
  480. data/sorbet/rbi/gems/debug_inspector.rbi +18 -0
  481. data/sorbet/rbi/gems/erubi.rbi +26 -0
  482. data/sorbet/rbi/gems/holidays.rbi +362 -0
  483. data/sorbet/rbi/gems/i18n.rbi +132 -0
  484. data/sorbet/rbi/gems/method_source.rbi +63 -0
  485. data/sorbet/rbi/gems/mime-types-data.rbi +16 -0
  486. data/sorbet/rbi/gems/mime-types.rbi +217 -0
  487. data/sorbet/rbi/gems/mimemagic.rbi +36 -0
  488. data/sorbet/rbi/gems/minitest.rbi +282 -0
  489. data/sorbet/rbi/gems/mustache.rbi +151 -0
  490. data/sorbet/rbi/gems/paperclip.rbi +644 -0
  491. data/sorbet/rbi/gems/parallel.rbi +81 -0
  492. data/sorbet/rbi/gems/parallel_tests.rbi +27 -0
  493. data/sorbet/rbi/gems/pry-rails.rbi +71 -0
  494. data/sorbet/rbi/gems/pry.rbi +1964 -0
  495. data/sorbet/rbi/gems/rack.rbi +228 -0
  496. data/sorbet/rbi/gems/railties.rbi +364 -0
  497. data/sorbet/rbi/gems/rspec-core.rbi +1704 -0
  498. data/sorbet/rbi/gems/rspec-expectations.rbi +389 -0
  499. data/sorbet/rbi/gems/rspec-support.rbi +206 -0
  500. data/sorbet/rbi/gems/rspec.rbi +14 -0
  501. data/sorbet/rbi/gems/spreadsheet_architect.rbi +10 -0
  502. data/sorbet/rbi/gems/sprockets.rbi +659 -0
  503. data/sorbet/rbi/gems/terrapin.rbi +115 -0
  504. data/sorbet/rbi/gems/testhq.rbi +14 -0
  505. data/sorbet/rbi/gems/thor.rbi +482 -0
  506. data/sorbet/rbi/gems/thread_safe.rbi +81 -0
  507. data/sorbet/rbi/gems/tzinfo.rbi +407 -0
  508. data/sorbet/rbi/gems/zeitwerk.rbi +131 -0
  509. data/sorbet/rbi/hidden-definitions/errors.txt +35111 -0
  510. data/sorbet/rbi/hidden-definitions/hidden.rbi +59974 -0
  511. data/sorbet/rbi/sorbet-typed/lib/actionmailer/all/actionmailer.rbi +13 -0
  512. data/sorbet/rbi/sorbet-typed/lib/actionpack/all/actionpack.rbi +855 -0
  513. data/sorbet/rbi/sorbet-typed/lib/actionview/all/actionview.rbi +314 -0
  514. data/sorbet/rbi/sorbet-typed/lib/activemodel/all/activemodel.rbi +423 -0
  515. data/sorbet/rbi/sorbet-typed/lib/activerecord/>=5.2/activerecord.rbi +17 -0
  516. data/sorbet/rbi/sorbet-typed/lib/activerecord/all/activerecord.rbi +1415 -0
  517. data/sorbet/rbi/sorbet-typed/lib/activerecord/~>6.0.0/activerecord.rbi +475 -0
  518. data/sorbet/rbi/sorbet-typed/lib/activestorage/>=6.0.0/activestorage.rbi +19 -0
  519. data/sorbet/rbi/sorbet-typed/lib/activestorage/all/activestorage.rbi +173 -0
  520. data/sorbet/rbi/sorbet-typed/lib/activesupport/>=6.0.0.rc1/activesupport.rbi +23 -0
  521. data/sorbet/rbi/sorbet-typed/lib/activesupport/all/activesupport.rbi +625 -0
  522. data/sorbet/rbi/sorbet-typed/lib/bundler/all/bundler.rbi +8684 -0
  523. data/sorbet/rbi/sorbet-typed/lib/minitest/all/minitest.rbi +99 -0
  524. data/sorbet/rbi/sorbet-typed/lib/railties/all/railties.rbi +25 -0
  525. data/sorbet/rbi/sorbet-typed/lib/rainbow/all/rainbow.rbi +254 -0
  526. data/sorbet/rbi/sorbet-typed/lib/ruby/all/gem.rbi +4222 -0
  527. data/sorbet/rbi/sorbet-typed/lib/ruby/all/open3.rbi +111 -0
  528. data/sorbet/rbi/sorbet-typed/lib/ruby/all/resolv.rbi +543 -0
  529. data/sorbet/rbi/todo.rbi +244 -0
  530. data/spec/commands/cards/available_cards_command_spec.rb +90 -0
  531. data/spec/commands/cards/card_data_command_spec.rb +1048 -0
  532. data/spec/constructors/has_helpers/base_constructor_spec.rb +49 -0
  533. data/spec/controllers/has_helpers/controller_spec.rb +71 -0
  534. data/spec/controllers/has_helpers/xlsx_export_spec.rb +96 -0
  535. data/spec/factories/advisors.rb +20 -0
  536. data/spec/factories/anonymous_cowards.rb +5 -0
  537. data/spec/factories/applications.rb +5 -0
  538. data/spec/factories/appointments.rb +6 -0
  539. data/spec/factories/attachment.rb +15 -0
  540. data/spec/factories/audits.rb +5 -0
  541. data/spec/factories/basic_schedules.rb +12 -0
  542. data/spec/factories/cards/card_definition_resources.rb +7 -0
  543. data/spec/factories/cards/card_definitions.rb +11 -0
  544. data/spec/factories/cards/card_fields.rb +22 -0
  545. data/spec/factories/cards/role_card_fields.rb +17 -0
  546. data/spec/factories/cards/role_resource_cards.rb +12 -0
  547. data/spec/factories/constant_definition.rb +7 -0
  548. data/spec/factories/contracts.rb +10 -0
  549. data/spec/factories/data_migration_models.rb +9 -0
  550. data/spec/factories/demographics.rb +22 -0
  551. data/spec/factories/events.rb +21 -0
  552. data/spec/factories/firms.rb +21 -0
  553. data/spec/factories/imports.rb +9 -0
  554. data/spec/factories/meetups.rb +13 -0
  555. data/spec/factories/organizations.rb +13 -0
  556. data/spec/factories/policies.rb +7 -0
  557. data/spec/factories/policy_notes.rb +9 -0
  558. data/spec/factories/read_only_values.rb +7 -0
  559. data/spec/factories/resources.rb +7 -0
  560. data/spec/factories/roles.rb +11 -0
  561. data/spec/factories/schedules.rb +13 -0
  562. data/spec/factories/shared/flattener/attributes.rb +10 -0
  563. data/spec/factories/shared/flattener/columns.rb +6 -0
  564. data/spec/factories/shared/flattener/resources.rb +5 -0
  565. data/spec/factories/templates.rb +12 -0
  566. data/spec/factories/user_organizations.rb +23 -0
  567. data/spec/factories/user_session.rb +11 -0
  568. data/spec/factories/users.rb +55 -0
  569. data/spec/fixtures/sample_config.yml +3 -0
  570. data/spec/graphql/has_helpers/mutations/external_data_sync_spec.rb +101 -0
  571. data/spec/graphql/has_helpers/resolvers/cards/available_cards_spec.rb +36 -0
  572. data/spec/graphql/has_helpers/resolvers/cards/card_data_spec.rb +42 -0
  573. data/spec/graphql/has_helpers/resolvers/cards/hover_card_data_spec.rb +59 -0
  574. data/spec/has_helpers/cards/cache_spec.rb +58 -0
  575. data/spec/has_helpers/cards/config_spec.rb +68 -0
  576. data/spec/has_helpers/cards/orphan_card_sweeper_spec.rb +97 -0
  577. data/spec/has_helpers/cards/remote_role_spec.rb +17 -0
  578. data/spec/has_helpers/cards/remote_roles_spec.rb +97 -0
  579. data/spec/has_helpers/cards/roles_spec.rb +52 -0
  580. data/spec/has_helpers/cards/smoke_spec.rb +168 -0
  581. data/spec/has_helpers/cards/support.rb +52 -0
  582. data/spec/has_helpers/resolvers/internal/roles_spec.rb +73 -0
  583. data/spec/has_helpers/service_trust/audit_log_cleanup_worker_spec.rb +33 -0
  584. data/spec/has_helpers/service_trust/audit_spec.rb +70 -0
  585. data/spec/has_helpers/service_trust/authenticated_as_service_spec.rb +65 -0
  586. data/spec/has_helpers/service_trust/authorization_spec.rb +37 -0
  587. data/spec/has_helpers/service_trust/capability_spec.rb +35 -0
  588. data/spec/has_helpers/service_trust/client_spec.rb +129 -0
  589. data/spec/has_helpers/service_trust/configuration_spec.rb +35 -0
  590. data/spec/has_helpers/service_trust/engine_spec.rb +13 -0
  591. data/spec/has_helpers/service_trust/errors_spec.rb +23 -0
  592. data/spec/has_helpers/service_trust/graphql_authentication_spec.rb +59 -0
  593. data/spec/has_helpers/service_trust/graphql_authorizable_spec.rb +91 -0
  594. data/spec/has_helpers/service_trust/graphql_client_spec.rb +47 -0
  595. data/spec/has_helpers/service_trust/jwks_client_spec.rb +85 -0
  596. data/spec/has_helpers/service_trust/jwks_controller_spec.rb +47 -0
  597. data/spec/has_helpers/service_trust/jwt_codec_spec.rb +30 -0
  598. data/spec/has_helpers/service_trust/key_manager_spec.rb +80 -0
  599. data/spec/has_helpers/service_trust/key_spec.rb +52 -0
  600. data/spec/has_helpers/service_trust/rake_tasks_spec.rb +58 -0
  601. data/spec/has_helpers/service_trust/rate_limiter_spec.rb +31 -0
  602. data/spec/has_helpers/service_trust/replay_cache_spec.rb +29 -0
  603. data/spec/has_helpers/service_trust/signer_verifier_spec.rb +159 -0
  604. data/spec/has_helpers/service_trust/smoke_spec.rb +216 -0
  605. data/spec/has_helpers/service_trust/support.rb +121 -0
  606. data/spec/has_helpers/service_trust/test_module_spec.rb +39 -0
  607. data/spec/has_helpers/service_trust/verify_middleware_spec.rb +82 -0
  608. data/spec/helpers/application_spec.rb +155 -0
  609. data/spec/importers/cards/importer_spec.rb +861 -0
  610. data/spec/internal/app/assets/config/manifest.js +0 -0
  611. data/spec/internal/app/constants/cat_egory.rb +33 -0
  612. data/spec/internal/app/constants/color.rb +19 -0
  613. data/spec/internal/app/constants/exclude_group_nil_constant.rb +5 -0
  614. data/spec/internal/app/constants/foo.rb +6 -0
  615. data/spec/internal/app/constants/fruit.rb +3 -0
  616. data/spec/internal/app/constants/generic_group_example.rb +12 -0
  617. data/spec/internal/app/constants/my_constant.rb +4 -0
  618. data/spec/internal/app/constants/product.rb +3 -0
  619. data/spec/internal/app/constants/shirt_size.rb +18 -0
  620. data/spec/internal/app/constants/simple_option.rb +14 -0
  621. data/spec/internal/app/constants/status.rb +5 -0
  622. data/spec/internal/app/constructors/organization_constructor.rb +4 -0
  623. data/spec/internal/app/controllers/application_controller.rb +27 -0
  624. data/spec/internal/app/controllers/search_extensions.rb +3 -0
  625. data/spec/internal/app/indices/advisor_index.rb +35 -0
  626. data/spec/internal/app/indices/contract_index.rb +52 -0
  627. data/spec/internal/app/indices/firm_index.rb +16 -0
  628. data/spec/internal/app/indices/policy_index.rb +20 -0
  629. data/spec/internal/app/models/advisor.rb +24 -0
  630. data/spec/internal/app/models/agency.rb +3 -0
  631. data/spec/internal/app/models/anonymous_coward.rb +5 -0
  632. data/spec/internal/app/models/another_event.rb +2 -0
  633. data/spec/internal/app/models/appointment.rb +3 -0
  634. data/spec/internal/app/models/attachment.rb +10 -0
  635. data/spec/internal/app/models/basic_schedule.rb +6 -0
  636. data/spec/internal/app/models/beneficiary.rb +6 -0
  637. data/spec/internal/app/models/client.rb +5 -0
  638. data/spec/internal/app/models/contract.rb +9 -0
  639. data/spec/internal/app/models/demographic.rb +4 -0
  640. data/spec/internal/app/models/division.rb +3 -0
  641. data/spec/internal/app/models/event.rb +10 -0
  642. data/spec/internal/app/models/event_template.rb +1 -0
  643. data/spec/internal/app/models/firm.rb +18 -0
  644. data/spec/internal/app/models/has_audits/audit.rb +15 -0
  645. data/spec/internal/app/models/import.rb +7 -0
  646. data/spec/internal/app/models/meetup.rb +8 -0
  647. data/spec/internal/app/models/organization.rb +17 -0
  648. data/spec/internal/app/models/organization_event.rb +5 -0
  649. data/spec/internal/app/models/policy.rb +16 -0
  650. data/spec/internal/app/models/policy_holder.rb +7 -0
  651. data/spec/internal/app/models/policy_note.rb +8 -0
  652. data/spec/internal/app/models/project.rb +3 -0
  653. data/spec/internal/app/models/read_only_value.rb +8 -0
  654. data/spec/internal/app/models/schedule.rb +12 -0
  655. data/spec/internal/app/models/template.rb +9 -0
  656. data/spec/internal/app/models/user.rb +31 -0
  657. data/spec/internal/app/models/user_organization.rb +17 -0
  658. data/spec/internal/app/views/has_lists/lists/_table.html.erb +1 -0
  659. data/spec/internal/config/database-standard.yml +24 -0
  660. data/spec/internal/config/initializers/resources.rb +19 -0
  661. data/spec/internal/config/routes.rb +19 -0
  662. data/spec/internal/config/storage.yml +3 -0
  663. data/spec/internal/db/good_job_schema.rb +141 -0
  664. data/spec/internal/db/schema.rb +334 -0
  665. data/spec/internal/log/.gitignore +1 -0
  666. data/spec/internal/public/favicon.ico +0 -0
  667. data/spec/lib/flattener/base_spec.rb +93 -0
  668. data/spec/lib/flattener/column/types_spec.rb +65 -0
  669. data/spec/lib/has_helpers/active_record/base_spec.rb +53 -0
  670. data/spec/lib/has_helpers/active_record/date_range_spec.rb +498 -0
  671. data/spec/lib/has_helpers/active_record/lookup_model_spec.rb +408 -0
  672. data/spec/lib/has_helpers/active_record/nested_resource_spec.rb +43 -0
  673. data/spec/lib/has_helpers/app/command/reports/generator_specs.rb +887 -0
  674. data/spec/lib/has_helpers/app/command_spec.rb +117 -0
  675. data/spec/lib/has_helpers/app/support/mocks/graphql_reports_helpers.rb +96 -0
  676. data/spec/lib/has_helpers/app/support/mocks/reports_api_mock.rb +47 -0
  677. data/spec/lib/has_helpers/attributes_spec.rb +220 -0
  678. data/spec/lib/has_helpers/aws_spec.rb +126 -0
  679. data/spec/lib/has_helpers/concerns/hydrate_concern_spec.rb +473 -0
  680. data/spec/lib/has_helpers/constant/definition_spec.rb +128 -0
  681. data/spec/lib/has_helpers/constant_spec.rb +432 -0
  682. data/spec/lib/has_helpers/core_ext/array_spec.rb +22 -0
  683. data/spec/lib/has_helpers/core_ext/date/infinity_spec.rb +67 -0
  684. data/spec/lib/has_helpers/core_ext/date_spec.rb +27 -0
  685. data/spec/lib/has_helpers/core_ext/false_class_spec.rb +7 -0
  686. data/spec/lib/has_helpers/core_ext/float_spec.rb +9 -0
  687. data/spec/lib/has_helpers/core_ext/hash_spec.rb +48 -0
  688. data/spec/lib/has_helpers/core_ext/integer_spec.rb +9 -0
  689. data/spec/lib/has_helpers/core_ext/nil_class_spec.rb +17 -0
  690. data/spec/lib/has_helpers/core_ext/object_spec.rb +54 -0
  691. data/spec/lib/has_helpers/core_ext/proc_spec.rb +24 -0
  692. data/spec/lib/has_helpers/core_ext/string_spec.rb +154 -0
  693. data/spec/lib/has_helpers/core_ext/true_class_spec.rb +7 -0
  694. data/spec/lib/has_helpers/data_migration/base_spec.rb +470 -0
  695. data/spec/lib/has_helpers/data_migration/large_worker_spec.rb +34 -0
  696. data/spec/lib/has_helpers/data_migration/master_worker_spec.rb +51 -0
  697. data/spec/lib/has_helpers/data_migration/migrator_spec.rb +113 -0
  698. data/spec/lib/has_helpers/data_migration/model_spec.rb +52 -0
  699. data/spec/lib/has_helpers/data_migration/small_worker_spec.rb +34 -0
  700. data/spec/lib/has_helpers/good_job/notification_alert_cleanup_worker_spec.rb +89 -0
  701. data/spec/lib/has_helpers/hash_accessors_spec.rb +66 -0
  702. data/spec/lib/has_helpers/index_spec.rb +442 -0
  703. data/spec/lib/has_helpers/lazy_config_spec.rb +31 -0
  704. data/spec/lib/has_helpers/organization_merge_spec.rb +88 -0
  705. data/spec/lib/has_helpers/resource_importer_spec.rb +119 -0
  706. data/spec/lib/has_helpers/rspec_spec.rb +16 -0
  707. data/spec/lib/has_helpers/search/provider_spec.rb +26 -0
  708. data/spec/lib/has_helpers/search/search_utils_spec.rb +134 -0
  709. data/spec/lib/has_helpers/sentry/graphql_trace_spec.rb +140 -0
  710. data/spec/lib/has_helpers/ui_attributes_spec.rb +42 -0
  711. data/spec/lib/has_helpers/utils/action_spec.rb +49 -0
  712. data/spec/lib/has_helpers/utils/alert_spec.rb +33 -0
  713. data/spec/lib/has_helpers/utils/dataset_spec.rb +10 -0
  714. data/spec/lib/has_helpers/utils/icon_spec.rb +23 -0
  715. data/spec/lib/has_helpers/utils/input_spec.rb +40 -0
  716. data/spec/lib/has_helpers/utils/log_notifier_spec.rb +25 -0
  717. data/spec/lib/has_helpers/utils/render_object_spec.rb +17 -0
  718. data/spec/lib/has_helpers/utils/s3_utils_spec.rb +78 -0
  719. data/spec/lib/has_helpers/utils/service_spec.rb +79 -0
  720. data/spec/lib/has_helpers/utils/slack_notifier_spec.rb +70 -0
  721. data/spec/lib/has_helpers/utils/user_restrictions_spec.rb +343 -0
  722. data/spec/lib/hq/structure/inserts_spec.rb +152 -0
  723. data/spec/lib/hq/structure_spec.rb +12 -0
  724. data/spec/models/cards/base_spec.rb +149 -0
  725. data/spec/models/cards/card_definition_resource_spec.rb +103 -0
  726. data/spec/models/cards/card_definition_spec.rb +50 -0
  727. data/spec/models/cards/card_field_spec.rb +46 -0
  728. data/spec/models/cards/field_builder_spec.rb +318 -0
  729. data/spec/models/cards/registry_spec.rb +147 -0
  730. data/spec/models/cards/role_card_field_spec.rb +111 -0
  731. data/spec/models/cards/role_resource_card_spec.rb +112 -0
  732. data/spec/models/has_helpers/master_record_spec.rb +20 -0
  733. data/spec/models/has_helpers/user_organization_spec.rb +28 -0
  734. data/spec/models/has_helpers/user_session_spec.rb +16 -0
  735. data/spec/presenters/base_presenter_spec.rb +166 -0
  736. data/spec/rails_helper.rb +119 -0
  737. data/spec/requests/has_helpers/factory_controller_spec.rb +159 -0
  738. data/spec/requests/has_helpers/health_check_controller_spec.rb +37 -0
  739. data/spec/scenarios/empty_setup.rb +6 -0
  740. data/spec/spec_helper.rb +22 -0
  741. data/spec/support/has_helpers/database_helpers.rb +12 -0
  742. data/spec/utils/sync_utils_spec.rb +233 -0
  743. data/spec/workers/goodjob/elasticsearch_process_batch_spec.rb +94 -0
  744. data/spec/workers/goodjob/elasticsearch_process_change_logs_master_worker_spec.rb +40 -0
  745. data/spec/workers/goodjob/elasticsearch_process_change_logs_worker_spec.rb +82 -0
  746. data/spec/workers/goodjob/elasticsearch_process_queue_spec.rb +89 -0
  747. data/spec/workers/goodjob/elasticsearch_reindex_spec.rb +49 -0
  748. metadata +1593 -0
@@ -0,0 +1,520 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "active_record"
5
+ require "humanize"
6
+
7
+ require "has_helpers/active_record/base"
8
+ require "has_helpers/active_record/nested_resource"
9
+ require "has_helpers/active_record/date_range"
10
+ require "has_helpers/active_record/validation"
11
+
12
+ module HasHelpers
13
+ HISTORY_MAX = 15
14
+
15
+ mattr_accessor :recently_viewed_resources
16
+
17
+ module ActiveRecord
18
+ include HasHelpers::ActiveRecordUtils
19
+ # Lookup for models
20
+ # @param [Hash] params The attribute:values that are used for the lookup
21
+ # @param [Hash] organization_id The value that are used for improve the lookup and only search in a determined organization
22
+ # @param [Hash] scope Additional scope for the lookup, defining joins, where clauses, and logical conditions.
23
+ # @param [String] condition The condition with which the lookup is performed (AND/OR). Default value is and.
24
+ # @param [String] show_error Boolean - Flag to indicate whether to show error messages or return nil when there are multiple or no results.
25
+ # @param [String] return_multiple Boolean - Flag to indicate whether to return all the models found or not.
26
+ # @param [Boolean] case_sensitive Boolean - Flag to indicate whether the lookup operation should be case-sensitive or case-insensitive.
27
+ # @return [Class/nil/String] Returns model instance or nil - The result of the lookup operation. If a single record is found, the instance is returned; otherwise, returns nil or an error message based on the show_error flag.
28
+
29
+ def lookup(params, organization_id, scope: nil, condition: LogicalOperator::AND, show_error: false, return_multiple: false, case_sensitive: true)
30
+ model = nil
31
+ if params.is_a? Hash
32
+ params.each do |key, _value|
33
+ association_values = key.to_s.split(".")
34
+ if !self.has_attribute?(key)
35
+ params = params.except(key) unless association_values.size > 1 && is_valid_association(association_values)
36
+ end
37
+ end
38
+ if params.any?
39
+ if condition == LogicalOperator::OR
40
+ model = add_joins(params, self, organization_id)
41
+ model = query_condition(params, case_sensitive, model, LogicalOperator::OR)
42
+ end
43
+
44
+ if condition == LogicalOperator::AND
45
+ model = add_joins(params, self, organization_id)
46
+ model = query_condition(params, case_sensitive, model)
47
+ end
48
+ end
49
+ else
50
+ model = lookup_by_attributes_precedence(params, organization_id, case_sensitive, return_multiple)
51
+ end
52
+ # scope example: [
53
+ # {:joins=>{:policies=>:advisors}, :where=>{:advisors=>{:npn=>11111529}}},
54
+ # {:joins=>{:policy_holders=>[{:policy=>{:writing_advisors=>:contract}}]}, :where=>{:"contracts.individual_number"=>"V1BUZ9J44XGJHF", :"contracts.corporate_number"=>"V1BUZ9J44XGJHF"}, :condition=>"or"}]
55
+ if scope.present?
56
+ scope.each do |item|
57
+ # join tables if specified in the scope item
58
+ model = model.joins(item[:joins]) if item[:joins].present?
59
+ if item[:condition].present? && item[:condition] == LogicalOperator::OR
60
+ model = query_condition(item[:where], case_sensitive, model, LogicalOperator::OR)
61
+ else
62
+ model ||= self
63
+ model = model.where(item[:where]) # LogicalOperator::AND by default
64
+ end
65
+ end
66
+ end
67
+ if return_multiple
68
+ ::ActiveRecord::Base.transaction do
69
+ return model.presence || []
70
+ rescue => e
71
+ ::ActiveRecord::Base.connection.rollback_db_transaction
72
+ error_message = "SQL error in #{__method__}: #{e.message}"
73
+ if show_error
74
+ return error_message
75
+ else
76
+ Rails.logger.info error_message
77
+ end
78
+ []
79
+ end
80
+ else
81
+ ::ActiveRecord::Base.transaction do
82
+ model = model.limit(2) unless model.nil? # Limiting the query to retrieve a maximum of 2 records for improved performance and to prevent unnecessary processing.
83
+ model.present? && model.size == 1 ? model.first : (show_error ? (model.present? && model.size > 1 ? "Multiple found." : "Not found.") : nil)
84
+ rescue => e
85
+ # In case of errors for example when we iterate over the lookup_attributes_precedence and try to match
86
+ # an string with an uuid (id), avoid the error and continue looking for the next attribute
87
+ ::Rails.logger.info "SQL error in #{__method__}: #{e.message}"
88
+ ::ActiveRecord::Base.connection.rollback_db_transaction
89
+ nil
90
+ end
91
+ end
92
+ end
93
+
94
+ def lookup_by_attributes_precedence(params, organization_id, case_sensitive, return_multiple)
95
+ model = nil
96
+ value = params
97
+ self.lookup_attributes_precedence.each do |_key, lookup_attribute|
98
+ model_found = false
99
+ joins = lookup_attribute.split(".")
100
+ query = self
101
+
102
+ # We don’t add organization_id in the query if organization_id is present in lookup_attributes_precedence, because organization_id is no longer in users.
103
+ query = query.where(organization_id: organization_id) if organization_id.present? && !organization_attribute_exist?
104
+
105
+ if joins.size > 1
106
+ joins[0...-1].each do |join_table|
107
+ table_name = self.reflect_on_association(join_table.singularize.to_sym)&.klass ? join_table.singularize.to_sym : join_table.to_sym # only singularize association if it's needed
108
+ query = query.joins(table_name) # the join need to be singularized and the where query uses the full string as 'demographics.ssn'
109
+ end
110
+ end
111
+
112
+ if case_sensitive || lookup_attribute.to_s.include?("id") || [:text, :string].exclude?(get_column_type(lookup_attribute))
113
+ model = query.where("#{lookup_attribute} = :param_value", param_value: value.to_s)
114
+ else
115
+ model = query.where("LOWER(#{lookup_attribute}) = :param_value", param_value: value.to_s.downcase)
116
+ end
117
+ ::ActiveRecord::Base.transaction do
118
+ model = model.limit(2) if model && !return_multiple # Limiting the query to retrieve a maximum of 2 records for improved performance and to prevent unnecessary processing.
119
+ model_found = true if model.length > 0
120
+ rescue => e
121
+ # In case of errors for example when we iterate over the lookup_attributes_precedence and try to match
122
+ # an string with an uuid (id), avoid the error and continue looking for the next attribute
123
+ ::Rails.logger.info "SQL error in #{__method__}: #{e.message}"
124
+ ::ActiveRecord::Base.connection.rollback_db_transaction
125
+ model = nil
126
+ model_found = false
127
+ end
128
+ break if model_found
129
+ end
130
+ model
131
+ end
132
+
133
+ def is_valid_association(association_values)
134
+ # the association needs to be singular, example for Clients : "demographic.ssn"
135
+ # the association would be "demographic" and the column name "ssn"
136
+ validation = self
137
+ association_values_without_last = association_values[0...-1] # Create a new array without the last element
138
+ column_name = association_values.last
139
+ association_values_without_last.each do |association|
140
+ validation = self.reflect_on_association(association.singularize.to_sym)&.klass
141
+ validation ||= self.reflect_on_association(association.to_sym)&.klass
142
+ end
143
+ validation&.column_names&.include?(column_name)
144
+ end
145
+
146
+ # Returns an array of valid columns for extraction search, such as advisor_id and advisor_import_key.
147
+ # The optional 'prefix' parameter appends a prefix to each column name (prefix + lookup attribute precedence),
148
+ # e.g., advisor_id, client_id, firm_id.
149
+ # When multi is set, the function generates columns with instance numbers,
150
+ # e.g., advisor_one_id, advisor_two_id, up to multi (advisor_multi_id).
151
+ def supported_columns_lookup(prefix = nil, multi = nil)
152
+ @prefix_supported_columns ||= {}
153
+ @prefix_supported_columns ["#{prefix}, #{multi}"] ||=
154
+ begin
155
+ keys_without_prefix = self.lookup_attributes_precedence.keys
156
+ return prefix.present? ? keys_without_prefix.map { |column| [prefix, column].join("_") } : keys_without_prefix.map(&:to_s) unless multi
157
+
158
+ keys_supported_columns = []
159
+ (1..multi).each do |n|
160
+ digit = n.humanize.underscore.despace
161
+ keys_without_prefix.each do |key|
162
+ keys_supported_columns << [prefix, digit, key].compact.join("_")
163
+ end
164
+ end
165
+ keys_supported_columns
166
+ end
167
+ end
168
+
169
+ # In case we have a nested association and an organization_id , iterate over and add the neccessary joins
170
+ def add_joins(params, query, organization_id)
171
+ params.each do |key, _value|
172
+ joins = key.to_s.split(".")
173
+ if joins.size > 1
174
+ joins[0...-1].each do |join_table|
175
+ association_klass = ::HasHelpers::ApplicationHelper.translate_klass_name(join_table.singularize.capitalize).safe_constantize
176
+ table_name = self.reflect_on_association(join_table.singularize.to_sym)&.klass ? join_table.singularize.to_sym : join_table.to_sym # only singularize association if it's needed
177
+ query = query.joins(table_name)
178
+ query = query.where("#{join_table}.organization_id = (?)", organization_id) if organization_id.present? && association_klass.present? && association_klass.has_attribute?(:organization_id)
179
+ end
180
+ end
181
+ end
182
+ query = query.where(organization_id: organization_id) if organization_id.present? && params.any? && !organization_attribute_exist?
183
+ query
184
+ end
185
+
186
+ # This is a list of attributes of the model that will be used to search by.
187
+ # Each model should override this method to lookup by simple attribute. e.g. Advisor.lookup("123456", 4) where "123456" is the unique import_key
188
+ # Note that nested associations should be defined as: table_name.attribute e.g. demographics.ssn
189
+ # Example for Advisor -> lookup_attributes_precedence = { id: "id", import_key: "import_key", code_name: "demographics.code_name", ssn: "demographics.ssn"}
190
+
191
+ def lookup_attributes_precedence
192
+ @lookup_attributes_precedence ||= {
193
+ id: "id"
194
+ }
195
+ end
196
+
197
+ # Checks whether the lookup_attributes_precedence hash contains the key :organization_id.
198
+ # Returns true if the key exists
199
+ def organization_attribute_exist?
200
+ self.lookup_attributes_precedence.key?(:organization_id)
201
+ end
202
+
203
+ # An array of columns that should be ignored or excluded, by default is an empty array
204
+ def ignore_columns
205
+ []
206
+ end
207
+
208
+ def dn_columns
209
+ self.column_names.select { |item| item.end_with?("_dn") }
210
+ end
211
+
212
+ # returns the available columns for creating a new object of the model class.
213
+ def klass_available_columns
214
+ self.column_names + klass_belongs_to_available_columns - ignore_columns - dn_columns - metadata_colums
215
+ end
216
+
217
+ def klass_belongs_to_available_columns
218
+ self.reflections.select { |_association_name, reflection| reflection.macro == :belongs_to }&.keys # client -> client_status, firm, organization
219
+ end
220
+
221
+ # This method assign the data considering associations and user context.
222
+ # returns a hash containing the hydrated model and any associations with errors.
223
+ def assign_data(data, prefix: nil, current_user: nil, multi: nil)
224
+ klass = self
225
+ model = klass.new
226
+
227
+ reflections = klass.reflections # has_many, belons_to, has_one
228
+ date_columns = klass.columns.select { |column| column.type == :date }.map(&:name) # start_date, end_date
229
+ columns = klass.column_names - reflections&.keys - date_columns # name, is_adjustment, note
230
+
231
+ associations_with_errors = {}
232
+
233
+ columns.each do |column|
234
+ model[column] = data[key_with_prefix(column, prefix)] if data[key_with_prefix(column, prefix)].present?
235
+ end
236
+
237
+ date_columns.each do |column|
238
+ model.assign_attributes({ column.to_sym => data[key_with_prefix(column, prefix)] }) if data[key_with_prefix(column, prefix)].present?
239
+ end
240
+
241
+ if current_user.present?
242
+ model.organization = current_user&.organizations&.first
243
+ model.created_by_id = current_user&.id if model.has_attribute?("created_by_id")
244
+ model.updated_by_id = current_user&.id if model.has_attribute?("updated_by_id")
245
+ end
246
+
247
+ reflections.each do |association_name, reflection|
248
+ result = nil
249
+ if !metadata_colums.include?(association_name)
250
+ association = reflection.options[:class_name].present? ? reflection.options[:class_name].safe_constantize : (reflection.klass || reflection.active_record)
251
+ is_constant = association.include?(::HasHelpers::Constant)
252
+ if is_constant
253
+ if data[key_with_prefix("#{association_name}_id", prefix)].present?
254
+ model.send("#{association_name}=", association.lookup(id: data[key_with_prefix("#{association_name}_id", prefix)]))
255
+ else
256
+ constant_value = data[key_with_prefix(association_name, prefix)] || data[key_with_prefix("#{association_name}_name", prefix)] # examples: policy_holder_type, policy_holder_type_id, policy_holder_type_name because the association name is only 'policy_holder_type'
257
+ model.send("#{association_name}=", association.lookup(constant_value)) if constant_value.present?
258
+ end
259
+ else
260
+ case reflection.macro
261
+ when :belongs_to, :has_one
262
+ association_data = remove_prefix(association, data, prefix.present? ? "#{prefix}_#{association_name}" : association_name)
263
+ if association_data.any?
264
+ result = association.lookup(association_data, model&.organization_id)
265
+ result.nil? ? associations_with_errors[association_name] = association : model.send("#{association_name}=", result)
266
+ end
267
+ when :has_many
268
+ if multi.present?
269
+ (1..multi).each do |n|
270
+ digit = n.humanize.underscore.despace
271
+ association_name = association_name.singularize
272
+ association_data = remove_prefix(association, data, prefix.present? ? "#{prefix}_#{association_name}_#{digit}" : "#{association_name}_#{digit}")
273
+ if association_data.any?
274
+ # TODO: improve for User model that no longer has organization_id
275
+ result = model.has_attribute?(:organization_id) ? association.lookup(association_data, nil) : association.lookup(association_data, model&.organization_id)
276
+ if result.present?
277
+ model.send("#{association_name.pluralize}") << result
278
+ else
279
+ associations_with_errors[association] = association_name
280
+ end
281
+ end
282
+ end
283
+
284
+ end
285
+
286
+ end
287
+ end
288
+ end
289
+ end
290
+ {
291
+ model: model,
292
+ associations_with_errors: associations_with_errors
293
+ }
294
+ end
295
+
296
+ # Custom method for constructing AND/OR conditions based on input parameters
297
+ # params example: {:"organizations.id"=>8, :name=>"Gayla Barrows"}
298
+ def query_condition(params, case_sensitive, model = nil, condition = LogicalOperator::AND)
299
+ table_name = self.table_name
300
+ conditions = []
301
+ query_params = {}
302
+
303
+ params.each do |column, value|
304
+ column_name_query = column.to_s.tr(".", "_") # convert column names to a standardized format for consistency example: organizations.id -> :organizations_id, demographic.ssn -> :demographic_ssn
305
+ association_values = column.to_s.split(".")
306
+
307
+ # If the column is part of an association (e.g., demographics.name),
308
+ # use the column name as it is. If it's not an association (e.g., name, id, import_key),
309
+ # set the table_column as "#{table_name}.#{column}" (e.g., users.name, users.id, users.import_key).
310
+ table_column = association_values.size > 1 ? column : "#{table_name}.#{column}"
311
+
312
+ if case_sensitive || column.to_s.include?("id") || [:text, :string].exclude?(get_column_type(table_column))
313
+ conditions << "#{table_column} = :#{column_name_query}"
314
+ else
315
+ conditions << "LOWER(#{table_column}) = :#{column_name_query}"
316
+ end
317
+
318
+ # a hash of query parameters for use in the WHERE clause {:organizations_id=>1, :users_name=>"Naomi'Lynch"}
319
+ query_params[column_name_query.to_sym] = case_sensitive ? value : value.to_s.downcase
320
+ end
321
+
322
+ query = conditions.join(" #{condition} ") # "organizations.id = :organizations_id OR users.name = :users_name"
323
+ model.nil? ? where(query, query_params) : model.where(query, query_params)
324
+ end
325
+
326
+ def get_column_type(column_or_attribute)
327
+ model_name, column_name = column_or_attribute.to_s.split(".")
328
+
329
+ if model_name.blank? || column_name.blank?
330
+ return self.columns_hash[column_or_attribute]&.type
331
+ end
332
+
333
+ if model_name == self.table_name
334
+ return self.columns_hash[column_name]&.type
335
+ end
336
+
337
+ reflection = self.reflections[model_name&.singularize] || self.reflections[model_name]
338
+ class_name = reflection&.klass || model_name.classify.safe_constantize
339
+
340
+ class_name&.columns_hash&.dig(column_name)&.type
341
+ end
342
+
343
+ def has_recently_viewed(*resources)
344
+ ::HasHelpers.recently_viewed_resources = resources.map { |r| r.to_s.demodulize.tableize.to_sym }
345
+
346
+ class_eval do
347
+ # Returns the recently viewed records for the given association.
348
+ #
349
+ # Examples
350
+ # class User < ActiveRecord::Base
351
+ # has_recently_viewed :advisor, :policy, "HasOutlook::DiscussionTemplateConfig"
352
+ # end
353
+ # user = ::HasHelpers::User.find(id)
354
+ #
355
+ # user.recently_viewed(:advisor)
356
+ # user.recently_viewed(:discussion_template_config)
357
+ def recently_viewed(association_name)
358
+ select_clauses = ["recently_vieweds.*"]
359
+ join_clauses = []
360
+
361
+ resource_klass = ::HasHelpers::RecentlyViewed.reflect_on_association(association_name).klass
362
+ resource_base_klass = resource_klass.base_class
363
+
364
+ # Detect owner view
365
+ unless resource_base_klass.instance_variable_defined?(:@owner_column_names)
366
+ owner_relation_name = "owner_#{ resource_base_klass.table_name }_view"
367
+ resource_base_klass.instance_variable_set(:@owner_relation_name, owner_relation_name)
368
+
369
+ if resource_base_klass.respond_to?(:owner_klass) && resource_base_klass.owner_klass
370
+ resource_base_klass.instance_variable_set(:@owner_column_names, resource_base_klass.owner_klass.column_names)
371
+ elsif ::HasHelpers::ActiveRecord.relation_exists?(owner_relation_name) # Check if the owner view is present.
372
+ # Detect column names for owner view.
373
+ resource_base_klass.instance_variable_set(
374
+ :@owner_column_names,
375
+ Class.new(::ActiveRecord::Base) do
376
+ self.table_name = owner_relation_name
377
+ end.column_names
378
+ )
379
+ else
380
+ # No owner view detected so join in the resource records
381
+ resource_base_klass.instance_variable_set(:@owner_relation_name, resource_base_klass.table_name)
382
+ resource_base_klass.instance_variable_set(
383
+ :@owner_column_names,
384
+ (resource_base_klass.column_names & %w[id name username number type])
385
+ )
386
+ end
387
+ end
388
+
389
+ # Add select clauses for owner columns.
390
+ owner_relation_name = resource_base_klass.instance_variable_get(:@owner_relation_name)
391
+ resource_base_klass.instance_variable_get(:@owner_column_names).each do |owner_column|
392
+ select_clauses << "#{ owner_relation_name }.#{ owner_column } AS owner_#{ owner_column }"
393
+ end
394
+
395
+ join_clauses << "INNER JOIN #{ owner_relation_name } ON #{ owner_relation_name }.id = recently_vieweds.#{ resource_base_klass.name.foreign_key }"
396
+ type_scope = ::HasHelpers::RecentlyViewed.where(owner_relation_name => { type: resource_klass }) if resource_klass != resource_base_klass
397
+
398
+ scope = ::HasHelpers::RecentlyViewed.
399
+ select(select_clauses.join(", ")).
400
+ joins(join_clauses.join(" ")).
401
+ where(created_by_id: id).
402
+ where(organization_id: organization_id). # this included so that we only include records from the user's primary organization
403
+ where("recently_vieweds.#{ resource_base_klass.name.foreign_key } IS NOT NULL").
404
+ limit(::HasHelpers::HISTORY_MAX) # Needed because STI parent classes may have more records than the limit, up to number_of_descendant_classes * history_max.
405
+
406
+ # type_scope may be nil or may scope to a sub-type, e.g. where(policies: { type: "Application" })
407
+ if type_scope
408
+ scope = scope.merge(type_scope)
409
+ end
410
+
411
+ scope
412
+ end
413
+
414
+ # this association is only used for the destroy callback
415
+ has_many :all_recently_viewed, dependent: :destroy, foreign_key: "created_by_id", class_name: "::HasHelpers::RecentlyViewed"
416
+ end
417
+
418
+ resources.each do |r|
419
+ base_resource_klass = r.to_s.classify.constantize
420
+
421
+ # Add association to the given resource class and all of its descendants.
422
+ [base_resource_klass, *base_resource_klass.descendants].each do |resource_klass|
423
+ resource_klass.class_eval do
424
+ has_many :all_recently_viewed, dependent: :destroy, class_name: "::HasHelpers::RecentlyViewed", foreign_key: r.to_s.singularize.foreign_key
425
+ end
426
+ end
427
+
428
+ ::HasHelpers::RecentlyViewed.class_eval do
429
+ belongs_to r.to_s.demodulize.singularize.underscore.to_sym, class_name: r.to_s.classify, optional: true
430
+
431
+ base_resource_klass.descendants.each do |descendant_klass|
432
+ belongs_to descendant_klass.name.demodulize.singularize.underscore.to_sym, foreign_key: r.to_s.singularize.foreign_key, optional: true
433
+ end
434
+ end
435
+ end
436
+ end
437
+
438
+ # Enable favorites
439
+ #
440
+ # === Examples
441
+ # Create a favorite with the default limit trigger and validation:
442
+ # has_favorites limit: 5
443
+ #
444
+ # Create a favorite with a custom validation:
445
+ # has_favorites validate_proc: ->(){ errors.add(:base, "Too many favorites") if limit > some_limit }
446
+ #
447
+ # Create a favorite with a custom limit trigger:
448
+ # class LimitReportFavorites < ::HasHelpers::LimitFavorites
449
+ # def trigger_function
450
+ # <<-SQL
451
+ # CREATE OR REPLACE FUNCTION public."#{function_name}"() RETURNS TRIGGER LANGUAGE plpgsql AS $function$
452
+ # DECLARE
453
+ # row_limit INTEGER;
454
+ # nrows INTEGER;
455
+ # resource_id TEXT;
456
+ # BEGIN
457
+ # row_limit := #{limit};
458
+ #
459
+ # SELECT
460
+ # COUNT(*) INTO nrows
461
+ # FROM favorites
462
+ # INNER JOIN report.reports AS reports ON reports.id = favorites.report_id
463
+ # WHERE favorites.created_by_id = NEW.created_by_id
464
+ # AND reports.resource_id = (SELECT r.resource_id FROM report.reports AS r WHERE r.id = NEW.report_id);
465
+ #
466
+ # IF nrows > row_limit THEN
467
+ # SELECT
468
+ # format('%s-%s', 'Report', NEW.report_id::text)
469
+ # INTO resource_id;
470
+ #
471
+ # RAISE NOTICE 'favorite limit: resource_id = %', resource_id;
472
+ # RAISE 'Exceeded favorite limit of % for %', row_limit, resource_id;
473
+ # END IF;
474
+ #
475
+ # RETURN NULL;
476
+ # END
477
+ # $function$;
478
+ # SQL
479
+ # end
480
+ # end
481
+ #
482
+ # has_favorites limit_trigger: ::LimitReportFavorites.new(limit: 5)
483
+ def has_favorites(limit: nil, validate_proc: nil, limit_trigger: nil)
484
+ class_name = self.name.demodulize
485
+ association_name = class_name.underscore.to_sym
486
+ foreign_key = class_name.foreign_key.to_sym
487
+
488
+ has_many :favorites, class_name: "::HasHelpers::Favorite", dependent: :destroy
489
+ ::HasHelpers::Favorite.belongs_to association_name, class_name: self.name, optional: true
490
+ ::HasHelpers::Favorite.has_owner association_name if ::HasHelpers::Favorite.respond_to?(:has_owner)
491
+
492
+ if limit
493
+ ::HasHelpers::LimitFavorites.triggers << ::HasHelpers::LimitFavorites.new(klass: self, limit: limit)
494
+
495
+ ::HasHelpers::Favorite.validate if: foreign_key do
496
+ if ::HasHelpers::Favorite.where(created_by_id: created_by_id).where.not(foreign_key => nil).count >= limit
497
+ errors.add(association_name, "Favorites are limited to #{limit} per #{association_name}.")
498
+ end
499
+ end
500
+ end
501
+
502
+ if validate_proc
503
+ ::HasHelpers::Favorite.validate(if: foreign_key, &validate_proc)
504
+ end
505
+
506
+ if limit_trigger
507
+ ::HasHelpers::LimitFavorites.triggers << limit_trigger
508
+ end
509
+ end
510
+
511
+ # TODO remove this in the future when elastic search or web services are implemented
512
+ def self.relation_exists?(relation_name)
513
+ ::ActiveRecord::Base.connection.execute("SELECT 1 FROM #{ relation_name } LIMIT 1") rescue false
514
+ end
515
+ end
516
+ end
517
+
518
+ ::ActiveRecord::Base.extend(::HasHelpers::ActiveRecord)
519
+ ::ActiveRecord::Base.extend(::HasHelpers::ActiveRecord::DateRange)
520
+ ::ActiveRecord::Base.extend(::HasHelpers::ActiveRecord::Validation)
@@ -0,0 +1,107 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ require "delegate"
5
+
6
+ module App
7
+ module Command
8
+ class FailureError < StandardError; end
9
+
10
+ Result = Struct.new(:explicit_value, :error, :exception) do
11
+ attr_accessor :failed, :explicit_value_is_set
12
+
13
+ # Accept Hash values for attributes.
14
+ # Result.new(error: "Something bad happened") # => #<struct value=nil, error="Something bad happened">
15
+ # Result.new(invalid_attr: nil) # => ArgumentError: Unknown struct member: :invalid_attr
16
+ def initialize(**attrs)
17
+ unknown_attrs = attrs.keys - self.class.members
18
+ raise ::ArgumentError, "Unknown struct member#{ unknown_attrs.length == 1 ? "" : "s" }: #{ unknown_attrs.map(&:inspect).join(", ") }" unless unknown_attrs.empty?
19
+ super(*attrs.values_at(*self.class.members))
20
+ end
21
+
22
+ def fail(msg = nil)
23
+ self.failed = true
24
+ self.error ||= msg
25
+ end
26
+
27
+ def fail!(msg = nil)
28
+ fail(msg)
29
+ raise FailureError, error
30
+ end
31
+
32
+ def value=(v)
33
+ self.explicit_value_is_set = true
34
+ self.explicit_value = v
35
+ end
36
+
37
+ def value
38
+ explicit_value
39
+ end
40
+ end
41
+
42
+ class ResultProxy < ::SimpleDelegator
43
+ def success?
44
+ !(failed || error)
45
+ end
46
+ end
47
+
48
+ def self.included(klass)
49
+ unless klass.method_defined?(:run)
50
+ klass.include(StubbedRunInstanceMethod)
51
+ end
52
+
53
+ klass.prepend(MaskingInstanceMethods)
54
+ klass.extend(ClassMethods)
55
+
56
+ klass.class_eval do
57
+ attr_writer :result
58
+ private :result= # We don't want users to set the result object, so it is made private (not that it would _really_ stop them from doing so).
59
+ end
60
+ end
61
+
62
+ module StubbedRunInstanceMethod
63
+ # Provides a default method which can serve as the super method for the masking method.
64
+ def run(...)
65
+ raise ::NotImplementedError
66
+ end
67
+ end
68
+
69
+ module MaskingInstanceMethods
70
+ # Mask the user-defined (or stubbed) run method and wrap it with some functionality.
71
+ # This provides a Result object which is accessible within the user-defined run method.
72
+ # This method should never raise an exception, but rather it should always return a result.
73
+ def run(...)
74
+ # Execute the user-defined run method.
75
+ begin
76
+ run_return_value = super(...)
77
+ result.value = run_return_value unless result.explicit_value_is_set
78
+ rescue Exception => e # rubocop:disable Lint/RescueException
79
+ result.error = e.message
80
+ result.exception = e
81
+ end
82
+
83
+ # Return the result.
84
+ ResultProxy.new(result)
85
+ end
86
+
87
+ def result
88
+ @result ||= Result.new
89
+ end
90
+
91
+ # Delegate fails to the result object.
92
+ def fail(*args)
93
+ result.fail(*args)
94
+ end
95
+
96
+ def fail!(*args)
97
+ result.fail!(*args)
98
+ end
99
+ end
100
+
101
+ module ClassMethods
102
+ def run(...)
103
+ new.run(...)
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,10 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require "has_helpers/app/command"
5
+
6
+ module App
7
+ def self.eager_load!
8
+ Zeitwerk::Loader.eager_load_all
9
+ end
10
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HasHelpers
4
+ module AssetsPrecompileHelper
5
+ class << self
6
+ def is_precompiling_assets?
7
+ !!(::Rake.try(:application)&.try(:top_level_tasks)&.select { |task| task =~ /^assets\:/ }&.any?)
8
+ end
9
+
10
+ def mock_sidekiq_limiters
11
+ require "sidekiq"
12
+ require "sidekiq-ent"
13
+ Sidekiq::Limiter.singleton_class.prepend(Module.new do
14
+ def concurrent(...); end
15
+ def window(...); end
16
+ end)
17
+ end
18
+ end
19
+ end
20
+ end