geoblacklight_admin 0.3.2 → 0.4.1

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -0
  3. data/app/controllers/admin/document_downloads_controller.rb +1 -1
  4. data/app/controllers/admin/ids_controller.rb +3 -0
  5. data/app/controllers/admin/imports_controller.rb +2 -2
  6. data/app/helpers/geoblacklight_admin_helper.rb +30 -0
  7. data/app/jobs/bulk_action_collect_documents.rb +11 -0
  8. data/app/jobs/geoblacklight_admin/store_image_job.rb +12 -0
  9. data/app/models/asset.rb +13 -0
  10. data/app/models/bulk_action.rb +8 -5
  11. data/app/models/bulk_actions/change_publication_state.rb +21 -0
  12. data/app/models/concerns/geoblacklight_admin/publication_state_search_behavior.rb +26 -0
  13. data/app/models/document/date_validator.rb +46 -0
  14. data/app/models/document.rb +114 -2
  15. data/app/models/document_access.rb +3 -1
  16. data/app/models/document_download.rb +3 -1
  17. data/app/models/element.rb +1 -0
  18. data/app/services/geoblacklight_admin/image_service/dynamic_map_layer.rb +15 -0
  19. data/app/services/geoblacklight_admin/image_service/iiif.rb +17 -0
  20. data/app/services/geoblacklight_admin/image_service/image_map_layer.rb +17 -0
  21. data/app/services/geoblacklight_admin/image_service/tiled_map_layer.rb +15 -0
  22. data/app/services/geoblacklight_admin/image_service/wms.rb +28 -0
  23. data/app/services/geoblacklight_admin/image_service.rb +238 -0
  24. data/app/services/geoblacklight_admin/item_viewer.rb +31 -0
  25. data/app/uploaders/asset_uploader.rb +31 -0
  26. data/app/views/admin/document_assets/index.html.erb +13 -1
  27. data/app/views/admin/documents/_document.html.erb +1 -1
  28. data/app/views/admin/documents/_document_kithe.html.erb +47 -0
  29. data/app/views/admin/documents/_form_nav.html.erb +1 -1
  30. data/app/views/admin/documents/_form_nav_kithe.html.erb +30 -0
  31. data/app/views/admin/documents/_result_facets.html.erb +26 -2
  32. data/app/views/admin/documents/index.html.erb +4 -8
  33. data/app/views/admin/documents/versions.html.erb +1 -1
  34. data/app/views/admin/imports/show.html.erb +35 -9
  35. data/app/views/admin/shared/_navbar.html.erb +6 -6
  36. data/db/migrate/20231106215104_bulk_action_sti.rb +10 -0
  37. data/lib/generators/geoblacklight_admin/config_generator.rb +33 -3
  38. data/{app/controllers/admin → lib/generators/geoblacklight_admin/templates}/api_controller.rb +4 -1
  39. data/lib/generators/geoblacklight_admin/templates/config/initializers/shrine.rb +3 -2
  40. data/lib/generators/geoblacklight_admin/templates/config/settings.yml +4 -1
  41. data/lib/generators/geoblacklight_admin/templates/views/_index_split_default.html.erb +27 -0
  42. data/lib/geoblacklight_admin/version.rb +1 -1
  43. data/lib/tasks/geoblacklight_admin/images.rake +30 -0
  44. metadata +24 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a8589ee3941b3bd4731accf0443452335f51b8f3f52a823e5c26306a089ccda0
4
- data.tar.gz: 8bf6d3d0a966b7ab815f79a89822f27549827f0a63dc00c8beae73048e1f8b8e
3
+ metadata.gz: 5514d1f3dfc7ba946eb58f955e9c3cfc76b073aeafcc86d0f62e3807bf013aba
4
+ data.tar.gz: 34c448fd3cb662cabf2f3f6cfd2f82b3b5166f681d7e4c0c3ef542ecad575893
5
5
  SHA512:
6
- metadata.gz: d1208b0966517c76db7276b26c0c17e50a595c8a120fc14ae5fd7144d448e53b345feb9614db1e41076562644756e7487e75f1146d652b8753f9a156bb7ab5bb
7
- data.tar.gz: 9cc8bc7a90157496e0d755f580ff1856317baaaa4bde49057388a97d5239aec1074523ecb3e8f40449381170245fcd9b70dd349d6751149077cda7483fe91d68
6
+ metadata.gz: 2dcfb661be04c25e22a1941471c06b1dabcf6ee14744fe2faf815172e32b2a00b06639a808fb35f5bb05fef8ef8b0ddde038c8a786b45d38f5aff3aa856c648a
7
+ data.tar.gz: 38a23540721636da43ee97a2068f10ed127a85b380ecb6d84f1c1ee0045848295b100d781f48762b0204d738530cccdfbd3578ef1927ff87bbe8f6c55349c61a
data/README.md CHANGED
@@ -77,6 +77,7 @@ bundle exec rails server
77
77
  ### Lint App
78
78
  ```bash
79
79
  standardrb .
80
+ standardrb --fix
80
81
  ```
81
82
 
82
83
  ### Test App
@@ -82,7 +82,7 @@ module Admin
82
82
 
83
83
  respond_to do |format|
84
84
  if DocumentDownload.destroy_all(params.dig(:document_download, :downloads, :file))
85
- format.html { redirect_to admin_document_downloads_path, notice: "Download Links were created destroyed." }
85
+ format.html { redirect_to admin_document_downloads_path, notice: "Download Links were destroyed." }
86
86
  else
87
87
  format.html { redirect_to admin_document_downloads_path, notice: "Download Links could not be destroyed." }
88
88
  end
@@ -141,6 +141,9 @@ module Admin
141
141
  # Publication State
142
142
  config.add_facet_field Settings.FIELDS.B1G_PUBLICATION_STATE, show: false
143
143
 
144
+ # Import ID
145
+ config.add_facet_field Settings.FIELDS.B1G_IMPORT_ID, label: "Import ID", show: false
146
+
144
147
  # Resouce Class
145
148
  config.add_facet_field Settings.FIELDS.RESOURCE_CLASS, show: false
146
149
 
@@ -14,8 +14,8 @@ module Admin
14
14
  # GET /imports/1
15
15
  # GET /imports/1.json
16
16
  def show
17
- @pagy_failed, @import_failed_documents = pagy(@import.import_documents.not_in_state(:success), items: 100)
18
- @pagy_success, @import_success_documents = pagy(@import.import_documents.in_state(:success), items: 100)
17
+ @pagy_failed, @import_failed_documents = pagy(@import.import_documents.not_in_state(:success), items: 50)
18
+ @pagy_success, @import_success_documents = pagy(@import.import_documents.in_state(:success), items: 50)
19
19
  end
20
20
 
21
21
  # GET /imports/new
@@ -115,4 +115,34 @@ module GeoblacklightAdminHelper
115
115
  ""
116
116
  end
117
117
  end
118
+
119
+ def link_to_admin_import(import)
120
+ path = admin_documents_path(
121
+ {
122
+ f: {b1g_geom_import_id_ssi: [import]}
123
+ }
124
+ )
125
+
126
+ link_to import.name, path
127
+ end
128
+
129
+ def link_to_gbl_import(label, import, state = false)
130
+ path = if state
131
+ blacklight_path(
132
+ {
133
+ f: {b1g_geom_import_id_ssi: [import]},
134
+ publication_state: state
135
+ }
136
+ )
137
+ else
138
+ blacklight_path(
139
+ {
140
+ f: {b1g_geom_import_id_ssi: [import]},
141
+ publication_state: "*"
142
+ }
143
+ )
144
+ end
145
+
146
+ link_to(label, path)
147
+ end
118
148
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ # BulkActionCollectDocuments class
4
+ class BulkActionCollectDocuments < ApplicationJob
5
+ queue_as :priority
6
+
7
+ def perform(bulk_action_id)
8
+ bulk_action = BulkAction.find(bulk_action_id)
9
+ bulk_action.collect_documents
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GeoblacklightAdmin
4
+ class StoreImageJob < ApplicationJob
5
+ queue_as :default
6
+
7
+ def perform(solr_document_id)
8
+ document = Document.find_by_friendlier_id(solr_document_id)
9
+ GeoblacklightAdmin::ImageService.new(document).store
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,13 @@
1
+ class Asset < Kithe::Asset
2
+ include AttrJson::Record::QueryScopes
3
+
4
+ set_shrine_uploader(AssetUploader)
5
+
6
+ # AttrJSON
7
+ attr_json :thumbnail, :boolean, default: "false"
8
+ attr_json :derivative_storage_type, :string, default: "public"
9
+
10
+ DERIVATIVE_STORAGE_TYPE_LOCATIONS = {
11
+ "public" => :kithe_derivatives
12
+ }.freeze
13
+ end
@@ -6,7 +6,9 @@ require "cgi"
6
6
  # BulkAction
7
7
  class BulkAction < ApplicationRecord
8
8
  # Callbacks
9
- after_create_commit :collect_documents
9
+ after_create do
10
+ BulkActionCollectDocuments.perform_later(id)
11
+ end
10
12
 
11
13
  # Associations
12
14
  has_many :documents, class_name: "BulkActionDocument", autosave: false, dependent: :destroy
@@ -38,9 +40,10 @@ class BulkAction < ApplicationRecord
38
40
  end
39
41
 
40
42
  def check_run_state
41
- return if state_machine.current_state == "complete"
43
+ nil if state_machine.current_state == "complete"
42
44
 
43
- state_machine.transition_to!(:complete) if documents.in_state(:queued).blank?
45
+ # @TODO / background job for collecting documents
46
+ # state_machine.transition_to!(:complete) if documents.in_state(:queued).blank?
44
47
  end
45
48
 
46
49
  def revert!
@@ -48,8 +51,6 @@ class BulkAction < ApplicationRecord
48
51
  BulkActionRevertJob.perform_later(self)
49
52
  end
50
53
 
51
- private
52
-
53
54
  def collect_documents
54
55
  cgi = CGI.unescape(scope)
55
56
  uri = URI.parse(cgi)
@@ -60,6 +61,8 @@ class BulkAction < ApplicationRecord
60
61
  end
61
62
  end
62
63
 
64
+ private
65
+
63
66
  def fetch_documents(uri)
64
67
  qargs = Rack::Utils.parse_nested_query(uri.query)
65
68
  fetch_documents = Document.where(friendlier_id: qargs["ids"])
@@ -0,0 +1,21 @@
1
+ module BulkActions
2
+ # Subclass for ChangePublicationState
3
+ class ChangePublicationState < BulkAction
4
+ # Add specific methods and validations for ChangePublicationState here
5
+ end
6
+
7
+ # Subclass for PublishDocument
8
+ class PublishDocument < BulkAction
9
+ # Add specific methods and validations for PublishDocument here
10
+ end
11
+
12
+ # Subclass for UnpublishDocument
13
+ class UnpublishDocument < BulkAction
14
+ # Add specific methods and validations for UnpublishDocument here
15
+ end
16
+
17
+ # Subclass for DraftDocument
18
+ class DraftDocument < BulkAction
19
+ # Add specific methods and validations for DraftDocument here
20
+ end
21
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GeoblacklightAdmin
4
+ module PublicationStateSearchBehavior
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ self.default_processor_chain += [:publication_state_records]
9
+ end
10
+
11
+ ##
12
+ # Show/Hide records by publication state in search
13
+ # Defaults to "published" items only
14
+ # publication_state: ['published', 'unpublished', 'draft']
15
+ # @param [Blacklight::Solr::Request]
16
+ # @return [Blacklight::Solr::Request]
17
+ def publication_state_records(solr_params)
18
+ solr_params[:fq] ||= []
19
+ solr_params[:fq] << if blacklight_params["publication_state"]
20
+ "b1g_publication_state_s:#{blacklight_params["publication_state"]}"
21
+ else
22
+ "b1g_publication_state_s:published"
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Date Validation
4
+ #
5
+ # ex. Bad date field type value
6
+ # "foo" is not a date
7
+ # "2023-12-5" is a date
8
+ class Document
9
+ # DateValidator
10
+ class DateValidator < ActiveModel::Validator
11
+ def validate(record)
12
+ # Assume true
13
+ valid_date = true
14
+
15
+ date_elements = Element.where(field_type: "date")
16
+
17
+ # Sane date values?
18
+ date_elements.each do |element|
19
+ unless record.send(element.solr_field).nil?
20
+ Rails.logger.debug("Date Validator")
21
+ Rails.logger.debug("Dates: #{record.send(element.solr_field).inspect}")
22
+ record.send(element.solr_field).each do |date|
23
+ Rails.logger.debug("\nDate: #{date}")
24
+ valid_date = proper_date(record, element, date, valid_date)
25
+
26
+ break if !valid_date
27
+ end
28
+ end
29
+ end
30
+
31
+ valid_date
32
+ end
33
+
34
+ def proper_date(record, element, date, valid_date)
35
+ if date.blank?
36
+ valid_date = true
37
+ elsif date.class != Date
38
+ valid_date = false
39
+ record.errors.add(GeoblacklightAdmin::Schema.instance.solr_fields[element.to_sym], "Bad date field type.")
40
+ end
41
+
42
+ Rails.logger.debug("#{date} - #{valid_date}")
43
+ valid_date
44
+ end
45
+ end
46
+ end
@@ -5,6 +5,13 @@ class Document < Kithe::Work
5
5
  include AttrJson::Record::QueryScopes
6
6
  include ActiveModel::Validations
7
7
 
8
+ delegate :viewer_protocol, to: :item_viewer
9
+ delegate :viewer_endpoint, to: :item_viewer
10
+
11
+ def item_viewer
12
+ GeoblacklightAdmin::ItemViewer.new(references)
13
+ end
14
+
8
15
  attr_accessor :skip_callbacks
9
16
 
10
17
  has_paper_trail ignore: [:publication_state]
@@ -50,6 +57,7 @@ class Document < Kithe::Work
50
57
  references_json.include?("downloadUrl")
51
58
  end
52
59
 
60
+ validates_with Document::DateValidator
53
61
  validates_with Document::DateRangeValidator
54
62
  validates_with Document::BboxValidator
55
63
  validates_with Document::GeomValidator
@@ -68,12 +76,15 @@ class Document < Kithe::Work
68
76
  attr_json :dct_references_s, Document::Reference.to_type, array: true, default: -> { [] }
69
77
 
70
78
  # Index Transformations - *_json functions
71
- def references_json
79
+ def references
72
80
  references = ActiveSupport::HashWithIndifferentAccess.new
73
81
  send(GeoblacklightAdmin::Schema.instance.solr_fields[:reference]).each do |ref|
74
82
  references[Document::Reference::REFERENCE_VALUES[ref.category.to_sym][:uri]] = ref.value
75
83
  end
76
- references = apply_downloads(references)
84
+ apply_downloads(references)
85
+ end
86
+
87
+ def references_json
77
88
  references.to_json
78
89
  end
79
90
 
@@ -121,8 +132,109 @@ class Document < Kithe::Work
121
132
  value = prefix + format.to_s
122
133
  value.html_safe
123
134
  end
135
+
136
+ ##
137
+ # GBL SolrDocument convience methods
138
+ #
139
+ def available?
140
+ public? || same_institution?
141
+ end
142
+
143
+ def public?
144
+ rights_field_data.present? && rights_field_data.casecmp("public").zero?
145
+ end
146
+
147
+ def local_restricted?
148
+ local? && restricted?
149
+ end
150
+
151
+ def local?
152
+ local = send(Settings.FIELDS.PROVIDER) || ""
153
+ local.casecmp(Settings.INSTITUTION_LOCAL_NAME)&.zero?
154
+ end
155
+
156
+ def restricted?
157
+ rights_field_data.blank? || rights_field_data.casecmp("restricted").zero?
158
+ end
159
+
160
+ def rights_field_data
161
+ send(Settings.FIELDS.ACCESS_RIGHTS) || ""
162
+ end
163
+
164
+ def downloadable?
165
+ (direct_download || download_types.present? || iiif_download) && available?
166
+ end
167
+
168
+ def direct_download
169
+ references.download.to_hash if references.download.present?
170
+ end
171
+
172
+ def display_note
173
+ send(Settings.FIELDS.DISPLAY_NOTE) || ""
174
+ end
175
+
176
+ def hgl_download
177
+ references.hgl.to_hash if references.hgl.present?
178
+ end
179
+
180
+ def oembed
181
+ references.oembed.endpoint if references.oembed.present?
182
+ end
183
+
184
+ def same_institution?
185
+ institution = send(Settings.FIELDS.PROVIDER) || ""
186
+ institution.casecmp(Settings.INSTITUTION.downcase).zero?
187
+ end
188
+
189
+ def iiif_download
190
+ references.iiif.to_hash if references.iiif.present?
191
+ end
192
+
193
+ def data_dictionary_download
194
+ references.data_dictionary.to_hash if references.data_dictionary.present?
195
+ end
196
+
197
+ def external_url
198
+ references.url&.endpoint
199
+ end
200
+
201
+ def itemtype
202
+ "http://schema.org/Dataset"
203
+ end
204
+
205
+ def geom_field
206
+ send(Settings.FIELDS.GEOMETRY) || ""
207
+ end
208
+
209
+ def geometry
210
+ # @TODO
211
+ # @geometry ||= Geoblacklight::Geometry.new(geom_field)
212
+ end
213
+
214
+ def wxs_identifier
215
+ send(Settings.FIELDS.WXS_IDENTIFIER) || ""
216
+ end
217
+
218
+ def file_format
219
+ send(Settings.FIELDS.FORMAT) || ""
220
+ end
221
+
222
+ ##
223
+ # Provides a convenience method to access a SolrDocument's References
224
+ # endpoint url without having to check and see if it is available
225
+ # :type => a string which if its a Geoblacklight::Constants::URI key
226
+ # will return a coresponding Geoblacklight::Reference
227
+ def checked_endpoint(type)
228
+ type = references.send(type)
229
+ type.endpoint if type.present?
230
+ end
231
+
124
232
  ### End / From GBL
125
233
 
234
+ def thumbnail
235
+ members.find { |m| m.respond_to?(:thumbnail) }
236
+ end
237
+
126
238
  def access_json
127
239
  access = {}
128
240
  access_urls.each { |au| access[au.institution_code] = au.access_url }
@@ -15,8 +15,9 @@ class DocumentAccess < ApplicationRecord
15
15
  ::CSV.foreach(file.path, headers: true) do |row|
16
16
  logger.debug("CSV Row: #{row.to_hash}")
17
17
  document_access = DocumentAccess.find_or_initialize_by(friendlier_id: row[0], institution_code: row[1])
18
- document_access.update(row.to_hash)
18
+ document_access.update!(row.to_hash)
19
19
  end
20
+ true
20
21
  end
21
22
 
22
23
  def self.destroy_all(file)
@@ -25,6 +26,7 @@ class DocumentAccess < ApplicationRecord
25
26
  logger.debug("CSV Row: #{row.to_hash}")
26
27
  DocumentAccess.destroy_by(id: row[0], friendlier_id: row[1])
27
28
  end
29
+ true
28
30
  end
29
31
 
30
32
  def to_csv
@@ -15,8 +15,9 @@ class DocumentDownload < ApplicationRecord
15
15
  ::CSV.foreach(file.path, headers: true) do |row|
16
16
  logger.debug("CSV Row: #{row.to_hash}")
17
17
  document_download = DocumentDownload.find_or_initialize_by(friendlier_id: row[0], label: row[1], value: row[2])
18
- document_download.update(row.to_hash)
18
+ document_download.update!(row.to_hash)
19
19
  end
20
+ true
20
21
  end
21
22
 
22
23
  def self.destroy_all(file)
@@ -25,6 +26,7 @@ class DocumentDownload < ApplicationRecord
25
26
  logger.debug("CSV Row: #{row.to_hash}")
26
27
  DocumentDownload.destroy_by(id: row[0], friendlier_id: row[1])
27
28
  end
29
+ true
28
30
  end
29
31
 
30
32
  def to_csv
@@ -28,6 +28,7 @@ class Element < ApplicationRecord
28
28
  text
29
29
  integer
30
30
  boolean
31
+ date
31
32
  datetime
32
33
  ].freeze
33
34
 
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GeoblacklightAdmin
4
+ class ImageService
5
+ module DynamicMapLayer
6
+ ##
7
+ # Formats and returns a thumbnail url from an ESRI Dynamic Map Layer endpoint.
8
+ # @param [SolrDocument]
9
+ # @return [String] image url
10
+ def self.image_url(document, _size)
11
+ "#{document.viewer_endpoint}/info/thumbnail/thumbnail.png"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GeoblacklightAdmin
4
+ class ImageService
5
+ module Iiif
6
+ ##
7
+ # Formats and returns a thumbnail url from an International Image
8
+ # Interoperability Framework endpoint.
9
+ # @param [SolrDocument]
10
+ # @param [Integer] thumbnail size
11
+ # @return [String] iiif thumbnail url
12
+ def self.image_url(document, size)
13
+ "#{document.viewer_endpoint.gsub("info.json", "")}full/#{size},/0/default.jpg"
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GeoblacklightAdmin
4
+ class ImageService
5
+ module ImageMapLayer
6
+ ##
7
+ # Formats and returns a thumbnail url from an ESRI Image Map Layer endpoint.
8
+ # information about the layer.
9
+ # @param [SolrDocument]
10
+ # @param [Integer] thumbnail size
11
+ # @return [String] thumbnail url
12
+ def self.image_url(document, _size)
13
+ "#{document.viewer_endpoint}/info/thumbnail/thumbnail.png"
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GeoblacklightAdmin
4
+ class ImageService
5
+ module TiledMapLayer
6
+ ##
7
+ # Formats and returns an image url from an ESRI Tiled Map Layer endpoint.
8
+ # @param [SolrDocument]
9
+ # @return [String] image url
10
+ def self.image_url(document, _size)
11
+ "#{document.viewer_endpoint}/info/thumbnail/thumbnail.png"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GeoblacklightAdmin
4
+ class ImageService
5
+ module Wms
6
+ ##
7
+ # Formats and returns a thumbnail url from a Web Map Service endpoint.
8
+ # This utilizes the GeoServer specific 'reflect' service to generate
9
+ # parameters like bbox that are difficult to tweak without more detailed
10
+ # information about the layer.
11
+ # @param [SolrDocument]
12
+ # @param [Integer] thumbnail size
13
+ # @return [String] wms thumbnail url
14
+ def self.image_url(document, size)
15
+ # Swap proxy url with princeton geoserver url.
16
+ # Thumbnail requests send geoserver auth.
17
+ endpoint = document.viewer_endpoint.gsub(Settings.PROXY_GEOSERVER_URL,
18
+ Settings.INSTITUTION_GEOSERVER_URL)
19
+ "#{endpoint}/reflect?" \
20
+ "&FORMAT=image%2Fpng" \
21
+ "&TRANSPARENT=TRUE" \
22
+ "&LAYERS=#{document["gbl_wxsIdentifier_s"]}" \
23
+ "&WIDTH=#{size}" \
24
+ "&HEIGHT=#{size}"
25
+ end
26
+ end
27
+ end
28
+ end