geoblacklight_admin 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +18 -9
  3. data/Rakefile +83 -47
  4. data/app/assets/javascripts/geoblacklight_admin/chosen.js +1 -0
  5. data/app/assets/stylesheets/geoblacklight_admin/_core.scss +24 -0
  6. data/app/assets/stylesheets/geoblacklight_admin/modules/_nav.scss +0 -5
  7. data/app/assets/stylesheets/geoblacklight_admin/modules/_tables.scss +1 -1
  8. data/app/controllers/admin/admin_controller.rb +16 -0
  9. data/app/controllers/admin/advanced_search_controller.rb +1 -1
  10. data/app/controllers/admin/assets_controller.rb +41 -5
  11. data/app/controllers/admin/bookmarks_controller.rb +14 -2
  12. data/app/controllers/admin/bulk_actions_controller.rb +31 -0
  13. data/app/controllers/admin/document_accesses_controller.rb +38 -0
  14. data/app/controllers/admin/document_assets_controller.rb +46 -9
  15. data/app/controllers/admin/document_distributions_controller.rb +172 -0
  16. data/app/controllers/admin/documents_controller.rb +41 -55
  17. data/app/controllers/admin/elements_controller.rb +22 -0
  18. data/app/controllers/admin/form_elements_controller.rb +31 -0
  19. data/app/controllers/admin/import_documents_controller.rb +11 -1
  20. data/app/controllers/admin/imports_controller.rb +32 -2
  21. data/app/controllers/admin/mappings_controller.rb +15 -0
  22. data/app/controllers/admin/notifications_controller.rb +27 -0
  23. data/app/controllers/admin/reference_types_controller.rb +106 -0
  24. data/app/controllers/admin/search_controller.rb +7 -0
  25. data/app/controllers/admin/users_controller.rb +10 -0
  26. data/app/helpers/asset_helper.rb +6 -0
  27. data/app/helpers/bulk_actions_helper.rb +9 -0
  28. data/app/helpers/document_helper.rb +36 -0
  29. data/app/helpers/geoblacklight_admin_helper.rb +88 -8
  30. data/app/helpers/mappings_helper.rb +26 -0
  31. data/app/indexers/document_indexer.rb +22 -2
  32. data/app/javascript/channels/consumer.js +6 -0
  33. data/app/javascript/channels/export_channel.js +30 -0
  34. data/app/javascript/channels/index.js +3 -0
  35. data/app/javascript/controllers/results_controller.js +14 -0
  36. data/app/javascript/index.js +8 -2
  37. data/app/jobs/export_job.rb +35 -8
  38. data/app/jobs/geoblacklight_admin/delete_thumbnail_job.rb +19 -0
  39. data/app/jobs/geoblacklight_admin/remove_parent_dct_references_uri_job.rb +16 -0
  40. data/app/jobs/geoblacklight_admin/set_parent_dct_references_uri_job.rb +17 -0
  41. data/app/jobs/geoblacklight_admin/store_image_job.rb +22 -0
  42. data/app/models/asset.rb +20 -0
  43. data/app/models/bulk_action.rb +2 -1
  44. data/app/models/document/geom_validator.rb +8 -0
  45. data/app/models/document/reference.rb +65 -65
  46. data/app/models/document.rb +128 -71
  47. data/app/models/document_distribution.rb +145 -0
  48. data/app/models/element.rb +2 -0
  49. data/app/models/geoblacklight_admin/schema.rb +10 -2
  50. data/app/models/import_document_state_machine.rb +1 -0
  51. data/app/models/reference_type.rb +40 -0
  52. data/app/models/user.rb +4 -2
  53. data/app/services/export_csv_document_distributions_service.rb +61 -0
  54. data/app/services/geoblacklight_admin/image_service/tms.rb +0 -4
  55. data/app/services/geoblacklight_admin/image_service.rb +1 -1
  56. data/app/services/geoblacklight_admin/item_viewer.rb +4 -4
  57. data/app/views/admin/bulk_actions/show.html.erb +1 -1
  58. data/app/views/admin/document_accesses/import.html.erb +6 -2
  59. data/app/views/admin/document_assets/_assets_table.html.erb +49 -0
  60. data/app/views/admin/document_assets/_form.html.erb +2 -3
  61. data/app/views/admin/document_assets/index.html.erb +1 -47
  62. data/app/views/admin/document_distributions/_document_distribution.html.erb +39 -0
  63. data/app/views/admin/document_distributions/_document_distribution.json.jbuilder +2 -0
  64. data/app/views/admin/document_distributions/_form.html.erb +34 -0
  65. data/app/views/admin/document_distributions/destroy_all.html.erb +82 -0
  66. data/app/views/admin/document_distributions/edit.html.erb +12 -0
  67. data/app/views/admin/document_distributions/import.html.erb +80 -0
  68. data/app/views/admin/document_distributions/index.html.erb +143 -0
  69. data/app/views/admin/document_distributions/index.json.jbuilder +1 -0
  70. data/app/views/admin/document_distributions/new.html.erb +11 -0
  71. data/app/views/admin/document_distributions/show.html.erb +10 -0
  72. data/app/views/admin/document_distributions/show.json.jbuilder +1 -0
  73. data/app/views/admin/documents/_document.html.erb +1 -3
  74. data/app/views/admin/documents/_form.html.erb +2 -4
  75. data/app/views/admin/documents/_form_control.html.erb +5 -2
  76. data/app/views/admin/documents/_form_nav.html.erb +14 -5
  77. data/app/views/admin/documents/_form_nav_kithe.html.erb +4 -1
  78. data/app/views/admin/documents/_json_aardvark.jbuilder +1 -1
  79. data/app/views/admin/documents/_json_gbl_v1.jbuilder +1 -1
  80. data/app/views/admin/documents/_result_selected_options.html.erb +5 -2
  81. data/app/views/admin/documents/admin.html.erb +5 -5
  82. data/app/views/admin/documents/features/_document_references.html.erb +23 -0
  83. data/app/views/admin/documents/features/_multiple_download_links.html.erb +29 -26
  84. data/app/views/admin/ids/fetch.json.jbuilder +0 -2
  85. data/app/views/admin/ids/index.json.jbuilder +0 -2
  86. data/app/views/admin/imports/_form.html.erb +1 -1
  87. data/app/views/admin/imports/show.html.erb +1 -1
  88. data/app/views/admin/layouts/application.html.erb +4 -2
  89. data/app/views/admin/reference_types/_form.html.erb +25 -0
  90. data/app/views/admin/reference_types/_reference_type.html.erb +52 -0
  91. data/app/views/admin/reference_types/_reference_type.json.jbuilder +2 -0
  92. data/app/views/admin/reference_types/edit.html.erb +12 -0
  93. data/app/views/admin/reference_types/index.html.erb +52 -0
  94. data/app/views/admin/reference_types/index.json.jbuilder +1 -0
  95. data/app/views/admin/reference_types/new.html.erb +11 -0
  96. data/app/views/admin/reference_types/show.html.erb +3 -0
  97. data/app/views/admin/reference_types/show.json.jbuilder +1 -0
  98. data/app/views/admin/shared/_footer.html.erb +5 -2
  99. data/app/views/admin/shared/_js_behaviors.html.erb +2 -3
  100. data/app/views/admin/shared/_navbar.html.erb +9 -2
  101. data/app/views/admin/users/index.html.erb +0 -1
  102. data/app/views/catalog/_show_gbl_admin.html.erb +1 -1
  103. data/config/initializers/defaults.yml +310 -0
  104. data/config/initializers/rails_config.rb +8 -0
  105. data/config/locales/documents.en.yml +14 -0
  106. data/config/routes.rb +30 -5
  107. data/db/import_references_schema_support.numbers +0 -0
  108. data/db/migrate/20230316183001_add_geoblacklight_admin_gem.rb +0 -12
  109. data/db/migrate/20241009200524_create_admin_reference_types.rb +13 -0
  110. data/db/migrate/20241010161420_create_document_references.rb +14 -0
  111. data/db/migrate/20241120238823_rename_references_to_distributions.rb +5 -0
  112. data/db/seeds.rb +5 -0
  113. data/db/seeds_elements.csv +1 -1
  114. data/db/seeds_elements.numbers +0 -0
  115. data/db/seeds_reference_types.csv +29 -0
  116. data/db/seeds_reference_types.numbers +0 -0
  117. data/db/structure.sql +1 -38
  118. data/lib/compose.yml +31 -0
  119. data/lib/generators/geoblacklight_admin/config_generator.rb +48 -12
  120. data/lib/generators/geoblacklight_admin/install_generator.rb +8 -0
  121. data/lib/generators/geoblacklight_admin/templates/config/database.yml +1 -1
  122. data/lib/generators/geoblacklight_admin/templates/config/initializers/devise.rb +0 -2
  123. data/lib/generators/geoblacklight_admin/templates/config/initializers/mime_types.rb +1 -0
  124. data/lib/generators/geoblacklight_admin/templates/demo-app/Dockerfile +31 -0
  125. data/lib/generators/geoblacklight_admin/templates/demo-app/compose.yml +42 -0
  126. data/lib/generators/geoblacklight_admin/templates/demo-app/start-server.sh +21 -0
  127. data/lib/geoblacklight_admin/engine.rb +4 -0
  128. data/lib/geoblacklight_admin/tasks/distributions.rake +69 -0
  129. data/lib/geoblacklight_admin/tasks/images.rake +1 -0
  130. data/lib/geoblacklight_admin/tasks/solr.rake +31 -0
  131. data/lib/geoblacklight_admin/version.rb +1 -1
  132. data/lib/geoblacklight_admin.rb +4 -0
  133. metadata +78 -41
  134. data/app/javascript/entrypoints/engine.js +0 -8
  135. data/config/locales/devise_invitable.en.yml +0 -31
  136. data/lib/generators/geoblacklight_admin/templates/devise/invitations/edit.html.erb +0 -15
  137. data/lib/generators/geoblacklight_admin/templates/devise/invitations/new.html.erb +0 -15
  138. data/lib/generators/geoblacklight_admin/templates/devise/mailer/invitation_instructions.html.erb +0 -11
  139. data/lib/generators/geoblacklight_admin/templates/devise/mailer/invitation_instructions.text.erb +0 -11
@@ -1,7 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # DocumentHelper
4
+ #
5
+ # This module provides helper methods for handling document-related
6
+ # functionalities such as generating publication state badges, creating
7
+ # localized links, and handling pagination links.
4
8
  module DocumentHelper
9
+ # Generates a badge for the publication state of a document.
10
+ #
11
+ # @param document [Object] the document object containing the publication state
12
+ # @return [String] HTML link with a span element representing the publication state
5
13
  def publication_state_badge(document)
6
14
  case document.publication_state
7
15
  when "draft"
@@ -19,15 +27,27 @@ module DocumentHelper
19
27
  end
20
28
  end
21
29
 
30
+ # Localizes a given link by parsing its URI and appending it to a base path.
31
+ #
32
+ # @param link [String] the link to be localized
33
+ # @return [String] the localized link
22
34
  def localize_link(link)
23
35
  uri = URI.parse(link)
24
36
  "/admin/documents?#{uri.query}"
25
37
  end
26
38
 
39
+ # Creates a sort link with a label and localized URL.
40
+ #
41
+ # @param link [Hash] a hash containing link attributes and self link
42
+ # @return [String] HTML link element for sorting
27
43
  def sort_link(link)
28
44
  link_to link["attributes"]["label"], localize_link(link["links"]["self"]), {class: "dropdown-item"}
29
45
  end
30
46
 
47
+ # Processes a link from an API response, determining whether to add or remove a facet.
48
+ #
49
+ # @param link [Hash] a hash containing links for adding or removing facets
50
+ # @return [Hash] a hash with action and link keys
31
51
  def link_from_api(link)
32
52
  # Append facet - Full URI returned
33
53
  uri = URI.parse(link["links"]["self"])
@@ -38,6 +58,10 @@ module DocumentHelper
38
58
  {action: "remove", link: "/admin/documents?#{uri.query}"}
39
59
  end
40
60
 
61
+ # Generates a link to the previous page in a paginated list.
62
+ #
63
+ # @param links [Hash] a hash containing pagination links
64
+ # @return [String] HTML link element for the previous page
41
65
  def previous_link(links)
42
66
  if links["prev"]
43
67
  link_to "Previous", localize_link(links["prev"]), {class: "btn btn-outline-primary btn-sm"}
@@ -46,6 +70,10 @@ module DocumentHelper
46
70
  end
47
71
  end
48
72
 
73
+ # Generates a link to the next page in a paginated list.
74
+ #
75
+ # @param links [Hash] a hash containing pagination links
76
+ # @return [String] HTML link element for the next page
49
77
  def next_link(links)
50
78
  if links["next"]
51
79
  link_to "Next", localize_link(links["next"]), {class: "btn btn-outline-primary btn-sm"}
@@ -54,10 +82,18 @@ module DocumentHelper
54
82
  end
55
83
  end
56
84
 
85
+ # Constructs a link to a document's page in the Blacklight catalog.
86
+ #
87
+ # @param document [Object] the document object
88
+ # @return [String] the URL to the document's Blacklight catalog page
57
89
  def blacklight_link(document)
58
90
  "#{BLACKLIGHT_URL}/catalog/#{document.friendlier_id}"
59
91
  end
60
92
 
93
+ # Determines if a document's thumbnail should be rendered.
94
+ #
95
+ # @param document [Object] the document object
96
+ # @return [Boolean] true if the thumbnail should be rendered, false otherwise
61
97
  def thumb_to_render?(document)
62
98
  document&.thumbnail&.file_url&.present? && document&.thumbnail&.file_derivatives&.present?
63
99
  end
@@ -1,13 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # GeoblacklightAdminHelper
4
+ #
5
+ # This module provides helper methods for the GeoBlacklight admin interface.
6
+ # It includes methods for handling JSON data, generating paths, formatting
7
+ # flash messages, and more.
4
8
  module GeoblacklightAdminHelper
5
9
  # @TODO:
6
10
  # Cannot generate app if uncommented...
7
11
  # Uncomment after app is generated to fix view errors
8
12
  include ::Pagy::Frontend if defined?(Pagy)
9
13
 
10
- # jbuilder helper
14
+ # Removes blank values from JSON data.
15
+ #
16
+ # @param value [String, Array] the value to check for presence
17
+ # @return [String, Array, nil] the original value if present, otherwise nil
11
18
  def no_json_blanks(value)
12
19
  case value
13
20
  when String
@@ -17,17 +24,21 @@ module GeoblacklightAdminHelper
17
24
  end
18
25
  end
19
26
 
20
- # qa (questioning_authoriry) gem oddly gives us no route helpers, so
21
- # let's make one ourselves, for it's current mount point, we can change
22
- # it if needed but at least it's DRY.
27
+ # Generates a search path for the QA (questioning_authority) gem.
28
+ #
29
+ # @param vocab [String] the vocabulary to search
30
+ # @param subauthority [String, nil] the subauthority to search
31
+ # @return [String] the generated search path
23
32
  def qa_search_vocab_path(vocab, subauthority = nil)
24
33
  path = "/authorities/search/#{CGI.escape vocab}"
25
-
26
34
  path += "/#{CGI.escape subauthority}" if subauthority
27
-
28
35
  path
29
36
  end
30
37
 
38
+ # Maps flash message levels to CSS classes.
39
+ #
40
+ # @param level [String] the flash message level
41
+ # @return [String] the corresponding CSS class
31
42
  def flash_class(level)
32
43
  alerts = {
33
44
  "notice" => "alert alert-info",
@@ -38,6 +49,9 @@ module GeoblacklightAdminHelper
38
49
  alerts[level]
39
50
  end
40
51
 
52
+ # Provides a mapping of institution codes to institution names.
53
+ #
54
+ # @return [Hash] a hash mapping institution codes to names
41
55
  def b1g_institution_codes
42
56
  {
43
57
  "01" => "Indiana University",
@@ -58,11 +72,17 @@ module GeoblacklightAdminHelper
58
72
  }
59
73
  end
60
74
 
75
+ # Generates an HTML badge for bookmarks.
76
+ #
77
+ # @return [String] the HTML string for the bookmarks badge
61
78
  def bookmarks_badge
62
79
  bookmarks_classes = ["badge", "badge-dark"]
63
80
  "<span class='#{bookmarks_classes.join(" ")}' id='bookmarks-count'>#{current_user.bookmarks.size}</span>"
64
81
  end
65
82
 
83
+ # Generates an HTML badge for notifications.
84
+ #
85
+ # @return [String] the HTML string for the notifications badge
66
86
  def notifications_badge
67
87
  notifications_classes = ["badge"]
68
88
  notifications_classes << "badge-dark" if current_user.notifications.unread.empty?
@@ -70,7 +90,10 @@ module GeoblacklightAdminHelper
70
90
  "<span class='#{notifications_classes.join(" ")}' id='notification-count'>#{current_user.notifications.unread.size}</span>"
71
91
  end
72
92
 
73
- # From Blacklight::HiddenSearchStateComponent
93
+ # Converts parameters into hidden form fields.
94
+ #
95
+ # @param params [Hash] the parameters to convert
96
+ # @return [String] the HTML string of hidden fields
74
97
  def params_as_hidden_fields(params)
75
98
  hidden_fields = []
76
99
  flatten_hash(params).each do |name, value|
@@ -83,6 +106,12 @@ module GeoblacklightAdminHelper
83
106
  safe_join(hidden_fields, "\n")
84
107
  end
85
108
 
109
+ # Flattens a nested hash into a single-level hash with keys representing the
110
+ # nested structure.
111
+ #
112
+ # @param hash [Hash] the hash to flatten
113
+ # @param ancestor_names [Array] the ancestor keys for nested hashes
114
+ # @return [Hash] the flattened hash
86
115
  def flatten_hash(hash, ancestor_names = [])
87
116
  flat_hash = {}
88
117
  hash.each do |k, v|
@@ -100,6 +129,10 @@ module GeoblacklightAdminHelper
100
129
  flat_hash
101
130
  end
102
131
 
132
+ # Generates a key for a flattened hash from an array of names.
133
+ #
134
+ # @param names [Array] the array of names
135
+ # @return [String] the generated key
103
136
  def flat_hash_key(names)
104
137
  names = Array.new(names)
105
138
  name = names.shift.to_s.dup
@@ -109,6 +142,10 @@ module GeoblacklightAdminHelper
109
142
  name
110
143
  end
111
144
 
145
+ # Maps a character to a CSS class for diff highlighting.
146
+ #
147
+ # @param char [String] the character representing a diff operation
148
+ # @return [String] the corresponding CSS class
112
149
  def diff_class(char)
113
150
  case char
114
151
  when "~"
@@ -122,6 +159,10 @@ module GeoblacklightAdminHelper
122
159
  end
123
160
  end
124
161
 
162
+ # Generates a link to the admin import page for a given import.
163
+ #
164
+ # @param import [Object] the import object
165
+ # @return [String] the HTML link to the admin import page
125
166
  def link_to_admin_import(import)
126
167
  path = admin_documents_path(
127
168
  {
@@ -132,6 +173,12 @@ module GeoblacklightAdminHelper
132
173
  link_to import.name, path
133
174
  end
134
175
 
176
+ # Generates a link to the GeoBlacklight import page with optional state.
177
+ #
178
+ # @param label [String] the link label
179
+ # @param import [Object] the import object
180
+ # @param state [Boolean] the publication state
181
+ # @return [String] the HTML link to the GeoBlacklight import page
135
182
  def link_to_gbl_import(label, import, state = false)
136
183
  path = if state
137
184
  blacklight_path(
@@ -152,7 +199,40 @@ module GeoblacklightAdminHelper
152
199
  link_to(label, path)
153
200
  end
154
201
 
202
+ # Generates options for asset DCT references.
203
+ #
204
+ # @return [String] the escaped JavaScript string of options
155
205
  def assets_dct_references_options
156
- escape_javascript(options_for_select(I18n.t("activemodel.enum_values.document/reference.category").invert.sort.insert(0, ["Choose Reference Type", nil]))).to_s
206
+ escape_javascript(options_for_select(I18n.t("activemodel.asset_enum_values.document/reference.category").invert.sort.insert(0, ["Choose Reference Type", nil]))).to_s
207
+ end
208
+
209
+ # Determines if a document's thumbnail should be rendered.
210
+ #
211
+ # @param document [Object] the document object
212
+ # @return [Boolean] true if the thumbnail should be rendered, false otherwise
213
+ def thumb_to_render?(document)
214
+ if document&.thumbnail&.file_url&.present? && document&.thumbnail&.file_derivatives&.present?
215
+ true
216
+ elsif document&.document_assets&.any?
217
+ document.document_assets.any? do |asset|
218
+ asset.file_derivatives&.key?(:thumb_standard_2X)
219
+ end
220
+ else
221
+ false
222
+ end
223
+ end
224
+
225
+ # Returns the URL of the thumbnail to render for a document.
226
+ #
227
+ # @param document [Object] the document object
228
+ # @return [String] the URL of the thumbnail to render
229
+ def thumbnail_to_render(document)
230
+ if document&.thumbnail&.file_url&.present? && document&.thumbnail&.file_derivatives&.present?
231
+ document.thumbnail.file_url(:thumb_standard_2X)
232
+ elsif document&.document_assets&.any?
233
+ document.document_assets.find do |asset|
234
+ asset.file_derivatives&.key?(:thumb_standard_2X)
235
+ end&.file_url(:thumb_standard_2X)
236
+ end
157
237
  end
158
238
  end
@@ -1,7 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # MappingsHelper
4
+ #
5
+ # This module provides helper methods for handling mappings in the application.
6
+ # It includes methods to generate attribute collections and provide mapping
7
+ # and delimiter suggestions based on import configurations.
4
8
  module MappingsHelper
9
+ # Returns a collection of attributes that can be used for mapping.
10
+ #
11
+ # The collection is generated from importable elements, sorted, and includes
12
+ # additional options for an empty string and "Discard".
13
+ #
14
+ # @return [Array<String>] a sorted array of attribute names with additional options.
5
15
  def attribute_collection
6
16
  attrs = Element.importable.map(&:solr_field).sort
7
17
  attrs.prepend("")
@@ -9,6 +19,14 @@ module MappingsHelper
9
19
  attrs
10
20
  end
11
21
 
22
+ # Provides a mapping suggestion for a given header based on the import configuration.
23
+ #
24
+ # Checks if the header is included in the import's mapping configuration and returns
25
+ # the destination if available.
26
+ #
27
+ # @param import [Object] the import object containing mapping configurations.
28
+ # @param header [String] the header for which the mapping suggestion is needed.
29
+ # @return [String, false] the destination mapping if available, otherwise false.
12
30
  def mapping_suggestion(import, header)
13
31
  if import.mapping_configuration.include?(header.to_sym)
14
32
  import.mapping_configuration[header.to_sym][:destination]
@@ -17,6 +35,14 @@ module MappingsHelper
17
35
  end
18
36
  end
19
37
 
38
+ # Provides a delimiter suggestion for a given header based on the import configuration.
39
+ #
40
+ # Checks if the header is included in the import's mapping configuration and returns
41
+ # the delimiter if available.
42
+ #
43
+ # @param import [Object] the import object containing mapping configurations.
44
+ # @param header [String] the header for which the delimiter suggestion is needed.
45
+ # @return [String, false] the delimiter if available, otherwise false.
20
46
  def delimiter_suggestion(import, header)
21
47
  if import.mapping_configuration.include?(header.to_sym)
22
48
  import.mapping_configuration[header.to_sym][:delimited]
@@ -1,6 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Solr indexing for our document class. Still a work in progress.
4
+ #
5
+ # The DocumentIndexer class is responsible for configuring how documents
6
+ # are indexed into Solr. It uses the Kithe::Indexer framework to map
7
+ # document attributes to Solr fields.
8
+ #
9
+ # The configuration block defines various fields that are indexed, including
10
+ # fields specific to GeoBlacklight and custom fields defined via the Element model.
11
+ #
12
+ # Fields:
13
+ # - model_pk_ssi: The primary key of the model, extracted from the object's ID.
14
+ # - gbl_mdVersion_s: A static version string for GeoBlacklight.
15
+ # - gbl_mdModified_dt: The modification date of the metadata.
16
+ # - date_created_dtsi: The creation date of the record, in UTC ISO8601 format.
17
+ # - date_modified_dtsi: The last modification date of the record, in UTC ISO8601 format.
18
+ # - b1g_geom_import_id_ssi: The import ID for GeoBlacklight administration.
19
+ #
20
+ # If the "elements" table exists, additional fields are indexed based on
21
+ # the Element model's configuration.
4
22
  class DocumentIndexer < Kithe::Indexer
5
23
  configure do
6
24
  # Kithe
@@ -12,8 +30,10 @@ class DocumentIndexer < Kithe::Indexer
12
30
  # to_field 'geomg_id_s', obj_extract('friendlier_id') # the actual db pk, a UUID
13
31
 
14
32
  # Define `to_field`(s) via Element
15
- Element.indexable.each do |elm|
16
- to_field elm.solr_field, obj_extract(elm.index_value)
33
+ if ActiveRecord::Base.connection.table_exists?("elements")
34
+ Element.indexable.each do |elm|
35
+ to_field elm.solr_field, obj_extract(elm.index_value)
36
+ end
17
37
  end
18
38
 
19
39
  to_field "gbl_mdModified_dt", obj_extract("gbl_mdModified_dt")
@@ -0,0 +1,6 @@
1
+ // Action Cable provides the framework to deal with WebSockets in Rails.
2
+ // You can generate new channels where WebSocket features live using the `bin/rails generate channel` command.
3
+
4
+ import { createConsumer } from "@rails/actioncable"
5
+
6
+ export default createConsumer()
@@ -0,0 +1,30 @@
1
+ import consumer from "./consumer"
2
+
3
+ consumer.subscriptions.create({ channel: "ExportChannel" }, {
4
+ connected() {
5
+ // Called when the subscription is ready for use on the server
6
+ console.log("GBL Admin - ExportChannel connected");
7
+ },
8
+
9
+ disconnected() {
10
+ // Called when the subscription has been terminated by the server
11
+ console.log("GBL Admin - ExportChannel disconnected");
12
+ },
13
+
14
+ received(data) {
15
+ console.log('GBL Admin - ExportChannel received!');
16
+ console.log(data);
17
+
18
+ if (data['progress']) {
19
+ console.log(data['progress']);
20
+ }
21
+
22
+ if (data['actions']) {
23
+ for (let index = 0; index < data.actions.length; ++index) {
24
+ var fnstring = data.actions[index].method;
25
+ var fn = window["GBLADMIN"][fnstring];
26
+ if (typeof fn === "function") fn(data.actions[index].payload);
27
+ }
28
+ }
29
+ }
30
+ });
@@ -0,0 +1,3 @@
1
+ export { default as consumer } from './consumer';
2
+ export { default as exportChannel } from './export_channel';
3
+ export { default as index } from './index';
@@ -12,6 +12,7 @@ import { Controller } from "stimulus"
12
12
  export default class extends Controller {
13
13
 
14
14
  connect() {
15
+ console.log("GBL Admin - ResultsController connected");
15
16
  }
16
17
 
17
18
  checkedState(checked, selector='input[type=checkbox]') {
@@ -228,6 +229,7 @@ export default class extends Controller {
228
229
  }
229
230
 
230
231
  exportCsvDocumentDownloads() {
232
+ console.log('Export - CsvDocumentDownloads')
231
233
  var scope = this.checkSelectionScope();
232
234
  var el = document.querySelector('#result-selected-options');
233
235
  if(scope === 'pageset') {
@@ -238,6 +240,7 @@ export default class extends Controller {
238
240
  }
239
241
 
240
242
  exportCsvDocumentAccessLinks() {
243
+ console.log('Export - CsvDocumentAccessLinks')
241
244
  var scope = this.checkSelectionScope();
242
245
  var el = document.querySelector('#result-selected-options');
243
246
  if(scope === 'pageset') {
@@ -247,6 +250,17 @@ export default class extends Controller {
247
250
  }
248
251
  }
249
252
 
253
+ exportCsvDocumentDistributions() {
254
+ console.log('Export - CsvDocumentDistributions')
255
+ var scope = this.checkSelectionScope();
256
+ var el = document.querySelector('#result-selected-options');
257
+ if(scope === 'pageset') {
258
+ window.location = el.dataset.pageset + "&format=csv_document_distributions"
259
+ } else {
260
+ window.location = el.dataset.resultset + "&format=csv_document_distributions"
261
+ }
262
+ }
263
+
250
264
  bulkAction() {
251
265
  var scope = this.checkSelectionScope();
252
266
  var el = document.querySelector('#result-selected-options');
@@ -1,8 +1,14 @@
1
1
  console.log('Vite ⚡️ Rails - GBL Admin')
2
2
 
3
- // Stimulus
3
+ // Import Stimulus and controllers
4
4
  import { Application } from '@hotwired/stimulus'
5
5
  import ResultsController from "./controllers/results_controller"
6
6
 
7
+ // Initialize Stimulus
7
8
  window.Stimulus = Application.start()
8
- Stimulus.register("results", ResultsController)
9
+
10
+ // Register controllers
11
+ Stimulus.register("results", ResultsController)
12
+
13
+ // Import channels
14
+ import '../channels';
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "csv"
4
+ require "zip"
4
5
 
5
6
  # ExportJob
6
7
  class ExportJob < ApplicationJob
@@ -23,27 +24,53 @@ class ExportJob < ApplicationJob
23
24
  logger.debug("Document Ids: #{document_ids}")
24
25
 
25
26
  # Send progress
26
- file_content = export_service.call(document_ids)
27
+ file_content_documents = export_service.call(document_ids)
28
+ file_content_document_distributions = ExportCsvDocumentDistributionsService.call(document_ids)
27
29
 
28
- # Write into tempfile
29
- @tempfile = Tempfile.new(["export-#{Time.zone.today}", ".csv"]).tap do |file|
30
+ # Write Documents into tempfile
31
+ @tempfile_documents = Tempfile.new(["documents-#{Time.zone.today}", ".csv"]).tap do |file|
30
32
  CSV.open(file, "wb") do |csv|
31
- file_content.each do |row|
33
+ file_content_documents.each do |row|
32
34
  csv << row
33
35
  end
34
36
  end
37
+ logger.debug("Tempfile Documents Path: #{file.path}")
38
+ logger.debug("Tempfile Documents Size: #{File.size(file.path)} bytes")
35
39
  end
36
40
 
41
+ # Write DocumentDistributions into tempfile
42
+ @tempfile_document_distributions = Tempfile.new(["document-distributions-#{Time.zone.today}", ".csv"]).tap do |file|
43
+ CSV.open(file, "wb") do |csv|
44
+ file_content_document_distributions.each do |row|
45
+ csv << row
46
+ end
47
+ end
48
+ logger.debug("Tempfile Document Distributions Path: #{file.path}")
49
+ logger.debug("Tempfile Document Distributions Size: #{File.size(file.path)} bytes")
50
+ end
51
+
52
+ # Create a zip file containing both tempfiles
53
+ zipfile_name = "export-#{Time.zone.today}.zip"
54
+ tmp_dir = Rails.root.join("tmp")
55
+ @tempfile_zip = Tempfile.new([zipfile_name, ".zip"], tmp_dir)
56
+
57
+ Zip::File.open(@tempfile_zip.path, Zip::File::CREATE) do |zipfile|
58
+ zipfile.add("documents.csv", @tempfile_documents.path)
59
+ zipfile.add("document-distributions.csv", @tempfile_document_distributions.path)
60
+ end
61
+ logger.debug("Zipfile Path: #{@tempfile_zip.path}")
62
+ logger.debug("Zipfile Size: #{File.size(@tempfile_zip.path)} bytes")
63
+
37
64
  # Create notification
38
65
  # Message: "Download Type|Row Count|Button Label"
39
- notification = ExportNotification.with(message: "CSV (#{export_service.short_name})|#{ActionController::Base.helpers.number_with_delimiter(file_content.size - 1)} rows|CSV")
66
+ notification = ExportNotification.with(message: "ZIP (#{export_service.short_name})|#{ActionController::Base.helpers.number_with_delimiter(file_content_documents.size - 1)} rows|ZIP")
40
67
 
41
68
  # Deliver notification
42
69
  notification.deliver(current_user)
43
70
 
44
- # Attach CSV file (can only attach after persisted)
45
- notification.record.file.attach(io: @tempfile, filename: "geomg-export-#{Time.zone.today}.csv",
46
- content_type: "text/csv")
71
+ # Attach ZIP file (can only attach after persisted)
72
+ notification.record.file.attach(io: File.open(@tempfile_zip), filename: zipfile_name,
73
+ content_type: "application/zip")
47
74
 
48
75
  # Update UI
49
76
  ActionCable.server.broadcast("export_channel", {
@@ -1,11 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ ##
4
+ # Module for GeoblacklightAdmin related jobs.
3
5
  module GeoblacklightAdmin
6
+ ##
7
+ # A job to delete the thumbnail associated with a Solr document.
8
+ #
9
+ # This job is enqueued with a priority queue and can optionally handle
10
+ # a bad document ID to transition its state.
4
11
  class DeleteThumbnailJob < ApplicationJob
12
+ ##
13
+ # Determines the queue to use based on the last argument.
5
14
  queue_as do
6
15
  arguments.last
7
16
  end
8
17
 
18
+ ##
19
+ # Performs the job to delete a thumbnail.
20
+ #
21
+ # @param solr_document_id [String] the ID of the Solr document
22
+ # @param bad_id [String, nil] optional ID of a bad document to transition
23
+ # @param queue [Symbol] the queue to use, defaults to :priority
24
+ #
25
+ # If the document has a thumbnail, it will be destroyed.
26
+ # If a bad_id is provided, it will transition the state of the
27
+ # BulkActionDocument to :success.
9
28
  def perform(solr_document_id, bad_id = nil, queue = :priority)
10
29
  document = Document.find_by_friendlier_id(solr_document_id)
11
30
  if document.thumbnail.present?
@@ -1,9 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ ##
4
+ # Module for GeoblacklightAdmin jobs.
3
5
  module GeoblacklightAdmin
6
+ ##
7
+ # Job to remove a specific DCT references URI from the parent asset.
8
+ #
9
+ # This job is queued with a priority level and is responsible for
10
+ # removing a DCT references URI from the parent of a given asset.
4
11
  class RemoveParentDctReferencesUriJob < ApplicationJob
5
12
  queue_as :priority
6
13
 
14
+ ##
15
+ # Performs the job of removing the DCT references URI from the parent asset.
16
+ #
17
+ # This method checks if the asset has a `dct_references_uri_key` present.
18
+ # If present, it deletes the URI from the parent's `dct_references_s` array
19
+ # if it matches the asset's full file URL, and then saves the parent asset.
20
+ #
21
+ # @param asset [Object] The asset whose parent's DCT references URI is to be removed.
22
+ # @raise [StandardError] Logs an error if an exception occurs during the process.
7
23
  def perform(asset)
8
24
  if asset.dct_references_uri_key.present?
9
25
  asset.parent.dct_references_s.delete_if { |i| i.value == asset.full_file_url }
@@ -1,9 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ ##
4
+ # Module for GeoblacklightAdmin jobs.
3
5
  module GeoblacklightAdmin
6
+ ##
7
+ # Job to set the parent DCT references URI for a given asset.
8
+ #
9
+ # This job is responsible for creating a new reference for the asset's
10
+ # DCT references URI key and appending it to the parent asset's
11
+ # `dct_references_s` array. It then saves the parent asset.
12
+ #
13
+ # If an error occurs during the process, it logs the error message.
4
14
  class SetParentDctReferencesUriJob < ApplicationJob
15
+ # Sets the queue for this job to :priority
5
16
  queue_as :priority
6
17
 
18
+ ##
19
+ # Performs the job to set the parent DCT references URI.
20
+ #
21
+ # @param asset [Object] The asset for which the DCT references URI is to be set.
22
+ #
23
+ # @return [void]
7
24
  def perform(asset)
8
25
  if asset.dct_references_uri_key.present?
9
26
  reference = Document::Reference.new
@@ -1,11 +1,33 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ ##
4
+ # Module for GeoblacklightAdmin jobs.
3
5
  module GeoblacklightAdmin
6
+ ##
7
+ # StoreImageJob is responsible for handling the storage of images
8
+ # associated with a Solr document. It manages the lifecycle of the
9
+ # image storage process, including state transitions and error handling.
4
10
  class StoreImageJob < ApplicationJob
11
+ ##
12
+ # Sets the queue for the job based on the last argument provided.
5
13
  queue_as do
6
14
  arguments.last
7
15
  end
8
16
 
17
+ ##
18
+ # Performs the job to store an image for a given Solr document.
19
+ #
20
+ # @param solr_document_id [String] the ID of the Solr document
21
+ # @param bad_id [String, nil] optional ID for a BulkActionDocument
22
+ # @param queue [Symbol] the queue to use for the job, defaults to :default
23
+ #
24
+ # This method:
25
+ # - Finds the document by its friendlier ID.
26
+ # - Deletes any existing thumbnail.
27
+ # - Transitions the document's thumbnail state to 'queued'.
28
+ # - Waits for a random period to ensure polite crawling.
29
+ # - Stores the image using the ImageService.
30
+ # - Transitions the BulkActionDocument state to 'success' if a bad_id is provided.
9
31
  def perform(solr_document_id, bad_id = nil, queue = :default)
10
32
  # Find the document
11
33
  document = Document.find_by_friendlier_id(solr_document_id)