Skip to content

Commit

Permalink
DEV-1241 SSD Proxy Reports from PT (#227)
Browse files Browse the repository at this point in the history
  • Loading branch information
moseshll authored Sep 30, 2024
1 parent 4f9e2c8 commit 1fc2f33
Show file tree
Hide file tree
Showing 26 changed files with 822 additions and 11 deletions.
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ gem "flag-icons-rails"
gem "rails-i18n", "~> 6.0.0"
gem "maxmind-geoip2"
gem "jira-ruby"
gem "ransack"
gem "kaminari"

gem "canister"
gem "ettin"
Expand Down
18 changes: 18 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,18 @@ GEM
thor (>= 0.14, < 2.0)
json (2.6.2)
jwt (2.5.0)
kaminari (1.2.2)
activesupport (>= 4.1.0)
kaminari-actionview (= 1.2.2)
kaminari-activerecord (= 1.2.2)
kaminari-core (= 1.2.2)
kaminari-actionview (1.2.2)
actionview
kaminari-core (= 1.2.2)
kaminari-activerecord (1.2.2)
activerecord
kaminari-core (= 1.2.2)
kaminari-core (1.2.2)
listen (3.0.8)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
Expand Down Expand Up @@ -280,6 +292,10 @@ GEM
thor (~> 1.0)
rainbow (3.1.1)
rake (13.1.0)
ransack (4.2.1)
activerecord (>= 6.1.5)
activesupport (>= 6.1.5)
i18n
rb-fsevent (0.11.2)
rb-inotify (0.10.1)
ffi (~> 1.0)
Expand Down Expand Up @@ -405,6 +421,7 @@ DEPENDENCIES
jbuilder (~> 2.5)
jira-ruby
jquery-rails
kaminari
keycard!
listen (>= 3.0.5, < 3.2)
loofah (~> 2.19)
Expand All @@ -421,6 +438,7 @@ DEPENDENCIES
rails (~> 6.1.7.7)
rails-controller-testing
rails-i18n (~> 6.0.0)
ransack
rubyzip (~> 2.0)
sassc-rails
selenium-webdriver
Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,17 +122,22 @@ Contacts
Contact Types
Logs
Registrations # for new users
SSD Proxy Reports
```

Most pages have the standard CRU(D) operations (not a lot of Deletes), Rails style.

## Design

There is very little branding, since it is not at all facing the public.
There has been some adjustments to improve color contrast.
There have been some adjustments to improve color contrast.
Otis uses `select2.org` JavaScript library to make searchable lists of items (users, institutions).
Also uses `Ckeditor` for rich text editing used in composing emails.

All index pages use Bootstrap Table (https://bootstrap-table.com) for data display. SSD Proxy Reports
has advanced search features server-side using Ransack (https://github.com/activerecord-hackery/ransack).
This approach is expected to be a model for updating the other index pages.

## Functionality

### Jira
Expand Down
142 changes: 142 additions & 0 deletions app/controllers/ht_ssd_proxy_reports_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# frozen_string_literal: true

class HTSSDProxyReportsController < ApplicationController
# This class is only responsible for an index page. There are no detail views or editing
# capabilities.
# Bootstrap Table gets all its data server-side, so most of the plumbing in this class
# is in support of `format=json` queries.

# Extensive use is made of Ransack (https://github.com/activerecord-hackery/ransack)
# which I chose because assembling LIKE queries in this controller appeared likely to
# become a rabbit hole.

# Pagination is provided by Kaminari. See the `results.page(...).per(...)` calls.

# This is what a JSON request looks like when it comes in from Bootstrap Table:
# ?format=json&pageSize=10&pageNumber=1&filter={"rights_code":"pdus"}&dateStart=2023-09-19&dateEnd=2024-09-19&sortName=inst_code&sortOrder=asc
# - dateStart and dateEnd are the two date ranges initially populated by @date_start and @date_end
# - sortName and sortOrder, if present, reflect the user's interaction with the column sort controls.
# - filter={...} reflects the filters selected or typed into the column filters in the table.

# The `filter` keys come from HTSSDProxyReportPresenter::ALL_FIELDS which combines relevant
# columns from the three associated database tables.

# Used by `#matchers` to translate `filter` keys into values that the `#ransack` method
# can apply to the Active Record query. Many of these (the text input ones)
# are of the form `*_i_cont` which is a case-insensitive contains equivalent to "LIKE '%value%'".
# Those selectable by a dropdown menu can use an equality (`_eq`) matcher.
RANSACK_MATCHERS = {
"author" => :ht_hathifile_author_i_cont,
"bib_num" => :ht_hathifile_bib_num_cont,
"content_provider_code" => :ht_hathifile_content_provider_code_eq,
"datetime" => :datetime_start,
"digitization_agent_code" => :ht_hathifile_digitization_agent_code,
"email" => :email_i_cont,
"htid" => :htid_i_cont,
"imprint" => :ht_hathifile_imprint_i_cont,
"inst_code" => :inst_code_eq,
"institution_name" => :ht_institution_name_i_cont,
"rights_code" => :ht_hathifile_rights_code_eq,
"rights_date_used" => :ht_hathifile_rights_date_used_eq,
"title" => :ht_hathifile_title_i_cont
}

# Translation table from params[:sortName] to a form Ransack can understand.
RANSACK_ORDER = {
"author" => :ht_hathifile_author,
"bib_num" => :ht_hathifile_bib_num,
"content_provider_code" => :ht_hathifile_content_provider_code,
"datetime" => :datetime,
"digitization_agent_code" => :ht_hathifile_digitization_agent_code,
"email" => :email,
"htid" => :htid,
"imprint" => :ht_hathifile_imprint,
"inst_code" => :inst_code,
"institution_name" => :ht_institution_name,
"rights_code" => :ht_hathifile_rights_code,
"rights_date_used" => :ht_hathifile_rights_date_used,
"title" => :ht_hathifile_title
}

def index
respond_to do |format|
format.html do
# Populate the date range fields with the latest datetime and
# then the start date a year earlier
@date_end = HTSSDProxyReport.maximum(:datetime).tap do |dt_end|
@date_start = (dt_end - 1.year).to_date.to_s
end.to_date.to_s
end
format.json do
render json: json_query
end
end
end

private

# @return [Hash] value to be returned to Bootstrap Table as JSON
def json_query
# Create a Ransack::Search with all of the filter fields translated into Ransack matchers.
search = HTSSDProxyReport.includes(:ht_hathifile, :ht_institution)
.ransack(matchers)
# Apply the sort field and order, or default if not provided.
# Ransack requires lower case sort direction.
sort_name = RANSACK_ORDER.fetch(params[:sortName], "datetime")
sort_order = params.fetch(:sortOrder, "asc")
search.sorts = "#{sort_name} #{sort_order.downcase}"
# Extract HTSSDProxyReport::ActiveRecord_Relation
result = search.result
# total is the number of results after user-selected filters e.g. {"rights_code":"pdus"}
# totalNotFiltered (see a few lines below) is the SELECT * for the whole shebang
total = result.count
# Paginate using Kaminari. index UI is always paginated.
# When exporting to Excel and the like, there is no pagination
# (hence performance issues on large data sets).
if params[:pageNumber] && params[:pageSize]
result = result.page(params[:pageNumber]).per(params[:pageSize])
end
# Translate each row of the result into JSON and stick it into struct with totals.
{
total: total,
totalNotFiltered: HTSSDProxyReport.count,
rows: result.map { |line| line_to_json line }
}
end

# Use presenter to translate HTSSDProxyReport into JSON hash.
# This is called for each object in the result.
def line_to_json(report)
report = presenter report
HTSSDProxyReportPresenter::ALL_FIELDS.to_h do |field|
[field, report.field_value(field)]
end
end

def presenter(report)
HTSSDProxyReportPresenter.new(report, controller: self, action: params[:action].to_sym)
end

# Filter param (if any) sent by Bootstrap Table translated into Hash.
# This will be subsequently be translated into a form Ransack can understand.
def filter
@filter ||= JSON.parse(params.fetch("filter", "{}"))
end

# Translate Bootstrap Table filter fields and date start/end fields
# into Ransack matchers
def matchers
return @matchers if @matchers

@matchers = filter.transform_keys do |key|
RANSACK_MATCHERS.fetch(key, key)
end
if params[:dateStart]
@matchers[:datetime_gteq] = params[:dateStart]
end
if params[:dateEnd]
@matchers[:datetime_lteq] = params[:dateEnd]
end
@matchers
end
end
2 changes: 1 addition & 1 deletion app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

module ApplicationHelper
def nav_menu
%w[users approval_requests institutions contacts contact_types logs registrations]
%w[users approval_requests institutions contacts contact_types logs registrations ssd_proxy_reports]
.select { |item| can?(:index, "ht_#{item}") }
.map { |item| {item: item, path: send("ht_#{item}_path")} }
end
Expand Down
28 changes: 28 additions & 0 deletions app/models/ht_hathifile.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# frozen_string_literal: true

# Used as a secondary source of information for SSD Proxy Reports
# Only fleshed out to the extent needed. There's a lot we could do here.
class HTHathifile < ApplicationRecord
self.table_name = "hathifiles.hf"
self.primary_key = "htid"
has_many :ht_ssd_proxy_report, foreign_key: :htid, primary_key: :htid

# SSD Proxy Reports uses Ransack gem to search by association
def self.ransackable_attributes(auth_object = nil)
%w[
author bib_num content_provider_code digitization_agent_code htid imprint
rights_code rights_date_used title
]
end

def self.ransackable_associations(auth_object = nil)
%w[ht_ssd_proxy_report]
end

private

# Ransack search matchers assume string values, so convert this integer
ransacker :bib_num do
Arel.sql("CONVERT(#{table_name}.bib_num, CHAR(9))")
end
end
5 changes: 5 additions & 0 deletions app/models/ht_institution.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ class HTInstitution < ApplicationRecord

before_save :set_defaults

# SSD Proxy Reports uses Ransack gem to search by association
def self.ransackable_attributes(auth_object = nil)
%w[inst_id name]
end

# https://stackoverflow.com/a/57485464
attribute :enabled, ActiveRecord::Type::Integer.new

Expand Down
33 changes: 33 additions & 0 deletions app/models/ht_ssd_proxy_report.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# frozen_string_literal: true

# Only used read-only in Otis for reporting
class HTSSDProxyReport < ApplicationRecord
self.table_name = "ht_web.reports_downloads_ssdproxy"
# default_scope { order(:datetime) }
has_one :ht_hathifile, foreign_key: :htid, primary_key: :htid
has_one :ht_institution, foreign_key: :inst_id, primary_key: :inst_code

def self.ransackable_attributes(auth_object = nil)
["datetime", "email", "htid", "id", "in_copyright", "inst_code", "is_partial", "sha", "yyyy", "yyyymm"]
end

def self.ransackable_associations(auth_object = nil)
["ht_hathifile", "ht_institution"]
end

def institution_name
institution&.name
end

def institution
ht_institution
end

def hf
ht_hathifile
end

ransacker :datetime do
Arel.sql("DATE(#{table_name}.datetime)")
end
end
Loading

0 comments on commit 1fc2f33

Please sign in to comment.