geoblacklight_admin 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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)