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.
- checksums.yaml +4 -4
- data/README.md +18 -9
- data/Rakefile +83 -47
- data/app/assets/javascripts/geoblacklight_admin/chosen.js +1 -0
- data/app/assets/stylesheets/geoblacklight_admin/_core.scss +24 -0
- data/app/assets/stylesheets/geoblacklight_admin/modules/_nav.scss +0 -5
- data/app/assets/stylesheets/geoblacklight_admin/modules/_tables.scss +1 -1
- data/app/controllers/admin/admin_controller.rb +16 -0
- data/app/controllers/admin/advanced_search_controller.rb +1 -1
- data/app/controllers/admin/assets_controller.rb +41 -5
- data/app/controllers/admin/bookmarks_controller.rb +14 -2
- data/app/controllers/admin/bulk_actions_controller.rb +31 -0
- data/app/controllers/admin/document_accesses_controller.rb +38 -0
- data/app/controllers/admin/document_assets_controller.rb +46 -9
- data/app/controllers/admin/document_distributions_controller.rb +172 -0
- data/app/controllers/admin/documents_controller.rb +41 -55
- data/app/controllers/admin/elements_controller.rb +22 -0
- data/app/controllers/admin/form_elements_controller.rb +31 -0
- data/app/controllers/admin/import_documents_controller.rb +11 -1
- data/app/controllers/admin/imports_controller.rb +32 -2
- data/app/controllers/admin/mappings_controller.rb +15 -0
- data/app/controllers/admin/notifications_controller.rb +27 -0
- data/app/controllers/admin/reference_types_controller.rb +106 -0
- data/app/controllers/admin/search_controller.rb +7 -0
- data/app/controllers/admin/users_controller.rb +10 -0
- data/app/helpers/asset_helper.rb +6 -0
- data/app/helpers/bulk_actions_helper.rb +9 -0
- data/app/helpers/document_helper.rb +36 -0
- data/app/helpers/geoblacklight_admin_helper.rb +88 -8
- data/app/helpers/mappings_helper.rb +26 -0
- data/app/indexers/document_indexer.rb +22 -2
- data/app/javascript/channels/consumer.js +6 -0
- data/app/javascript/channels/export_channel.js +30 -0
- data/app/javascript/channels/index.js +3 -0
- data/app/javascript/controllers/results_controller.js +14 -0
- data/app/javascript/index.js +8 -2
- data/app/jobs/export_job.rb +35 -8
- data/app/jobs/geoblacklight_admin/delete_thumbnail_job.rb +19 -0
- data/app/jobs/geoblacklight_admin/remove_parent_dct_references_uri_job.rb +16 -0
- data/app/jobs/geoblacklight_admin/set_parent_dct_references_uri_job.rb +17 -0
- data/app/jobs/geoblacklight_admin/store_image_job.rb +22 -0
- data/app/models/asset.rb +20 -0
- data/app/models/bulk_action.rb +2 -1
- data/app/models/document/geom_validator.rb +8 -0
- data/app/models/document/reference.rb +65 -65
- data/app/models/document.rb +128 -71
- data/app/models/document_distribution.rb +145 -0
- data/app/models/element.rb +2 -0
- data/app/models/geoblacklight_admin/schema.rb +10 -2
- data/app/models/import_document_state_machine.rb +1 -0
- data/app/models/reference_type.rb +40 -0
- data/app/models/user.rb +4 -2
- data/app/services/export_csv_document_distributions_service.rb +61 -0
- data/app/services/geoblacklight_admin/image_service/tms.rb +0 -4
- data/app/services/geoblacklight_admin/image_service.rb +1 -1
- data/app/services/geoblacklight_admin/item_viewer.rb +4 -4
- data/app/views/admin/bulk_actions/show.html.erb +1 -1
- data/app/views/admin/document_accesses/import.html.erb +6 -2
- data/app/views/admin/document_assets/_assets_table.html.erb +49 -0
- data/app/views/admin/document_assets/_form.html.erb +2 -3
- data/app/views/admin/document_assets/index.html.erb +1 -47
- data/app/views/admin/document_distributions/_document_distribution.html.erb +39 -0
- data/app/views/admin/document_distributions/_document_distribution.json.jbuilder +2 -0
- data/app/views/admin/document_distributions/_form.html.erb +34 -0
- data/app/views/admin/document_distributions/destroy_all.html.erb +82 -0
- data/app/views/admin/document_distributions/edit.html.erb +12 -0
- data/app/views/admin/document_distributions/import.html.erb +80 -0
- data/app/views/admin/document_distributions/index.html.erb +143 -0
- data/app/views/admin/document_distributions/index.json.jbuilder +1 -0
- data/app/views/admin/document_distributions/new.html.erb +11 -0
- data/app/views/admin/document_distributions/show.html.erb +10 -0
- data/app/views/admin/document_distributions/show.json.jbuilder +1 -0
- data/app/views/admin/documents/_document.html.erb +1 -3
- data/app/views/admin/documents/_form.html.erb +2 -4
- data/app/views/admin/documents/_form_control.html.erb +5 -2
- data/app/views/admin/documents/_form_nav.html.erb +14 -5
- data/app/views/admin/documents/_form_nav_kithe.html.erb +4 -1
- data/app/views/admin/documents/_json_aardvark.jbuilder +1 -1
- data/app/views/admin/documents/_json_gbl_v1.jbuilder +1 -1
- data/app/views/admin/documents/_result_selected_options.html.erb +5 -2
- data/app/views/admin/documents/admin.html.erb +5 -5
- data/app/views/admin/documents/features/_document_references.html.erb +23 -0
- data/app/views/admin/documents/features/_multiple_download_links.html.erb +29 -26
- data/app/views/admin/ids/fetch.json.jbuilder +0 -2
- data/app/views/admin/ids/index.json.jbuilder +0 -2
- data/app/views/admin/imports/_form.html.erb +1 -1
- data/app/views/admin/imports/show.html.erb +1 -1
- data/app/views/admin/layouts/application.html.erb +4 -2
- data/app/views/admin/reference_types/_form.html.erb +25 -0
- data/app/views/admin/reference_types/_reference_type.html.erb +52 -0
- data/app/views/admin/reference_types/_reference_type.json.jbuilder +2 -0
- data/app/views/admin/reference_types/edit.html.erb +12 -0
- data/app/views/admin/reference_types/index.html.erb +52 -0
- data/app/views/admin/reference_types/index.json.jbuilder +1 -0
- data/app/views/admin/reference_types/new.html.erb +11 -0
- data/app/views/admin/reference_types/show.html.erb +3 -0
- data/app/views/admin/reference_types/show.json.jbuilder +1 -0
- data/app/views/admin/shared/_footer.html.erb +5 -2
- data/app/views/admin/shared/_js_behaviors.html.erb +2 -3
- data/app/views/admin/shared/_navbar.html.erb +9 -2
- data/app/views/admin/users/index.html.erb +0 -1
- data/app/views/catalog/_show_gbl_admin.html.erb +1 -1
- data/config/initializers/defaults.yml +310 -0
- data/config/initializers/rails_config.rb +8 -0
- data/config/locales/documents.en.yml +14 -0
- data/config/routes.rb +30 -5
- data/db/import_references_schema_support.numbers +0 -0
- data/db/migrate/20230316183001_add_geoblacklight_admin_gem.rb +0 -12
- data/db/migrate/20241009200524_create_admin_reference_types.rb +13 -0
- data/db/migrate/20241010161420_create_document_references.rb +14 -0
- data/db/migrate/20241120238823_rename_references_to_distributions.rb +5 -0
- data/db/seeds.rb +5 -0
- data/db/seeds_elements.csv +1 -1
- data/db/seeds_elements.numbers +0 -0
- data/db/seeds_reference_types.csv +29 -0
- data/db/seeds_reference_types.numbers +0 -0
- data/db/structure.sql +1 -38
- data/lib/compose.yml +31 -0
- data/lib/generators/geoblacklight_admin/config_generator.rb +48 -12
- data/lib/generators/geoblacklight_admin/install_generator.rb +8 -0
- data/lib/generators/geoblacklight_admin/templates/config/database.yml +1 -1
- data/lib/generators/geoblacklight_admin/templates/config/initializers/devise.rb +0 -2
- data/lib/generators/geoblacklight_admin/templates/config/initializers/mime_types.rb +1 -0
- data/lib/generators/geoblacklight_admin/templates/demo-app/Dockerfile +31 -0
- data/lib/generators/geoblacklight_admin/templates/demo-app/compose.yml +42 -0
- data/lib/generators/geoblacklight_admin/templates/demo-app/start-server.sh +21 -0
- data/lib/geoblacklight_admin/engine.rb +4 -0
- data/lib/geoblacklight_admin/tasks/distributions.rake +69 -0
- data/lib/geoblacklight_admin/tasks/images.rake +1 -0
- data/lib/geoblacklight_admin/tasks/solr.rake +31 -0
- data/lib/geoblacklight_admin/version.rb +1 -1
- data/lib/geoblacklight_admin.rb +4 -0
- metadata +78 -41
- data/app/javascript/entrypoints/engine.js +0 -8
- data/config/locales/devise_invitable.en.yml +0 -31
- data/lib/generators/geoblacklight_admin/templates/devise/invitations/edit.html.erb +0 -15
- data/lib/generators/geoblacklight_admin/templates/devise/invitations/new.html.erb +0 -15
- data/lib/generators/geoblacklight_admin/templates/devise/mailer/invitation_instructions.html.erb +0 -11
- 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
|
-
#
|
|
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
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
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
|
-
#
|
|
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.
|
|
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
|
-
|
|
16
|
-
|
|
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
|
+
});
|
|
@@ -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');
|
data/app/javascript/index.js
CHANGED
|
@@ -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
|
-
|
|
9
|
+
|
|
10
|
+
// Register controllers
|
|
11
|
+
Stimulus.register("results", ResultsController)
|
|
12
|
+
|
|
13
|
+
// Import channels
|
|
14
|
+
import '../channels';
|
data/app/jobs/export_job.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
-
@
|
|
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
|
-
|
|
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: "
|
|
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
|
|
45
|
-
notification.record.file.attach(io: @
|
|
46
|
-
content_type: "
|
|
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)
|