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,310 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ module Reports
5
+ module Generator
6
+ class GeneratorError < StandardError
7
+ ERROR_MSG = "%{error_type} ERROR (%{option_type} - %{option_name})"
8
+ MISSING_HAS_REPORTS_MSG = "Unable to generate report options for %{class_name}: missing has_reports"
9
+
10
+ # Allows for accumulating errors.
11
+ def initialize(full_error_list: nil)
12
+ full_message = full_error_list&.reject(&:blank?)&.join("\n")
13
+ super(full_message)
14
+ end
15
+
16
+ def self.create_backtrace_string(base_error: nil)
17
+ if base_error
18
+ [
19
+ "-" * 20,
20
+ base_error.message,
21
+ *base_error.backtrace.map { |line| "> #{ line }" }
22
+ ].join("\n")
23
+ else
24
+ nil
25
+ end
26
+ end
27
+
28
+ def self.create_error_message(msg: "", base_error: nil)
29
+ [
30
+ msg,
31
+ create_backtrace_string(base_error: base_error)
32
+ ].reject(&:blank?).join("\n")
33
+ end
34
+ end
35
+
36
+ # This is a scipt to run the options generator for a set of classes.
37
+ # It will generate the select options, filter options, and expansions for the
38
+ # given classes.
39
+ # This should be run on the project who wants ti use the reproting app.
40
+
41
+ #=== Arguments
42
+ # +klasses+: A collection of the classes for which options will be generated. Eg. `Generator.run([Advisor, Policy])`
43
+ #
44
+ #=== Options
45
+ # +:schema+: Optional schema name. This will be the default schema for all table names.
46
+ #
47
+ #=== Examples
48
+ # ::Reports::Generator.run(Advisor)
49
+ # ::Reports::Generator.run_all --> to create resources and run for all flatteners
50
+ def self.run(
51
+ klasses,
52
+ # audit: true,
53
+ verbose: false,
54
+ **options
55
+ )
56
+ aggregate_errors = []
57
+
58
+ # Generate the options inside a transaction. This ensures that if there
59
+ # are any errors, we can rollback the entire generate, including any
60
+ # modifications made in the `before_generate` hook.
61
+ ::ActiveRecord::Base.transaction do
62
+ # if defined? @@before_generate_block
63
+ # class_eval(&@@before_generate_block)
64
+ # end
65
+
66
+ begin
67
+ Array(klasses).each do |klass|
68
+ # Check klass for has_reports
69
+ # raise GeneratorError.new(full_error_list: GeneratorError::MISSING_HAS_REPORTS_MSG % { class_name: klass.name }) unless klass.has_reports(meta_only: true)
70
+ # just one request per class
71
+ # each time we run the generator we need to check if all flatteners are created on db as a resource
72
+ check_resources
73
+ response = Reports::ExternalGraphqlClient.query_resource_by_name(name: klass.name)
74
+ resource_id = response.dig("data", "resourceName", "id")
75
+ # Select options
76
+ begin
77
+ # first generate the resource if it doesnt exist
78
+ # call the mutation to generate the resource
79
+ select_option_attributes = generate_select_options(klass, resource_id, **options)
80
+ importer = ::Reports::Importer.new(resource_id, resource_name: klass.name, **options)
81
+ # select_option_attributes.uniq! { |attrs| attrs[:name] } # FIXME: temporary work-around until duplicate entries are fixed - Gotta test those reports!!! *
82
+ errors = importer.import_select_options(select_option_attributes, resource_id, verbose: verbose)
83
+ if errors.any?
84
+ aggregate_errors.concat(errors)
85
+ end
86
+ rescue ::Reports::Importer::AuditError => e
87
+ aggregate_errors << GeneratorError.create_error_message(msg: GeneratorError::ERROR_MSG % { error_type: "AUDIT", option_type: "SelectOption", option_name: klass.name }, base_error: e)
88
+ end
89
+ # Filter options
90
+ begin
91
+ importer = ::Reports::Importer.new(resource_id, resource_name: klass.name, **options)
92
+ filter_option_attributes = generate_filter_options(klass, resource_id, **options)
93
+ # filter_option_attributes.uniq! { |attrs| attrs[:name] } # FIXME: temporary work-around until duplicate entries are fixed - Gotta test those reports!!! *
94
+ errors = importer.import_filter_options(filter_option_attributes, resource_id, verbose: verbose)
95
+ if errors.any?
96
+ aggregate_errors.concat(errors)
97
+ end
98
+ rescue ::Reports::Importer::AuditError => e
99
+ aggregate_errors << GeneratorError.create_error_message(msg: GeneratorError::ERROR_MSG % { error_type: "AUDIT", option_type: "FilterOption", option_name: klass.name }, base_error: e)
100
+ end
101
+ end
102
+ rescue => e
103
+ aggregate_errors << GeneratorError.create_error_message(msg: "FATAL EXCEPTION", base_error: e)
104
+ end
105
+ # Return errors
106
+ aggregate_errors
107
+ end
108
+ end
109
+
110
+ def self.create_resources(klass_name = nil)
111
+ # This method is used to ensure that the resources are created in the database.
112
+ # It will create the resources if they do not exist.
113
+ # It will return an array of errors if any resources could not be created.
114
+ response_messages = []
115
+ if klass_name
116
+ response = Reports::ExternalGraphqlClient.query_resource_by_name(name: klass_name)
117
+ resource_id = response.dig("data", "resourceName", "id")
118
+ unless resource_id
119
+ # If not found, create it using the mutation
120
+ create_resource = Reports::ExternalGraphqlClient.create_resource(mutation_name: "createResource", input_name: "ReportResourceInput", attributes: { name: klass_name, type: "HasReports::Resource" })
121
+ if create_resource["errors"]
122
+ response_messages << "Failed to create resource for flattener #{klass_name}: #{create_resource["errors"]}"
123
+ else
124
+ response_messages << "Successfully created resource for flattener #{klass_name}"
125
+ end
126
+ end
127
+ else
128
+ flatteners = all_flattener_classes
129
+ flatteners.each do |flattener|
130
+ # Check if the resource exists via the external GraphQL client
131
+ flattener_name = flattener.name.sub(/^Flatteners::/, "") # Remove the Flatteners:: prefix
132
+ response = Reports::ExternalGraphqlClient.query_resource_by_name(name: flattener_name)
133
+ resource_id = response.dig("data", "resourceName", "id")
134
+ unless resource_id
135
+ # If not found, create it using the mutation
136
+ create_resource = Reports::ExternalGraphqlClient.create_resource(
137
+ mutation_name: "createResource",
138
+ input_name: "ReportResourceInput",
139
+ attributes: {
140
+ name: flattener_name,
141
+ type: "HasReports::Resource"
142
+ }
143
+ )
144
+ if create_resource["errors"]
145
+ response_messages << "Failed to create resource for flattener #{flattener_name}: #{create_resource["errors"]}"
146
+ else
147
+ response_messages << "Successfully created resource for flattener #{flattener_name}"
148
+ end
149
+ end
150
+ end
151
+ end
152
+ response_messages
153
+ end
154
+
155
+ def self.check_resources
156
+ # Fetch all resources with error handling
157
+ all_resources = Reports::ExternalGraphqlClient.query_all_resources
158
+ unless all_resources.key?("data")
159
+ Rails.logger.error "Failed to fetch resources: #{all_resources.inspect}"
160
+ return []
161
+ end
162
+ resource_array = all_resources.dig("data", "resources") || []
163
+ # Get all flattener classes more efficiently
164
+ flatteners = all_flattener_classes
165
+ # Extract resource names once
166
+ resource_names = (resource_array["edges"] || []).filter_map { |edge| edge["node"]&.dig("name")&.demodulize }.to_set
167
+ # Find and process missing flatteners
168
+ missing_flatteners = flatteners.each_with_object([]) do |flattener, arr|
169
+ flattener_name = flattener.name.demodulize
170
+ arr << flattener unless resource_names.include?(flattener_name)
171
+ end
172
+ # Create missing resources with logging
173
+ missing_flatteners.each do |flattener|
174
+ flattener_name = flattener.name.demodulize
175
+ next if flattener_name.blank?
176
+
177
+ create_resources(flattener_name)
178
+ end
179
+
180
+ missing_flatteners
181
+ end
182
+
183
+ def self.run_all
184
+ response_messages = []
185
+ flatteners = all_flattener_classes
186
+ flatteners.each do |flattener|
187
+ # Check if the resource exists via the external GraphQL client
188
+ flattener_name = flattener.name.sub(/^Flatteners::/, "") # Remove the Flatteners:: prefix
189
+ response = Reports::ExternalGraphqlClient.query_resource_by_name(name: flattener_name)
190
+ resource_name = response.dig("data", "resourceName", "name")
191
+ if resource_name.present?
192
+ run(resource_name.constantize) unless resource_name.to_s.demodulize == "UserOrganization"
193
+ else
194
+ # If not found, create it using the mutation
195
+ create_resource = Reports::ExternalGraphqlClient.create_resource(
196
+ mutation_name: "createResource",
197
+ input_name: "ReportResourceInput",
198
+ attributes: {
199
+ name: flattener_name,
200
+ type: "HasReports::Resource"
201
+ }
202
+ )
203
+ if create_resource["errors"]
204
+ response_messages << "Failed to create resource for flattener #{flattener_name}: #{create_resource["errors"]}"
205
+ else
206
+ resource_name = Reports::ExternalGraphqlClient.query_resource_by_name(name: flattener_name)
207
+ resource_name = resource_name.dig("data", "resourceName", "name")
208
+ klass = resource_name&.safe_constantize
209
+ if klass
210
+ run(klass)
211
+ response_messages << "Successfully created resource for flattener #{flattener_name}"
212
+ else
213
+ response_messages << "Resource created but class not found for #{flattener_name}"
214
+ end
215
+ end
216
+ end
217
+ end
218
+ end
219
+
220
+ def self.generate_select_options(klass, resource_id, **options)
221
+ results = []
222
+ results.concat(::Reports::BaseOptions.generate_select_options(klass, resource_id, **options).to_a)
223
+ results.concat(::Reports::ConstantOptions.generate_select_options(klass, **options).to_a)
224
+ results.concat(::Reports::BelongsToOptions.generate_select_options(klass, **options).to_a)
225
+ results.concat(::Reports::HasManyOptions.generate_select_options(klass, **options).to_a)
226
+ results.concat(::Reports::NestedAttributeOptions.generate_select_options(klass, **options).to_a)
227
+ results
228
+ end
229
+
230
+ def self.generate_filter_options(klass, resource_id, **options)
231
+ [].tap do |results|
232
+ results.concat(::Reports::BaseOptions.generate_filter_options(klass, **options).to_a)
233
+ results.concat(::Reports::ConstantOptions.generate_filter_options(klass, **options).to_a)
234
+ results.concat(::Reports::BelongsToOptions.generate_filter_options(klass, resource_id, **options).to_a)
235
+ results.concat(::Reports::HasManyOptions.generate_filter_options(klass, **options).to_a)
236
+ results.concat(::Reports::NestedAttributeOptions.generate_filter_options(klass, **options).to_a)
237
+ end
238
+ end
239
+
240
+ # Lists all flatteners defined by reading the filesystem.
241
+ # This is used to be able to obtain flattener names with prefixes like HasHelpers::Organization.
242
+ def self.all_flattener_classes
243
+ base_path = Rails.root.join("app/models/flatteners")
244
+ Dir.glob(base_path.join("**/*.rb")).filter_map do |path|
245
+ const_name = Pathname.new(path).
246
+ relative_path_from(base_path.parent).
247
+ to_s.
248
+ delete_suffix(".rb").
249
+ split("/").
250
+ map(&:camelize).
251
+ join("::")
252
+ const_name.safe_constantize
253
+ end.select { |c| c.is_a?(Class) && c < Flattener::Base }
254
+ end
255
+
256
+ def self.custom_select_options(klass, &block)
257
+ if block
258
+ previous_custom_select_options = custom_select_options_cache[klass]
259
+ custom_select_options_cache[klass] = proc do |results = []|
260
+ # Invoke the previous callback (if any)
261
+ previous_custom_select_options.call(results) if previous_custom_select_options
262
+ results.concat(block.call)
263
+ end
264
+ else
265
+ custom_select_options_cache[klass].nil? ? [] : custom_select_options_cache[klass].call
266
+ end
267
+ end
268
+
269
+ #=== Examples
270
+ # # Removing redundant terms - in this case a second consecutive "Client" is removed.
271
+ # build_option_name(:primary, :client, :client_id, preserve_id: false) # => "Primary Client Id"
272
+ def self.build_option_name(*parts, column_type: nil, preserve_id: false)
273
+ # Create an enumerator which yields the words used to build the name.
274
+ words = Enumerator.new do |yielder|
275
+ parts.lazy.
276
+ map(&:to_s).
277
+ map do |part|
278
+ if column_type == "boolean"
279
+ part.gsub(/(^|_)is_/, '\1')
280
+ else
281
+ part
282
+ end
283
+ end.
284
+ map do |part|
285
+ if preserve_id
286
+ part.tr("_", " ") # Prevents titleize from removing "_id"
287
+ else
288
+ part
289
+ end
290
+ end.
291
+ map(&:titleize).
292
+ each do |part|
293
+ # Yield each word separately. E.g. "Client Id" => ["Client", "Id"]
294
+ part.split(/\s+/).each do |sub_part|
295
+ sub_part = sub_part.gsub("Id", "ID")
296
+ yielder << sub_part
297
+ end
298
+ end
299
+ end
300
+
301
+ # Get all the parts of the name, excluding consecutive repeated elements.
302
+ sanitized_parts = [*words.first] # Always include the first word
303
+ words.each_cons(2) do |part1, part2|
304
+ sanitized_parts << part2 unless part1 == part2
305
+ end
306
+
307
+ sanitized_parts.join(" ")
308
+ end
309
+ end
310
+ end
@@ -0,0 +1,105 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ module Reports
5
+ module HasManyOptions
6
+ # Generate select options (attributes) for all associations with nested
7
+ # attributes for the given class.
8
+ def self.generate_select_options(klass, nested_as: nil, **_options)
9
+ generate_options(
10
+ klass: klass,
11
+ type: "Select",
12
+ nested_as: nested_as
13
+ )
14
+ end
15
+
16
+ def self.generate_filter_options(klass, nested_as: nil, **_options)
17
+ generate_options(klass: klass, type: "Filter", nested_as: nested_as)
18
+ end
19
+
20
+ def self.flattener(klass)
21
+ Flattener.for(klass).pivot_or_self
22
+ end
23
+
24
+ # Returns a lazy enumerator which yields the belongs_to associations (only those available for reports) for the given class.
25
+ def self.has_many_associations(klass)
26
+ flattener(klass).associations.lazy.
27
+ reject(&:is_constant?).
28
+ reject(&:is_nested_attributes?).
29
+ select { |association| association.macro == :has_many }.
30
+ reject { |association| association.ar_association.through_reflection }.
31
+ reject { |association| association.flattener.class.is_pivot? }
32
+ end
33
+
34
+ def self.generate_options(klass:, type:, nested_as: nil, **_options)
35
+ Enumerator.new do |yielder|
36
+ # Yield a select option for each belongs to association
37
+ has_many_associations(klass).each do |association|
38
+ expansions = Array(association.expand_on)
39
+ assoc_name = build_resource_name(association.name).singularize
40
+ break if assoc_name == "Favorite"
41
+ association_resource_id = Reports::ExternalGraphqlClient.
42
+ query_resource_by_name(name: assoc_name)&.
43
+ dig("data", "resourceName", "id")
44
+ # to define the join resource, we need to define the join association on the flattener we let a commented example on projects
45
+ join_resource_id =
46
+ if association.join_resource.present?
47
+ Reports::ExternalGraphqlClient.
48
+ query_resource_by_name(name: build_resource_name(association.join_resource))&.
49
+ dig("data", "resourceName", "id")
50
+ end
51
+ # join_name = join_resource.dig("data", "resourceName", "name")
52
+ next if association_resource_id.blank?
53
+ association_and_descendants(association).each do |_association_klass, association_name|
54
+ expansions.each do |expansion|
55
+ option = {
56
+ name: ::Reports::Generator.build_option_name(*expansion, association_name),
57
+ associationResourceId: association_resource_id.to_i,
58
+ associationName: association_name,
59
+ associationColumnName: [*nested_as, association.foreign_key].join("_"),
60
+ }
61
+ option[:joinResourceId] = join_resource_id if join_resource_id.present?
62
+ option[:columnType] = "link" if type == "Select"
63
+ option[:selectOptionType] = "HasMany" if type == "Select"
64
+ option[:filterOptionType] = "HasMany" if type == "Filter"
65
+ yielder << option
66
+ end
67
+
68
+ option = {
69
+ name: ::Reports::Generator.build_option_name(association_name),
70
+ associationResourceId: association_resource_id.to_i,
71
+ associationName: association_name,
72
+ associationColumnName: [*nested_as, association.foreign_key].join("_"),
73
+ }
74
+ option[:joinResourceId] = join_resource_id if join_resource_id.present?
75
+ option[:columnType] = "link" if type == "Select"
76
+ option[:selectOptionType] = "HasMany" if type == "Select"
77
+ option[:filterOptionType] = "HasMany" if type == "Filter"
78
+ yielder << option
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ def self.build_resource_name(name)
85
+ Reports::Generator.build_option_name(name).delete(" ")
86
+ end
87
+
88
+ def self.association_and_descendants(association)
89
+ [
90
+ [association.klass, association.name.to_s],
91
+ *association.klass.descendants.map { |d| [d, format_descendent_name(d, association)] }
92
+ ]
93
+ end
94
+
95
+ # To avoid naming conflicts, suffix the descendant's name with the association
96
+ # unless the association's name matches the association's klass name
97
+ def self.format_descendent_name(descendant, association)
98
+ if association.name.to_s.classify == association.klass.name.demodulize
99
+ descendant.name.tableize
100
+ else
101
+ "#{descendant.name.underscore}_#{association.name}"
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,132 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ # TODO: Move lib/ out from under app/
5
+
6
+ #=== Instantiating
7
+ # To get an instance of an importer you must provide a resource (HasReports::Resource)
8
+ # such as `Reports::Importer.new(resource: HasReports::Resource::MY_RESOURCE)
9
+ module Reports
10
+ class Importer
11
+ AuditError = Class.new(::StandardError)
12
+
13
+ include ::HasHelpers::Attributes # For assignment of attributes passed to initialize, e.g. `Reports::Importer.new(resource: my_resource)`
14
+
15
+ RESOURCE_TYPE_MISMATCH_ERR_MSG = "When initializing HasReports::Importer, :resource must be an instance of HasReports::Resource"
16
+ AUDIT_EXTRA_RECORD_LOG_MSG = "%{option_type} name: %{name}, resource: %{resource} was flagged during an audit. The record may need to be migrated to match a new name or removed as the case may require."
17
+ AUDIT_EXTRA_EXPANSION_LOG_MSG = "Resource: %{resource}, Expanded Value: \"%{expanded_value}\", Expanded Column: \"%{expanded_column_name}\", Dynamic: \"%{is_dynamic}\", Node: \"%{node}\", was flagged during an audit. The record may need to be migrated to match a new name or removed as the case may require."
18
+
19
+ attr_accessor :resource_id, :resource_name
20
+
21
+ def initialize(resource_id, resource_name: nil, **_options)
22
+ @resource_id = resource_id
23
+ @resource_name = resource_name
24
+ end
25
+
26
+ # def import_select_options(values, resource_id, audit: true, verbose: false)
27
+ def import_select_options(values, resource_id, verbose: false)
28
+ values = Array.wrap(values)
29
+ [].tap do |results|
30
+ values.each do |attrs|
31
+ puts "--> SelectOption: #{ attrs.slice(:name, :select_statement).inspect }" if verbose
32
+ result = build_select_option(attrs, resource_id)
33
+ results << result
34
+ rescue => e
35
+ puts "Error importing SelectOption: #{ attrs.slice(:name, :select_statement).inspect } - #{ e.message }" if verbose
36
+ results << {
37
+ success: false,
38
+ message: "Exception during import: #{e.message}",
39
+ backtrace: e.backtrace
40
+ }
41
+ end
42
+ end
43
+ end
44
+
45
+ def build_select_option(attrs, resource_id)
46
+ attrs[:resourceId] = resource_id.to_i if resource_id.present?
47
+ results = Reports::ExternalGraphqlClient.create_resource(mutation_name: "createHasReportsSelectOption", input_name: "HasReportsSelectOptionInput", attributes: attrs) if attrs.present?
48
+ create_data = results&.dig("data", "createHasReportsSelectOption")
49
+ if create_data && create_data["errors"].blank?
50
+ {
51
+ success: true,
52
+ message: "SelectOption created successfully with ID #{create_data["resource"]["id"]}",
53
+ errors: nil
54
+ }
55
+ else
56
+ errors =
57
+ if create_data.present?
58
+ create_data["errors"] || []
59
+ elsif results&.dig("errors").present?
60
+ results["errors"].map do |e|
61
+ {
62
+ message: e["message"],
63
+ path: e.dig("extensions", "problems")&.flat_map { |p| p["path"] } || [],
64
+ explanation: e.dig("extensions", "problems")&.map { |p| p["explanation"] }&.join("; ")
65
+ }
66
+ end
67
+ else
68
+ []
69
+ end
70
+
71
+ {
72
+ success: false,
73
+ message: "Failed to create SelectOption '#{@resource_name}'->'#{attrs[:name]}'",
74
+ errors: errors
75
+ }
76
+ end
77
+ end
78
+
79
+ # def import_filter_options(values, resource_id, audit: true, verbose: false)
80
+ def import_filter_options(values, resource_id, verbose: false)
81
+ values = Array.wrap(values)
82
+ [].tap do |results|
83
+ values.each do |attrs|
84
+ puts "--> FilterOption: #{ attrs.slice(:name, :where_statement).inspect }" if verbose
85
+ result = build_filter_option(attrs, resource_id)
86
+ results << result
87
+ rescue => e
88
+ puts "Error importing FilterOption: #{ attrs.slice(:name, :where_statement).inspect } - #{ e.message }" if verbose
89
+ results << {
90
+ success: false,
91
+ message: "Exception during import: #{e.message}",
92
+ backtrace: e.backtrace
93
+ }
94
+ end
95
+ end
96
+ end
97
+
98
+ def build_filter_option(attrs, resource_id)
99
+ attrs[:resourceId] = resource_id.to_i if resource_id.present?
100
+ results = Reports::ExternalGraphqlClient.create_resource(mutation_name: "createHasReportsFilterOption", input_name: "HasReportsFilterOptionInput", attributes: attrs) if attrs.present?
101
+ create_data = results&.dig("data", "createHasReportsFilterOption")
102
+ if create_data && create_data["errors"].blank?
103
+ {
104
+ success: true,
105
+ message: "FilterOption created successfully with ID #{create_data["resource"]["id"]}",
106
+ errors: nil
107
+ }
108
+ else
109
+ errors =
110
+ if create_data.present?
111
+ create_data["errors"] || []
112
+ elsif results&.dig("errors").present?
113
+ results["errors"].map do |e|
114
+ {
115
+ message: e["message"],
116
+ path: e.dig("extensions", "problems")&.flat_map { |p| p["path"] } || [],
117
+ explanation: e.dig("extensions", "problems")&.map { |p| p["explanation"] }&.join("; ")
118
+ }
119
+ end
120
+ else
121
+ []
122
+ end
123
+
124
+ {
125
+ success: false,
126
+ message: "Failed to create FilterOption '#{@resource_name}'->'#{attrs[:name]}'",
127
+ errors: errors
128
+ }
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,134 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Reports
5
+ module NestedAttributeOptions
6
+ # Generate select options (attributes) for all associations with nested
7
+ # attributes for the given class.
8
+ def self.generate_select_options(klass, **_options)
9
+ [].tap do |results|
10
+ flattener_klass = ::Flattener.for(klass).pivot_or_self
11
+ associations = flattener_klass.associations.lazy.select(&:is_nested_attributes?)
12
+ associations.
13
+ reject { |association| association.klass.nil? }.
14
+ reject { |association| association.flattener.class.is_pivot? }.
15
+ reject { |association| association.merge_into_parent }.
16
+ select { |association| association.count >= 0 }.
17
+ each do |association|
18
+ resource = Flattener::Resource.new(
19
+ flattener: association.flattener,
20
+ association: association,
21
+ parent: Flattener::Resource.new(flattener: Flattener.for(klass).new)
22
+ )
23
+ association_root_name = association.name.to_s.singularize
24
+
25
+ expansions = Array(association.klass).map do |expand_on|
26
+ expand_on.respond_to?(:name) ? expand_on.name : expand_on.to_s
27
+ end
28
+
29
+ expansions.each do |_expansion|
30
+ resource.attributes.each do |attribute|
31
+ child_column_name = (attribute.column.alias_name || attribute.column_name || attribute.column.name).to_s
32
+ column_name = [association_root_name, *child_column_name].join("_").underscore
33
+ column_type = ::Flattener::Column::Types.sql_type_to_type(attribute.column.sql_type)
34
+ next if column_type == ::Flattener::Column::Types::JSON # skip json
35
+
36
+ result = {
37
+ name: ::Reports::Generator.build_option_name(column_name, preserve_id: true),
38
+ columnType: column_type.to_s,
39
+ nestedResourceCount: (association.count if association.count > 0),
40
+ associationName: association.name.to_s,
41
+ associationColumnName: column_name,
42
+ }
43
+
44
+ if association.expand_on_column
45
+ result[:associationColumnName] = [association_root_name, *child_column_name].join("_").underscore
46
+ end
47
+
48
+ if [::Flattener::Column::Types::DATE, ::Flattener::Column::Types::DATETIME].include?(column_type)
49
+ result[:selectOptionType] = "BelongsTo"
50
+ end
51
+
52
+ results << result
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ def self.generate_filter_options(klass, **options)
60
+ [].tap do |results|
61
+ flattener_klass = ::Flattener.for(klass).pivot_or_self
62
+ associations = flattener_klass.associations.lazy.
63
+ select(&:is_nested_attributes?)
64
+ associations.
65
+ reject { |association| association.flattener.class.is_pivot? }.
66
+ reject { |association| association.merge_into_parent }.
67
+ select { |association| association.count >= 0 }.
68
+ each do |association|
69
+ resource = Flattener::Resource.new(
70
+ flattener: association.flattener,
71
+ association: association,
72
+ parent: Flattener::Resource.new(flattener: Flattener.for(klass).new)
73
+ )
74
+ association_root_name = association.name.to_s.singularize
75
+ expansions = Array(association.klass).map do |expand_on|
76
+ expand_on.respond_to?(:name) ? expand_on.name : expand_on.to_s
77
+ end
78
+ expansions.each do |_expansion|
79
+ resource.attributes.each do |attribute|
80
+ child_column_name = (attribute.column.alias_name || attribute.column_name || attribute.column.name).to_s
81
+ column_name = [association_root_name, *child_column_name].join("_").underscore
82
+ column_type = ::Flattener::Column::Types.sql_type_to_type(attribute.column.sql_type)
83
+ next if column_type == ::Flattener::Column::Types::JSON # skip json
84
+ filter_option = ::Reports::FilterOptionGenerator.for(column_type).
85
+ new(flattener_attr: attribute, attr: column_name, name: ::Reports::Generator.build_option_name(column_name, preserve_id: true), **options).
86
+ generate.
87
+ tap do |o|
88
+ o[:nestedResourceCount] = (association.count if association.count > 0)
89
+ o[:associationName] = association.name.to_s
90
+ if [::Flattener::Column::Types::DATE, ::Flattener::Column::Types::DATETIME].include?(column_type)
91
+ o.merge!(
92
+ filterOptionType: "BelongsTo",
93
+ columnType: nil,
94
+ operationType: nil,
95
+ partialType: nil,
96
+ )
97
+ end
98
+ end
99
+
100
+ results << filter_option
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ # Check if the attribute is nested (i.e. sales_manager_1_name)
108
+ def self.is_nested?(attr)
109
+ is_expanded?(attr.resource)
110
+ end
111
+
112
+ def self.is_expanded?(resource)
113
+ return false unless resource.try :association
114
+ resource.association.count > 0 || is_expanded?(resource.parent)
115
+ end
116
+
117
+ def self.expand_on
118
+ return @expand_on if @expand_on_memoized
119
+
120
+ @expand_on_memoized = true
121
+
122
+ @expand_on = @expand_on.call if @expand_on.is_a?(Proc)
123
+
124
+ # Fallback only if @expand_on was never set
125
+ if @expand_on.nil? && klass
126
+ @expand_on = klass.nested_attributes_options.keys
127
+ end
128
+
129
+ @expand_on = Array(@expand_on).map do |expansion|
130
+ expansion.is_a?(Hash) ? expansion : { value: expansion, column_name: "type" }
131
+ end
132
+ end
133
+ end
134
+ end