blacklight-spotlight 3.0.0.alpha.9 → 3.0.0.rc4

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 (262) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -3
  3. data/app/assets/images/blacklight/arrow-alt-circle-left.svg +1 -0
  4. data/app/assets/images/blacklight/arrow-alt-circle-right.svg +1 -0
  5. data/app/assets/javascripts/spotlight/admin/{add_new_page_button.js → add_new_button.js} +7 -0
  6. data/app/assets/javascripts/spotlight/admin/block_mixins/autocompleteable.js +4 -4
  7. data/app/assets/javascripts/spotlight/admin/blocks/browse_block.js +55 -1
  8. data/app/assets/javascripts/spotlight/admin/blocks/browse_group_categories_block.js +88 -0
  9. data/app/assets/javascripts/spotlight/admin/blocks/pages_block.js +1 -1
  10. data/app/assets/javascripts/spotlight/admin/blocks/solr_documents_base_block.js +1 -1
  11. data/app/assets/javascripts/spotlight/admin/blocks/uploaded_items_block.js +7 -2
  12. data/app/assets/javascripts/spotlight/admin/crop.es6 +11 -0
  13. data/app/assets/javascripts/spotlight/admin/croppable.js +1 -1
  14. data/app/assets/javascripts/spotlight/admin/index.js +0 -2
  15. data/app/assets/javascripts/spotlight/admin/reindex_monitor.js +1 -0
  16. data/app/assets/javascripts/spotlight/admin/search_typeahead.js +2 -2
  17. data/app/assets/javascripts/spotlight/admin/sir-trevor/block_controls.js +21 -12
  18. data/app/assets/javascripts/spotlight/admin/sir-trevor/locales.js +11 -3
  19. data/app/assets/javascripts/spotlight/user/browse_group_categories.js +59 -0
  20. data/app/assets/javascripts/spotlight/user/index.js +1 -0
  21. data/app/assets/stylesheets/spotlight/_accessibility.scss +1 -1
  22. data/app/assets/stylesheets/spotlight/_breadcrumbs.scss +8 -0
  23. data/app/assets/stylesheets/spotlight/_browse.scss +16 -0
  24. data/app/assets/stylesheets/spotlight/_catalog.scss +6 -6
  25. data/app/assets/stylesheets/spotlight/_curation.scss +6 -0
  26. data/app/assets/stylesheets/spotlight/_featured_browse_categories_block.scss +214 -83
  27. data/app/assets/stylesheets/spotlight/_header.scss +1 -1
  28. data/app/assets/stylesheets/spotlight/_item_text_block.scss +6 -0
  29. data/app/assets/stylesheets/spotlight/_pages.scss +9 -4
  30. data/app/assets/stylesheets/spotlight/_report_a_problem.scss +5 -2
  31. data/app/assets/stylesheets/spotlight/_spotlight.scss +2 -0
  32. data/app/assets/stylesheets/spotlight/_translations.scss +7 -0
  33. data/app/assets/stylesheets/spotlight/browse_group_categories_block.scss +92 -0
  34. data/app/builders/spotlight/bootstrap_breadcrumbs_builder.rb +1 -2
  35. data/app/controllers/concerns/spotlight/search_helper.rb +2 -8
  36. data/app/controllers/spotlight/appearances_controller.rb +0 -12
  37. data/app/controllers/spotlight/browse_controller.rb +7 -3
  38. data/app/controllers/spotlight/catalog_controller.rb +5 -2
  39. data/app/controllers/spotlight/concerns/application_controller.rb +13 -2
  40. data/app/controllers/spotlight/dashboards_controller.rb +1 -1
  41. data/app/controllers/spotlight/exhibits_controller.rb +2 -3
  42. data/app/controllers/spotlight/featured_images_controller.rb +1 -1
  43. data/app/controllers/spotlight/groups_controller.rb +80 -0
  44. data/app/controllers/spotlight/pages_controller.rb +6 -9
  45. data/app/controllers/spotlight/resources/csv_upload_controller.rb +1 -1
  46. data/app/controllers/spotlight/searches_controller.rb +7 -19
  47. data/app/controllers/spotlight/translations_controller.rb +46 -0
  48. data/app/helpers/spotlight/application_helper.rb +20 -1
  49. data/app/helpers/spotlight/crop_helper.rb +4 -1
  50. data/app/helpers/spotlight/crud_link_helpers.rb +1 -1
  51. data/app/helpers/spotlight/main_app_helpers.rb +1 -1
  52. data/app/helpers/spotlight/pages_helper.rb +1 -1
  53. data/app/jobs/concerns/spotlight/job_tracking.rb +47 -0
  54. data/app/jobs/concerns/spotlight/limit_concurrency.rb +33 -0
  55. data/app/jobs/spotlight/add_uploads_from_csv.rb +34 -6
  56. data/app/jobs/spotlight/application_job.rb +8 -0
  57. data/app/jobs/spotlight/cleanup_job_trackers_job.rb +13 -0
  58. data/app/jobs/spotlight/default_thumbnail_job.rb +1 -3
  59. data/app/jobs/spotlight/reindex_exhibit_job.rb +36 -0
  60. data/app/jobs/spotlight/reindex_job.rb +49 -41
  61. data/app/jobs/spotlight/rename_sidecar_field_job.rb +2 -2
  62. data/app/jobs/spotlight/update_job_trackers_job.rb +20 -0
  63. data/app/mailers/spotlight/indexing_complete_mailer.rb +3 -2
  64. data/app/models/concerns/spotlight/exhibit_defaults.rb +1 -1
  65. data/app/models/concerns/spotlight/translatables.rb +17 -1
  66. data/app/models/concerns/spotlight/user.rb +2 -1
  67. data/app/models/sir_trevor_rails/blocks/browse_group_categories_block.rb +25 -0
  68. data/app/models/spotlight/ability.rb +2 -0
  69. data/app/models/spotlight/about_page.rb +3 -1
  70. data/app/models/spotlight/blacklight_configuration.rb +2 -1
  71. data/app/models/spotlight/contact.rb +1 -1
  72. data/app/models/spotlight/custom_field.rb +3 -3
  73. data/app/models/spotlight/event.rb +13 -0
  74. data/app/models/spotlight/exhibit.rb +18 -14
  75. data/app/models/spotlight/feature_page.rb +3 -1
  76. data/app/models/spotlight/featured_image.rb +29 -12
  77. data/app/models/spotlight/group.rb +22 -0
  78. data/app/models/spotlight/group_member.rb +11 -0
  79. data/app/models/spotlight/home_page.rb +3 -1
  80. data/app/models/spotlight/job_tracker.rb +105 -0
  81. data/app/models/spotlight/main_navigation.rb +2 -2
  82. data/app/models/spotlight/masthead.rb +1 -1
  83. data/app/models/spotlight/page.rb +5 -1
  84. data/app/models/spotlight/page_configurations.rb +6 -0
  85. data/app/models/spotlight/page_content.rb +2 -0
  86. data/app/models/spotlight/reindex_progress.rb +44 -27
  87. data/app/models/spotlight/resource.rb +24 -58
  88. data/app/models/spotlight/resources/csv_upload.rb +2 -1
  89. data/app/models/spotlight/resources/iiif_harvester.rb +10 -1
  90. data/app/models/spotlight/resources/iiif_manifest.rb +11 -7
  91. data/app/models/spotlight/resources/iiif_service.rb +1 -1
  92. data/app/models/spotlight/resources/json_upload.rb +12 -0
  93. data/app/models/spotlight/resources/upload.rb +25 -2
  94. data/app/models/spotlight/search.rb +10 -1
  95. data/app/models/spotlight/solr_document_sidecar.rb +9 -6
  96. data/app/models/spotlight/temporary_image.rb +8 -0
  97. data/app/services/spotlight/etl.rb +7 -0
  98. data/app/services/spotlight/etl/context.rb +52 -0
  99. data/app/services/spotlight/etl/executor.rb +194 -0
  100. data/app/services/spotlight/etl/loaders.rb +12 -0
  101. data/app/services/spotlight/etl/pipeline.rb +81 -0
  102. data/app/services/spotlight/etl/solr_loader.rb +96 -0
  103. data/app/services/spotlight/etl/sources.rb +25 -0
  104. data/app/services/spotlight/etl/step.rb +82 -0
  105. data/app/services/spotlight/etl/transforms.rb +64 -0
  106. data/app/services/spotlight/exhibit_import_export_service.rb +482 -0
  107. data/app/services/spotlight/validity_checker.rb +5 -5
  108. data/app/values/custom_field_name.rb +1 -0
  109. data/app/views/catalog/_save_search.html.erb +1 -1
  110. data/app/views/spotlight/about_pages/_empty.html.erb +5 -5
  111. data/app/views/spotlight/browse/_search.html.erb +5 -3
  112. data/app/views/spotlight/browse/_search_title.html.erb +2 -1
  113. data/app/views/spotlight/browse/index.html.erb +13 -0
  114. data/app/views/spotlight/catalog/_document.html.erb +2 -4
  115. data/app/views/spotlight/catalog/index.iiif_json.jbuilder +22 -0
  116. data/app/views/spotlight/contacts/_form.html.erb +1 -1
  117. data/app/views/spotlight/dashboards/_reindexing_activity.html.erb +6 -6
  118. data/app/views/spotlight/feature_pages/_empty.html.erb +5 -5
  119. data/app/views/spotlight/featured_images/_form.html.erb +1 -1
  120. data/app/views/spotlight/featured_images/_upload_form.html.erb +1 -1
  121. data/app/views/spotlight/home_pages/_empty.html.erb +3 -3
  122. data/app/views/spotlight/indexing_complete_mailer/documents_indexed.html.erb +9 -0
  123. data/app/views/spotlight/pages/_form.html.erb +2 -2
  124. data/app/views/spotlight/searches/_form.html.erb +13 -0
  125. data/app/views/spotlight/searches/_group.html.erb +27 -0
  126. data/app/views/spotlight/searches/_search.html.erb +1 -0
  127. data/app/views/spotlight/searches/index.html.erb +58 -17
  128. data/app/views/spotlight/shared/_honeypot_field.html.erb +4 -0
  129. data/app/views/spotlight/shared/_locale_picker.html.erb +1 -1
  130. data/app/views/spotlight/shared/_report_a_problem.html.erb +7 -10
  131. data/app/views/spotlight/sir_trevor/blocks/_browse_block.html.erb +1 -0
  132. data/app/views/spotlight/sir_trevor/blocks/_browse_group_categories_block.html.erb +45 -0
  133. data/app/views/spotlight/sir_trevor/blocks/_uploaded_items_block.html.erb +7 -1
  134. data/app/views/spotlight/translations/_browse_categories.html.erb +29 -3
  135. data/app/views/spotlight/translations/_general.html.erb +7 -7
  136. data/app/views/spotlight/translations/_groups.html.erb +34 -0
  137. data/app/views/spotlight/translations/_import.html.erb +24 -0
  138. data/app/views/spotlight/translations/_metadata.html.erb +1 -1
  139. data/app/views/spotlight/translations/_page.html.erb +5 -5
  140. data/app/views/spotlight/translations/_pages.html.erb +4 -4
  141. data/app/views/spotlight/translations/_pages_table.html.erb +5 -5
  142. data/app/views/spotlight/translations/_search_fields.html.erb +3 -3
  143. data/app/views/spotlight/translations/edit.html.erb +14 -6
  144. data/app/views/spotlight/translations/show.yaml.yamlbuilder +81 -0
  145. data/config/i18n-tasks.yml +7 -0
  146. data/config/locales/spotlight.ar.yml +57 -24
  147. data/config/locales/spotlight.en.yml +184 -128
  148. data/config/routes.rb +16 -1
  149. data/db/migrate/20200403161512_add_subtitle_to_searches.rb +7 -0
  150. data/db/migrate/20210113092223_create_spotlight_groups.rb +23 -0
  151. data/db/migrate/20210122082032_create_job_trackers.rb +22 -0
  152. data/db/migrate/20210126123041_create_events.rb +15 -0
  153. data/lib/generators/spotlight/install_generator.rb +23 -2
  154. data/lib/generators/spotlight/scaffold_resource_generator.rb +5 -13
  155. data/lib/generators/spotlight/templates/config/initializers/sir_trevor_rails.rb +10 -0
  156. data/lib/generators/spotlight/templates/config/initializers/spotlight_initializer.rb +3 -1
  157. data/lib/spotlight/engine.rb +35 -4
  158. data/lib/spotlight/upload_field_config.rb +1 -0
  159. data/lib/spotlight/version.rb +1 -1
  160. data/spec/controllers/spotlight/about_pages_controller_spec.rb +3 -3
  161. data/spec/controllers/spotlight/browse_controller_spec.rb +24 -1
  162. data/spec/controllers/spotlight/catalog_controller_spec.rb +4 -2
  163. data/spec/controllers/spotlight/contacts_controller_spec.rb +2 -2
  164. data/spec/controllers/spotlight/feature_pages_controller_spec.rb +11 -0
  165. data/spec/controllers/spotlight/groups_controller_spec.rb +103 -0
  166. data/spec/controllers/spotlight/home_pages_controller_spec.rb +2 -2
  167. data/spec/controllers/spotlight/resources/csv_upload_controller_spec.rb +4 -4
  168. data/spec/controllers/spotlight/searches_controller_spec.rb +10 -3
  169. data/spec/controllers/spotlight/translations_controller_spec.rb +53 -2
  170. data/spec/controllers/spotlight/view_configurations_controller_spec.rb +1 -1
  171. data/spec/examples.txt +1448 -125
  172. data/spec/factories/featured_images.rb +4 -0
  173. data/spec/factories/group.rb +17 -0
  174. data/spec/factories/job_trackers.rb +9 -0
  175. data/spec/factories/searches.rb +11 -1
  176. data/spec/features/add_contacts_spec.rb +1 -1
  177. data/spec/features/add_items_spec.rb +9 -4
  178. data/spec/features/browse_category_admin_spec.rb +39 -7
  179. data/spec/features/browse_category_navigation_spec.rb +44 -0
  180. data/spec/features/browse_category_spec.rb +2 -2
  181. data/spec/features/catalog_spec.rb +2 -2
  182. data/spec/features/create_exhibit_spec.rb +5 -4
  183. data/spec/features/dashboard_spec.rb +7 -7
  184. data/spec/features/edit_search_fields_spec.rb +2 -2
  185. data/spec/features/exhibits/administration_spec.rb +3 -3
  186. data/spec/features/exhibits/edit_metadata_fields_spec.rb +1 -1
  187. data/spec/features/exhibits/language_create_edit_spec.rb +3 -3
  188. data/spec/features/exhibits/translation_editing_spec.rb +57 -8
  189. data/spec/features/home_page_spec.rb +13 -4
  190. data/spec/features/item_admin_spec.rb +4 -4
  191. data/spec/features/javascript/about_page_admin_spec.rb +1 -1
  192. data/spec/features/javascript/block_controls_spec.rb +3 -1
  193. data/spec/features/javascript/blocks/browse_group_categories_block_spec.rb +64 -0
  194. data/spec/features/javascript/blocks/uploaded_items_block_spec.rb +4 -1
  195. data/spec/features/javascript/browse_group_admin_spec.rb +45 -0
  196. data/spec/features/javascript/edit_in_place_spec.rb +3 -3
  197. data/spec/features/javascript/feature_page_admin_spec.rb +1 -1
  198. data/spec/features/javascript/reindex_monitor_spec.rb +1 -1
  199. data/spec/features/javascript/search_config_admin_spec.rb +1 -1
  200. data/spec/features/report_a_problem_spec.rb +6 -5
  201. data/spec/features/site_users_management_spec.rb +4 -4
  202. data/spec/helpers/spotlight/crud_link_helpers_spec.rb +3 -3
  203. data/spec/helpers/spotlight/pages_helper_spec.rb +10 -2
  204. data/spec/i18n_spec.rb +0 -2
  205. data/spec/jobs/spotlight/add_uploads_from_csv_spec.rb +13 -1
  206. data/spec/jobs/spotlight/reindex_exhibit_job_spec.rb +43 -0
  207. data/spec/jobs/spotlight/reindex_job_spec.rb +30 -59
  208. data/spec/mailers/spotlight/indexing_complete_mailer_spec.rb +11 -1
  209. data/spec/models/sir_trevor_rails/blocks/browse_group_categories_block_spec.rb +41 -0
  210. data/spec/models/solr_document_spec.rb +2 -3
  211. data/spec/models/spotlight/access_controls_enforcement_search_builder_spec.rb +1 -0
  212. data/spec/models/spotlight/exhibit_spec.rb +21 -59
  213. data/spec/models/spotlight/featured_image_spec.rb +27 -0
  214. data/spec/models/spotlight/group_spec.rb +19 -0
  215. data/spec/models/spotlight/main_navigation_spec.rb +1 -1
  216. data/spec/models/spotlight/page_spec.rb +6 -1
  217. data/spec/models/spotlight/reindex_progress_spec.rb +89 -87
  218. data/spec/models/spotlight/resource_spec.rb +69 -90
  219. data/spec/models/spotlight/resources/iiif_harvester_spec.rb +9 -10
  220. data/spec/models/spotlight/resources/upload_spec.rb +43 -79
  221. data/spec/models/spotlight/role_spec.rb +3 -3
  222. data/spec/models/spotlight/search_spec.rb +30 -3
  223. data/spec/models/spotlight/solr_document_sidecar_spec.rb +1 -0
  224. data/spec/services/spotlight/etl/context_spec.rb +66 -0
  225. data/spec/services/spotlight/etl/executor_spec.rb +149 -0
  226. data/spec/services/spotlight/etl/pipeline_spec.rb +22 -0
  227. data/spec/services/spotlight/etl/solr_loader_spec.rb +76 -0
  228. data/spec/services/spotlight/etl/step_spec.rb +70 -0
  229. data/spec/{serializers/spotlight/exhibit_export_serializer_spec.rb → services/spotlight/exhibit_import_export_service_spec.rb} +168 -23
  230. data/spec/services/spotlight/iiif_resource_resolver_spec.rb +1 -1
  231. data/spec/spec_helper.rb +3 -6
  232. data/spec/support/features/test_features_helpers.rb +15 -0
  233. data/spec/test_app_templates/Gemfile.extra +1 -3
  234. data/spec/test_app_templates/catalog_controller.rb +6 -3
  235. data/spec/test_app_templates/lib/generators/test_app_generator.rb +1 -1
  236. data/spec/views/shared/_exhibit_navbar.html.erb_spec.rb +1 -1
  237. data/spec/views/spotlight/browse/index.html.erb_spec.rb +2 -0
  238. data/spec/views/spotlight/dashboards/_analytics.html.erb_spec.rb +1 -1
  239. data/spec/views/spotlight/dashboards/_reindexing_activity.html.erb_spec.rb +28 -25
  240. data/spec/views/spotlight/metadata_configurations/_metadata_field.html.erb_spec.rb +3 -3
  241. data/spec/views/spotlight/pages/show.html.erb_spec.rb +1 -0
  242. data/spec/views/spotlight/search_configurations/_facets.html.erb_spec.rb +1 -1
  243. data/spec/views/spotlight/search_configurations/_sort.html.erb_spec.rb +7 -8
  244. data/spec/views/spotlight/translations/_import.html.erb_spec.rb +24 -0
  245. data/vendor/assets/javascripts/leaflet-iiif.js +46 -21
  246. data/vendor/assets/javascripts/tiny-slider.js +3218 -0
  247. data/vendor/assets/stylesheets/tiny-slider.css +1 -0
  248. metadata +444 -289
  249. data/app/models/concerns/spotlight/resources/open_graph.rb +0 -36
  250. data/app/models/spotlight/reindexing_log_entry.rb +0 -42
  251. data/app/serializers/spotlight/exhibit_export_serializer.rb +0 -205
  252. data/app/serializers/spotlight/featured_image_representer.rb +0 -29
  253. data/app/serializers/spotlight/main_navigation_representer.rb +0 -13
  254. data/app/serializers/spotlight/page_representer.rb +0 -33
  255. data/app/services/spotlight/resources/iiif_builder.rb +0 -19
  256. data/app/services/spotlight/solr_document_builder.rb +0 -76
  257. data/app/services/spotlight/upload_solr_document_builder.rb +0 -57
  258. data/spec/factories/reindexing_log_entries.rb +0 -54
  259. data/spec/models/spotlight/reindexing_log_entry_spec.rb +0 -129
  260. data/spec/models/spotlight/resources/open_graph_spec.rb +0 -65
  261. data/spec/services/spotlight/solr_document_builder_spec.rb +0 -66
  262. data/vendor/assets/javascripts/handlebars-v1.3.0.js +0 -2746
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spotlight
4
+ module Etl
5
+ module Loaders
6
+ # A loader that just prints the data to $stderr for debugging.
7
+ WarnLoader = lambda do |data, _context|
8
+ warn(JSON.pretty_generate(data))
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spotlight
4
+ module Etl
5
+ # ETL pipeline definition
6
+ class Pipeline
7
+ include ActiveSupport::Benchmarkable
8
+
9
+ attr_reader :context, :source
10
+
11
+ # This ETL pipeline system, while somewhat generic, was implemented for Spotlight
12
+ # to transform Spotlight::Resource instances into Solr documents. The resources
13
+ # go through a series of steps (sources, transforms, loaders) to produce one or
14
+ # more documents in the Solr index.
15
+ #
16
+ # All of the steps below can be provided as:
17
+ # - a lambda
18
+ # - a ruby class (which will be initialized for each pipeline execution)
19
+ # - or, a hash (of any length) with:
20
+ # - a key (used only for clarity in logging, particularly useful to label lambdas)
21
+ # - a value that is one of the valid step types (lambda or ruby class).
22
+ #
23
+ # Any of the transform or loader steps can `throw :skip` to skip the current source.
24
+ #
25
+ # Any exceptions raised by the pipeline's steps are sent to the context's
26
+ # error handler by calling `#on_error` on the context object.
27
+
28
+ # sources return enumerables that convert from the Spotlight::Etl::Context
29
+ # to some data structure that the transform steps can handle. The Context is provided
30
+ # by the implementation when the pipeline is executed.
31
+ class_attribute :sources, default: []
32
+
33
+ # The transform steps (pre-processes, transforms, and post-processes) receive
34
+ # the current data state and the pipeline. The return value from the transforms
35
+ # steps replaces the current data state, however the return values for pre- and
36
+ # post- processing is ignored (although they may mutate the provided data, pipeline, etc).
37
+ #
38
+ # Through the pipeline argument, the transform steps can access:
39
+ # - `context`, the implementation-provided resource
40
+ # - `source`, the current source instance
41
+ class_attribute :pre_processes, default: []
42
+ class_attribute :transforms, default: []
43
+ class_attribute :post_processes, default: []
44
+
45
+ # loaders receive the transformed data and.. do something with it (like load it into Solr)
46
+ # After all documents are transformed, the loader may also receive `#finalize` to finish any
47
+ # additional processing.
48
+ class_attribute :loaders, default: []
49
+
50
+ def initialize
51
+ yield(self) if block_given?
52
+ end
53
+
54
+ ##
55
+ # Execute the ETL pipeline
56
+ #
57
+ # @param [Spotlight::Etl::Context] context
58
+ # @param [Hash] data the initial data structure to pass through to the transform steps
59
+ # @yield (optioanlly..) each transformed document after it is transformed but before
60
+ # it is sent to the loaders
61
+ def call(context, data: {}, cache: nil, &block)
62
+ executor(context, cache: cache).call(data: data, &block)
63
+ end
64
+
65
+ ##
66
+ # Estimate the number of documents that will be produced by the pipeline
67
+ #
68
+ # @param [Spotlight::Etl::Context] context
69
+ # @return [Number]
70
+ def estimated_size(context)
71
+ executor(context).estimated_size
72
+ end
73
+
74
+ private
75
+
76
+ def executor(context, **args)
77
+ Spotlight::Etl::Executor.new(self, context, **args)
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spotlight
4
+ module Etl
5
+ # Solr data loader with a built-in buffer to combine document updates into batches
6
+ class SolrLoader
7
+ attr_reader :queue, :batch_size
8
+
9
+ delegate :size, to: :queue
10
+
11
+ def initialize(batch_size: Spotlight::Engine.config.solr_batch_size, solr_connection: nil)
12
+ @queue = Queue.new
13
+ @batch_size = batch_size
14
+ @blacklight_solr = solr_connection
15
+ end
16
+
17
+ def call(data, pipeline = nil)
18
+ @queue << data
19
+
20
+ write_to_index(pipeline) if @queue.size >= @batch_size
21
+ end
22
+
23
+ def finalize(pipeline = nil)
24
+ write_to_index(pipeline)
25
+
26
+ commit! if pipeline.nil? || pipeline.context.additional_parameters[:commit]
27
+ end
28
+
29
+ private
30
+
31
+ def write_to_index(pipeline)
32
+ batch = drain_queue
33
+
34
+ return unless write? && batch.any?
35
+
36
+ send_batch(batch, pipeline)
37
+ end
38
+
39
+ def send_batch(documents, pipeline)
40
+ blacklight_solr.update params: { commitWithin: 500 },
41
+ data: documents.to_json,
42
+ headers: { 'Content-Type' => 'application/json' }
43
+ rescue StandardError => e
44
+ logger.warn "Error sending a batch of documents to solr: #{e}"
45
+
46
+ documents.each do |doc|
47
+ send_one(doc, pipeline)
48
+ end
49
+ end
50
+
51
+ def send_one(document, pipeline)
52
+ blacklight_solr.update params: { commitWithin: 500 },
53
+ data: [document].to_json,
54
+ headers: { 'Content-Type' => 'application/json' }
55
+ rescue StandardError => e
56
+ pipeline&.on_error(self, e, document.to_json)
57
+ end
58
+
59
+ def blacklight_solr
60
+ @blacklight_solr ||= RSolr.connect(connection_config.merge(adapter: connection_config[:http_adapter]))
61
+ end
62
+
63
+ def connection_config
64
+ Blacklight.connection_config
65
+ end
66
+
67
+ def drain_queue
68
+ arr = []
69
+
70
+ begin
71
+ arr << @queue.deq(true) while arr.length < @batch_size && !@queue.empty?
72
+ rescue ThreadError
73
+ # @queue throws a ThreadError if it is empty...
74
+ end
75
+
76
+ arr
77
+ end
78
+
79
+ def commit!
80
+ return unless write?
81
+
82
+ blacklight_solr.commit
83
+ rescue StandardError => e
84
+ logger.warn "Unable to commit to solr: #{e}"
85
+ end
86
+
87
+ def write?
88
+ Spotlight::Engine.config.writable_index
89
+ end
90
+
91
+ def logger
92
+ Rails.logger
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spotlight
4
+ module Etl
5
+ # Basic ETL source implementations
6
+ module Sources
7
+ # A simple source that just returns the original resource(s)
8
+ IdentitySource = lambda do |context|
9
+ Array.wrap(context.resource)
10
+ end
11
+
12
+ # A transform step that calls a method on the resource to generate a source
13
+ def self.SourceMethodSource(method) # rubocop:disable Naming/MethodName
14
+ lambda do |context|
15
+ context.resource.public_send(method)
16
+ end
17
+ end
18
+
19
+ # A simple source that retrieves the stored data from a Spotlight::Resource
20
+ StoredData = lambda do |context, **|
21
+ Array.wrap(context.resource.data)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spotlight
4
+ module Etl
5
+ # ETL pipeline step
6
+ class Step
7
+ attr_reader :definition, :context, :executor
8
+
9
+ # @param [Class, Proc] definition the step to run
10
+ # @param [String] label
11
+ # @param [Spotlight::Etl::Executor] executor the execution environment
12
+ def initialize(definition, label: nil, executor: nil)
13
+ @definition = definition
14
+ @executor = executor
15
+ @label = label
16
+ end
17
+
18
+ # rubocop:disable Metrics/MethodLength
19
+ def call(*args)
20
+ with_logger do |logger|
21
+ logger.debug { "Called with #{transform_data_for_debugging(args.first)}" }
22
+
23
+ catch :skip do
24
+ return action.call(*args).tap do |result|
25
+ logger.debug { " => Returning #{transform_data_for_debugging(result)}" } if $VERBOSE
26
+ end
27
+ end
28
+
29
+ logger.debug ' => Caught skip.'
30
+ throw :skip
31
+ end
32
+ rescue StandardError => e
33
+ with_logger do |logger|
34
+ logger.error("Caught exception #{e}")
35
+ end
36
+ raise(e)
37
+ end
38
+ # rubocop:enable Metrics/MethodLength
39
+
40
+ def finalize(*args)
41
+ action.finalize(*args) if action.respond_to? :finalize
42
+ end
43
+
44
+ private
45
+
46
+ # @return [#call]
47
+ def action
48
+ case definition
49
+ when Class
50
+ # memoize the class' instance for the lifetime of the step
51
+ @memoized_action ||= definition.new
52
+ else # Proc, etc
53
+ definition
54
+ end
55
+ end
56
+
57
+ # @return [#to_string]
58
+ def label
59
+ @label || definition
60
+ end
61
+
62
+ # NOTE: this is super weird to support Rails 5.2
63
+ # @return [Logger]
64
+ def with_logger
65
+ yield(Rails.logger) && return unless executor
66
+
67
+ executor.with_logger do |logger|
68
+ logger.tagged(label) do
69
+ yield logger
70
+ end
71
+ end
72
+ end
73
+
74
+ ##
75
+ # @param [Hash] data
76
+ # @return [String] a simplified + truncated version of the data hash for debugging
77
+ def transform_data_for_debugging(data)
78
+ executor&.transform_data_for_debugging(data) || data.inspect.truncate(100)
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spotlight
4
+ module Etl
5
+ # Basic + default transform steps
6
+ module Transforms
7
+ # A transform step that "transforms" the source into the data element
8
+ IdentityTransform = lambda do |data, pipeline|
9
+ data.merge(pipeline.source)
10
+ end
11
+
12
+ # A transform step that calls a method on the source to generate a document
13
+ def self.SourceMethodTransform(method) # rubocop:disable Naming/MethodName
14
+ lambda do |data, pipeline|
15
+ data.merge(pipeline.source.public_send(method))
16
+ end
17
+ end
18
+
19
+ # A transform step that throws away blank data
20
+ RejectBlank = lambda do |data, _|
21
+ throw :skip if data.blank?
22
+ data
23
+ end
24
+
25
+ # A trasnform step that ensures data has a unique key attribute
26
+ RejectMissingUniqueId = lambda do |data, pipeline|
27
+ id = pipeline.context.unique_key(data)
28
+
29
+ throw :skip if id.blank?
30
+ data
31
+ end
32
+
33
+ # A transform that adds exhibit-specific metadata (like Spotlight sidecar data)
34
+ # to the document
35
+ ApplyExhibitMetadata = lambda do |data, pipeline|
36
+ resource = pipeline.context.resource
37
+ document_model = pipeline.context.document_model
38
+ id = pipeline.context.unique_key(data)
39
+
40
+ next data unless document_model && id.present?
41
+
42
+ exhibit_metadata = document_model.build_for_exhibit(id, resource.exhibit, resource: (resource if resource.persisted?))
43
+
44
+ data.reverse_merge(exhibit_metadata.to_solr)
45
+ end
46
+
47
+ # A transform that adds application-specific metadata (like what resource generated the solr document)
48
+ ApplyApplicationMetadata = lambda do |data, pipeline|
49
+ resource = pipeline.context.resource
50
+ document_model = pipeline.context.document_model
51
+
52
+ data.reverse_merge(
53
+ Spotlight::Engine.config.resource_global_id_field => (resource.to_global_id.to_s if resource.persisted?),
54
+ document_model.resource_type_field => resource.class.to_s.tableize
55
+ )
56
+ end
57
+
58
+ # A transform that adds externally-provided metadata to the document
59
+ ApplyPipelineMetadata = lambda do |data, pipeline|
60
+ data.reverse_merge(pipeline.context.additional_metadata)
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,482 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'roar/decorator'
4
+ require 'roar/json'
5
+ require 'base64'
6
+ require 'tempfile'
7
+
8
+ module Spotlight
9
+ # Utility service for importing and exporting exhibit data
10
+ class ExhibitImportExportService
11
+ class_attribute :serialization_pipeline, default: %i[
12
+ raw_json
13
+ add_feature_page_hierarchy
14
+ add_browse_group_hierarchy
15
+ add_page_content
16
+ attach_featured_images
17
+ attach_attachments
18
+ ]
19
+
20
+ attr_reader :exhibit, :include
21
+
22
+ def initialize(exhibit, include: Spotlight::Engine.config.exports)
23
+ @exhibit = exhibit
24
+ @include = include
25
+ end
26
+
27
+ def from_hash!(hash)
28
+ hash = hash.deep_symbolize_keys.reverse_merge(
29
+ main_navigations: {},
30
+ contact_emails: {},
31
+ searches: {},
32
+ about_pages: {},
33
+ feature_pages: {},
34
+ contacts: {},
35
+ custom_fields: {},
36
+ solr_document_sidecars: {},
37
+ resources: {},
38
+ attachments: {},
39
+ languages: {},
40
+ translations: {},
41
+ owned_taggings: {},
42
+ groups: {}
43
+ )
44
+
45
+ exhibit_attributes = hash.reject { |_k, v| v.is_a?(Array) || v.is_a?(Hash) }
46
+ exhibit.update(exhibit_attributes.except(:theme))
47
+ exhibit.theme = exhibit_attributes[:theme] if exhibit.themes.include? exhibit_attributes[:theme]
48
+
49
+ deserialize_featured_image(exhibit, :masthead, hash[:masthead]) if hash[:masthead]
50
+ deserialize_featured_image(exhibit, :thumbnail, hash[:thumbnail]) if hash[:thumbnail]
51
+
52
+ exhibit.blacklight_configuration.update hash[:blacklight_configuration].deep_stringify_keys if hash[:blacklight_configuration]
53
+
54
+ hash[:main_navigations].each do |attr|
55
+ ar = exhibit.main_navigations.find_or_initialize_by(nav_type: attr[:nav_type])
56
+ ar.update(attr)
57
+ end
58
+
59
+ hash[:contact_emails].each do |attr|
60
+ ar = exhibit.contact_emails.find_or_initialize_by(email: attr[:email])
61
+ ar.update(attr)
62
+ end
63
+
64
+ hash[:groups].each do |attr|
65
+ gr = exhibit.groups.find_or_initialize_by(slug: attr[:slug])
66
+ gr.update(attr)
67
+ end
68
+
69
+ hash[:searches].each do |attr|
70
+ group_slugs = attr.delete(:group_slugs) || []
71
+ masthead = attr.delete(:masthead)
72
+ thumbnail = attr.delete(:thumbnail)
73
+
74
+ ar = exhibit.searches.find_or_initialize_by(slug: attr[:slug])
75
+ ar.update(attr)
76
+
77
+ ar.update(groups: exhibit.groups.select { |x| group_slugs.include? x.slug })
78
+
79
+ deserialize_featured_image(ar, :masthead, masthead) if masthead
80
+ deserialize_featured_image(ar, :thumbnail, thumbnail) if thumbnail
81
+ end
82
+
83
+ hash[:about_pages].each do |attr|
84
+ masthead = attr.delete(:masthead)
85
+ thumbnail = attr.delete(:thumbnail)
86
+ translated_pages = attr.delete(:translated_pages) || []
87
+
88
+ ar = exhibit.about_pages.find_or_initialize_by(slug: attr[:slug])
89
+ ar.update(attr)
90
+
91
+ deserialize_featured_image(ar, :masthead, masthead) if masthead
92
+ deserialize_featured_image(ar, :thumbnail, thumbnail) if thumbnail
93
+
94
+ translated_pages.each do |tattr|
95
+ masthead = tattr.delete(:masthead)
96
+ thumbnail = tattr.delete(:thumbnail)
97
+
98
+ tar = ar.translated_page_for(tattr[:locale]) || ar.clone_for_locale(tattr[:locale])
99
+ tar.update(tattr)
100
+
101
+ deserialize_featured_image(ar, :masthead, masthead) if masthead
102
+ deserialize_featured_image(ar, :thumbnail, thumbnail) if thumbnail
103
+ end
104
+ end
105
+
106
+ hash[:feature_pages].each do |attr|
107
+ masthead = attr.delete(:masthead)
108
+ thumbnail = attr.delete(:thumbnail)
109
+
110
+ ar = exhibit.feature_pages.find_or_initialize_by(slug: attr[:slug])
111
+ ar.update(attr.except(:parent_page_slug, :translated_pages))
112
+
113
+ deserialize_featured_image(ar, :masthead, masthead) if masthead
114
+ deserialize_featured_image(ar, :thumbnail, thumbnail) if thumbnail
115
+ end
116
+
117
+ feature_pages = exhibit.feature_pages.index_by(&:slug)
118
+ hash[:feature_pages].each do |attr|
119
+ next unless attr[:parent_page_slug]
120
+
121
+ feature_pages[attr[:slug]].parent_page_id = feature_pages[attr[:parent_page_slug]].id
122
+ end
123
+
124
+ hash[:feature_pages].each do |attr|
125
+ ar = exhibit.feature_pages.find_or_initialize_by(slug: attr[:slug])
126
+
127
+ (attr[:translated_pages] || []).each do |tattr|
128
+ masthead = tattr.delete(:masthead)
129
+ thumbnail = tattr.delete(:thumbnail)
130
+
131
+ tar = ar.translated_page_for(tattr[:locale]) || ar.clone_for_locale(tattr[:locale])
132
+ tar.update(tattr)
133
+
134
+ deserialize_featured_image(ar, :masthead, masthead) if masthead
135
+ deserialize_featured_image(ar, :thumbnail, thumbnail) if thumbnail
136
+ end
137
+ end
138
+
139
+ if hash[:home_page]
140
+ translated_pages = hash[:home_page].delete(:translated_pages) || []
141
+ exhibit.home_page.update(hash[:home_page].except(:thumbnail))
142
+ deserialize_featured_image(exhibit.home_page, :thumbnail, hash[:home_page][:thumbnail]) if hash[:home_page][:thumbnail]
143
+
144
+ translated_pages.each do |tattr|
145
+ masthead = tattr.delete(:masthead)
146
+ thumbnail = tattr.delete(:thumbnail)
147
+
148
+ tar = exhibit.home_page.translated_page_for(tattr[:locale]) || exhibit.home_page.clone_for_locale(tattr[:locale])
149
+ tar.update(tattr)
150
+
151
+ deserialize_featured_image(ar, :masthead, masthead) if masthead
152
+ deserialize_featured_image(ar, :thumbnail, thumbnail) if thumbnail
153
+ end
154
+ end
155
+
156
+ hash[:contacts].each do |attr|
157
+ avatar = attr.delete(:avatar)
158
+
159
+ ar = exhibit.contacts.find_or_initialize_by(slug: attr[:slug])
160
+ ar.update(attr)
161
+
162
+ deserialize_featured_image(ar, :avatar, avatar) if avatar
163
+ end
164
+
165
+ hash[:custom_fields].each do |attr|
166
+ ar = exhibit.custom_fields.find_or_initialize_by(slug: attr[:slug])
167
+ ar.update(attr)
168
+ end
169
+
170
+ hash[:solr_document_sidecars].each do |attr|
171
+ ar = exhibit.solr_document_sidecars.find_or_initialize_by(document_id: attr[:document_id])
172
+ ar.update(attr)
173
+ end
174
+
175
+ hash[:resources].each do |attr|
176
+ upload = attr.delete(:upload)
177
+
178
+ ar = exhibit.resources.find_or_initialize_by(type: attr[:type], url: attr[:url])
179
+ ar.update(attr)
180
+
181
+ deserialize_featured_image(ar, :upload, upload) if upload
182
+ end
183
+
184
+ hash[:attachments].each do |attr|
185
+ file = attr.delete(:file)
186
+
187
+ # dedupe by something??
188
+ ar = exhibit.attachments.build(attr)
189
+ ar.file = CarrierWave::SanitizedFile.new tempfile: StringIO.new(Base64.decode64(file[:content])),
190
+ filename: file[:filename],
191
+ content_type: file[:content_type]
192
+ end
193
+
194
+ hash[:languages].each do |attr|
195
+ ar = exhibit.languages.find_or_initialize_by(locale: attr[:locale])
196
+ ar.update(attr)
197
+ end
198
+
199
+ hash[:translations].each do |attr|
200
+ ar = exhibit.translations.find_or_initialize_by(locale: attr[:locale], key: attr[:key])
201
+ ar.update(attr)
202
+ end
203
+
204
+ hash[:owned_taggings].each do |attr|
205
+ tag = ActsAsTaggableOn::Tag.find_or_create_by(name: attr[:tag][:name])
206
+ exhibit.owned_taggings.build(attr.except(:tag).merge(tag_id: tag.id))
207
+ end
208
+ end
209
+
210
+ def deserialize_featured_image(obj, method, data)
211
+ file = data.delete(:image)
212
+ image = obj.public_send("build_#{method}")
213
+ image.update(data)
214
+ if file
215
+ image.image = CarrierWave::SanitizedFile.new tempfile: StringIO.new(Base64.decode64(file[:content])),
216
+ filename: file[:filename],
217
+ content_type: file[:content_type]
218
+ # Unset the iiif_tilesource field as the new image should be different, because
219
+ # the source has been reloaded
220
+ image.iiif_tilesource = nil
221
+ end
222
+ image.save!
223
+ obj.update(method => image)
224
+ end
225
+
226
+ def as_json(*_args)
227
+ self.class.serialization_pipeline.inject({}) do |memo, step|
228
+ method(step).call(memo)
229
+ end
230
+ end
231
+
232
+ private
233
+
234
+ def attach_featured_images(json)
235
+ json[:masthead] = serialize_featured_image(json[:masthead_id]) if json[:masthead_id]
236
+ json.delete(:masthead_id)
237
+ json[:thumbnail] = serialize_featured_image(json[:thumbnail_id]) if json[:thumbnail_id]
238
+ json.delete(:thumbnail_id)
239
+
240
+ (json[:searches] || []).each do |search|
241
+ search[:masthead] = serialize_featured_image(search[:masthead_id]) if search[:masthead_id]
242
+ search.delete(:masthead_id)
243
+ search[:thumbnail] = serialize_featured_image(search[:thumbnail_id]) if search[:thumbnail_id]
244
+ search.delete(:thumbnail_id)
245
+ end
246
+
247
+ (json[:about_pages] || []).each do |page|
248
+ page[:masthead] = serialize_featured_image(page[:masthead_id]) if page[:masthead_id]
249
+ page.delete(:masthead_id)
250
+ page[:thumbnail] = serialize_featured_image(page[:thumbnail_id]) if page[:thumbnail_id]
251
+ page.delete(:thumbnail_id)
252
+
253
+ (page[:translated_pages] || []).each do |translated_page|
254
+ translated_page[:masthead] = serialize_featured_image(translated_page[:masthead_id]) if translated_page[:masthead_id]
255
+ translated_page.delete(:masthead_id)
256
+ translated_page[:thumbnail] = serialize_featured_image(translated_page[:thumbnail_id]) if translated_page[:thumbnail_id]
257
+ translated_page.delete(:thumbnail_id)
258
+ end
259
+ end
260
+
261
+ (json[:feature_pages] || []).each do |page|
262
+ page[:masthead] = serialize_featured_image(page[:masthead_id]) if page[:masthead_id]
263
+ page.delete(:masthead_id)
264
+ page[:thumbnail] = serialize_featured_image(page[:thumbnail_id]) if page[:thumbnail_id]
265
+ page.delete(:thumbnail_id)
266
+
267
+ (page[:translated_pages] || []).each do |translated_page|
268
+ translated_page[:masthead] = serialize_featured_image(translated_page[:masthead_id]) if translated_page[:masthead_id]
269
+ translated_page.delete(:masthead_id)
270
+ translated_page[:thumbnail] = serialize_featured_image(translated_page[:thumbnail_id]) if translated_page[:thumbnail_id]
271
+ translated_page.delete(:thumbnail_id)
272
+ end
273
+ end
274
+
275
+ if json[:home_page]
276
+ json[:home_page][:masthead] = serialize_featured_image(json[:home_page][:masthead_id]) if json[:home_page][:masthead_id]
277
+ json[:home_page].delete(:masthead_id)
278
+ json[:home_page][:thumbnail] = serialize_featured_image(json[:home_page][:thumbnail_id]) if json[:home_page][:thumbnail_id]
279
+ json[:home_page].delete(:thumbnail_id)
280
+
281
+ (json[:home_page][:translated_pages] || []).each do |translated_page|
282
+ translated_page[:masthead] = serialize_featured_image(translated_page[:masthead_id]) if translated_page[:masthead_id]
283
+ translated_page.delete(:masthead_id)
284
+ translated_page[:thumbnail] = serialize_featured_image(translated_page[:thumbnail_id]) if translated_page[:thumbnail_id]
285
+ translated_page.delete(:thumbnail_id)
286
+ end
287
+ end
288
+
289
+ (json[:contacts] || []).each do |page|
290
+ page[:avatar] = serialize_featured_image(page[:avatar_id]) if page[:avatar_id]
291
+ page.delete(:avatar_id)
292
+ end
293
+
294
+ (json[:resources] || []).each do |page|
295
+ page[:upload] = serialize_featured_image(page[:upload_id]) if page[:upload_id]
296
+ page.delete(:upload_id)
297
+ end
298
+
299
+ json
300
+ end
301
+
302
+ def serialize_featured_image(id)
303
+ image = Spotlight::FeaturedImage.find(id)
304
+ file = image.image.file
305
+ if file
306
+ img = {
307
+ image: {
308
+ filename: file.filename, content_type: file.content_type, content: Base64.encode64(file.read)
309
+ }
310
+ }
311
+ end
312
+
313
+ image.as_json(except: %i[id image]).merge(img || {}).deep_symbolize_keys
314
+ end
315
+
316
+ def attach_attachments(json)
317
+ return json unless json[:attachments]
318
+
319
+ json[:attachments].each do |attachment|
320
+ a = exhibit.attachments.find(attachment[:id])
321
+ file = a.file.file
322
+
323
+ attachment[:file] = {
324
+ filename: file.filename,
325
+ content_type: file.content_type,
326
+ content: Base64.encode64(file.read)
327
+ }
328
+
329
+ attachment.delete(:id)
330
+ end
331
+
332
+ json
333
+ end
334
+
335
+ def add_feature_page_hierarchy(json)
336
+ return json unless json[:feature_pages]
337
+
338
+ page_id_map = json[:feature_pages].map { |x| [x[:id], x[:slug]] }.to_h
339
+
340
+ json[:feature_pages].each do |page|
341
+ page.delete(:id)
342
+ next unless page[:parent_page_id]
343
+
344
+ page[:parent_page_slug] = page_id_map[page[:parent_page_id]]
345
+ page.delete(:parent_page_id)
346
+ end
347
+
348
+ json
349
+ end
350
+
351
+ def add_browse_group_hierarchy(json)
352
+ return json unless json[:groups] && json[:searches]
353
+
354
+ json[:searches].each do |attr|
355
+ search = exhibit.searches.find_by(slug: attr[:slug])
356
+
357
+ attr[:group_slugs] = search.groups.pluck(:slug)
358
+ end
359
+
360
+ json
361
+ end
362
+
363
+ def add_page_content(json)
364
+ (json[:feature_pages] || []).each do |page|
365
+ p = exhibit.feature_pages.find_by(slug: page[:slug])
366
+ page[:content] = p.read_attribute(:content)
367
+ (page[:translated_pages]).each do |translated_page|
368
+ translated_page[:content] = p.translated_page_for(translated_page[:locale]).read_attribute(:content)
369
+ end
370
+ end
371
+
372
+ (json[:about_pages] || []).each do |page|
373
+ p = exhibit.about_pages.find_by(slug: page[:slug])
374
+ page[:content] = p.read_attribute(:content)
375
+ (page[:translated_pages]).each do |translated_page|
376
+ translated_page[:content] = p.translated_page_for(translated_page[:locale]).read_attribute(:content)
377
+ end
378
+ end
379
+
380
+ json[:home_page][:content] = exhibit.home_page.read_attribute(:content) if json[:home_page]
381
+ (json.dig(:home_page, :translated_pages) || []).each do |translated_page|
382
+ translated_page[:content] = exhibit.home_page.translated_page_for(translated_page[:locale]).read_attribute(:content)
383
+ end
384
+ json
385
+ end
386
+
387
+ def raw_json(_input = nil)
388
+ exhibit.as_json(
389
+ {
390
+ except: %i[id slug site_id],
391
+ include: {}.merge(
392
+ if_include?(:config,
393
+ main_navigations: {
394
+ except: %i[id exhibit_id]
395
+ },
396
+ contact_emails: {
397
+ except: %i[id exhibit_id confirmation_token]
398
+ },
399
+ languages: {
400
+ except: %i[id exhibit_id]
401
+ },
402
+ translations: {
403
+ only: %i[locale key value interpolations is_proc]
404
+ })
405
+ ).merge(
406
+ if_include?(:pages,
407
+ searches: { # thumbnail
408
+ except: %i[id scope exhibit_id]
409
+ },
410
+ groups: {
411
+ except: %i[id exhibit_id]
412
+ },
413
+ about_pages: { # thumbnail
414
+ except: %i[id scope exhibit_id parent_page_id content],
415
+ include: {
416
+ translated_pages: {
417
+ except: %i[id scope exhibit_id parent_page_id default_locale_page_id content]
418
+ }
419
+ }
420
+ },
421
+ home_page: { # thumbnail
422
+ except: %i[id slug scope exhibit_id parent_page_id content],
423
+ include: {
424
+ translated_pages: {
425
+ except: %i[id scope exhibit_id parent_page_id default_locale_page_id content]
426
+ }
427
+ }
428
+ },
429
+ feature_pages: { # thumbnail
430
+ except: %i[scope exhibit_id content],
431
+ include: {
432
+ translated_pages: {
433
+ except: %i[id scope exhibit_id parent_page_id default_locale_page_id content]
434
+ }
435
+ }
436
+ },
437
+ contacts: {
438
+ except: %i[id exhibit_id]
439
+ })
440
+ ).merge(
441
+ if_include?(:blacklight_configuration,
442
+ blacklight_configuration: {
443
+ except: %i[id exhibit_id]
444
+ },
445
+ # blacklight_configuration
446
+ custom_fields: {
447
+ except: %i[id exhibit_id]
448
+ })
449
+ ).merge(
450
+ if_include?(:resources,
451
+ # resources
452
+ solr_document_sidecars: {
453
+ except: %i[id exhibit_id]
454
+ },
455
+ owned_taggings: {
456
+ only: %i[taggable_id taggable_type context],
457
+ include: {
458
+ tag: {
459
+ only: [:name]
460
+ }
461
+ }
462
+ },
463
+ resources: { # upload
464
+ except: %i[id exhibit_id],
465
+ methods: :type
466
+ })
467
+ ).merge(
468
+ if_include?(:attachments,
469
+ # attachments
470
+ attachments: { # file
471
+ except: %i[exhibit_id]
472
+ })
473
+ )
474
+ }.merge(include[:config] ? {} : { only: %i[does_not_exist] })
475
+ ).deep_symbolize_keys
476
+ end
477
+
478
+ def if_include?(config, res)
479
+ include[config] ? res : {}
480
+ end
481
+ end
482
+ end