geoblacklight_admin 0.3.2 → 0.4.1

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