Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2830 persistent endpoints for downloads #2936

Open
wants to merge 15 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 34 additions & 4 deletions app/controllers/downloads_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@ class DownloadsController < ApplicationController
include DataControllerConfiguration::ProjectDataControllerConfiguration

before_action :set_download, only: [:show, :download_file, :destroy, :update, :file, :api_show, :edit]
before_action :set_download_api, only: [:api_file, :api_show]
before_action :set_download_api, only: [:api_file, :api_show, :api_destroy]

after_action -> { set_pagination_headers(:downloads) }, only: [:api_index], if: :json_request?

skip_forgery_protection only: [:api_build, :api_destroy]
Dismissed Show dismissed Hide dismissed

# GET /downloads
# GET /downloads.json
def index
respond_to do |format|
format.html do
@recent_objects = Download.recent_from_project_id(sessions_current_project_id).order(updated_at: :desc).limit(10)
@recent_objects = Download.unscoped.recent_from_project_id(sessions_current_project_id).order(updated_at: :desc).limit(10)
render '/shared/data/all/index'
end
format.json {
Expand Down Expand Up @@ -63,7 +65,8 @@ def update
# GET /downloads/list
# GET /downloads/list.json
def list
@downloads = Download.where(project_id: sessions_current_project_id).order(:id).page(params[:page]).per(params[:per])
# has a default scope
@downloads = Download.unscoped.where(project_id: sessions_current_project_id).order(:id).page(params[:page]).per(params[:per])
end

# GET /downloads/1/file
Expand All @@ -77,11 +80,13 @@ def file
end

def api_index
@downloads = Download.where(is_public: true, project_id: sessions_current_project_id)
# If default scope is removed return here
@downloads = Download.where(project_id: sessions_current_project_id)
.order('downloads.id').page(params[:page]).per(params[:per])
render '/downloads/api/v1/index'
end

# GET /api/v1/downloads/123/file.json
def api_file
if @download.ready?
@download.increment!(:times_downloaded)
Expand All @@ -95,9 +100,30 @@ def api_show
render '/downloads/api/v1/show'
end

# DELETE /api/v1/downloads/1.json
def api_destroy
if @download.destroy
render json: {id: @download.id_was, status: :destroyed}
else
render json: {}, status: :unprocessable_entity
end
end

# /api/v1/downloads/build?type=Download::DwcArchive::Complete
def api_build
@download = Download.create(api_build_params)
render '/downloads/api/v1/show'
end

def api_terminate
@download.terminate # TODO, add method, or change to :destroy
render '/downloads/api/v1/show'
end

private

def set_download
# Why .unscoped ?
@download = Download.unscoped.where(project_id: sessions_current_project_id).find(params[:id])
end

Expand All @@ -108,4 +134,8 @@ def set_download_api
def download_params
params.require(:download).permit(:is_public, :name, :expires )
end

def api_build_params
params.permit(:type, predicate_extensions: {})
end
end
9 changes: 8 additions & 1 deletion app/controllers/projects_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,14 @@ def set_project
end

def project_params
params.require(:project).permit(:name, :set_new_api_access_token, :clear_api_access_token, Project.key_value_preferences, Project.array_preferences, Project.hash_preferences)
params.require(:project).permit(
:name,
:set_new_api_access_token,
:clear_api_access_token,
Project.key_value_preferences,
Project.array_preferences,
Project.hash_preferences,
project_members_attributes: [:user_id, :destroy])
end

def go_to
Expand Down
10 changes: 10 additions & 0 deletions app/helpers/downloads_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,14 @@ def download_file_api_url(download)
nil
end
end

# @return [Hash]
# calculated attributes used in /download responses
def download_status(download)
return nil if download.nil? || !download.persisted?
return {
ready: download.ready?,
expired: download.expired?
}
end
end
69 changes: 37 additions & 32 deletions app/helpers/projects_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,111 +2,116 @@
# versus those that aren't
#
module ProjectsHelper

CLASSIFIER = {
nomenclature: [TaxonName, TaxonNameRelationship, TaxonNameClassification, TypeMaterial ],
digitization: [CollectionObject, CollectingEvent, Loan, LoanItem, TaxonDetermination ],
descriptive: [Otu, ControlledVocabularyTerm, Content, Observation, ObservationMatrix, Descriptor, Image, BiologicalAssociation, CharacterState, ObservationMatrixRow, ObservationMatrixColumn],
literature: [ProjectSource, Citation, CitationTopic, Documentation, Document ],
geospatial: [Georeference, AssertedDistribution],
}
}

CLASSIFIER_ANNOTATION = [Identifier, Note, Tag, AlternateValue, Attribution, Confidence, Depiction ]

def project_tag(project)
return nil if project.nil?
project.name
end

def projects_search_form
render('/projects/quick_search_form')
end

def project_link(project)
return nil if project.nil?
l = link_to(project.name, select_project_path(project))
project.id == sessions_current_project_id ?
content_tag(:mark, l) :
l
end

def projects_list(projects)
projects.collect { |p| content_tag(:li, project_link(p)) }.join.html_safe
end


def project_login_link(project)
return nil unless (!is_project_member_by_id?(sessions_current_user_id, sessions_current_project_id) && (sessions_current_project_id != project.id))
link_to('Login to ' + project.name, select_project_path(project), class: ['button-default'])
end

# Came from application_controller

def invalid_object(object)
!(!object.try(:project_id) || project_matches(object))
end

def project_matches(object)
object.try(:project_id) == sessions_current_project_id
end

def taxonworks_classification(project_cutoff: 1000)
result = {}
data = Hash.new(0)
data = Hash.new(0)

CLASSIFIER.keys.each do |k|
CLASSIFIER[k].each do |m|
data[k] += m.count
end
end

result[:taxonworks] = {
data: data,
data:,
total: data.values.sum
}
result[:projects] = {}

result[:projects] = {}

Project.all.each do |p|
d = project_classification(p)
next if d[:total] < project_cutoff

result[:projects].merge!({ p.name => d })
end

result
end

def project_classification(project)
classification = Hash.new(0)

CLASSIFIER.keys.each do |k|
CLASSIFIER[k].each do |m|
classification[k] += m.where(project_id: project.id).count
end
end

# Add annotations to their respective class
CLASSIFIER_ANNOTATION.each do |m|
field = m.column_names.select{|n| n =~ /_type/}.first
CLASSIFIER.keys.each do |k|
CLASSIFIER[k].each do |s|
classification[k] += m.where(project_id: project.id, field => s.name ).count
end
end
end
end

return {
data: classification,
total: classification.values.sum
}
end

def document_gb_per_year
gb_per_year( Document.group_by_year(:created_at, format: "%Y").sum(:document_file_file_size))
gb_per_year( Document.group_by_year(:created_at, format: '%Y').sum(:document_file_file_size))
end

def image_gb_per_year
gb_per_year( Image.group_by_year(:created_at, format: "%Y").sum(:image_file_file_size) )
gb_per_year( Image.group_by_year(:created_at, format: '%Y').sum(:image_file_file_size) )
end

def gb_per_year(sums)
min = sums.keys.sort.first
max = sums.keys.sort.last
max = sums.keys.sort.last

data = {}

Expand All @@ -131,12 +136,12 @@ def cumulative_gb_per_year(sums)
end

def document_cumulative_gb_per_year
cumulative_gb_per_year(Document.group_by_year(:created_at, format: "%Y").sum(:document_file_file_size))
cumulative_gb_per_year(Document.group_by_year(:created_at, format: '%Y').sum(:document_file_file_size))
end

def image_cumulative_gb_per_year
cumulative_gb_per_year(Image.group_by_year(:created_at, format: "%Y").sum(:image_file_file_size))
cumulative_gb_per_year(Image.group_by_year(:created_at, format: '%Y').sum(:image_file_file_size))
end


end
6 changes: 1 addition & 5 deletions app/helpers/workbench/sessions_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,7 @@ def is_superuser?
sessions_signed_in? && ( is_administrator? || is_project_administrator? )
end

def is_project_member?(user, project)
project.project_members.include?(user) # TODO - change to ID
end

def is_project_member_by_id(user_id, project_id)
def is_project_member_by_id?(user_id, project_id)
ProjectMember.where(user_id: user_id, project_id: project_id).any?
end

Expand Down
2 changes: 1 addition & 1 deletion app/jobs/dwca_create_download_job.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
class DwcaCreateDownloadJob < ApplicationJob
queue_as :dwca_export

# @param download [a Download instance]
# @param core_scope [String, ActiveRecord::Relation]
# String of SQL generated from the scope
# SQL must return a list of DwcOccurrence records
# take a download, and a list of scopes, and save the result to the download, that's all
# @return
# # TODO: handle extension scopes
def perform(download, core_scope: nil, extension_scopes: {biological_associations: nil}, predicate_extension_params: {})
begin
Expand Down
26 changes: 25 additions & 1 deletion app/models/download.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class Download < ApplicationRecord
include Housekeeping
include Shared::IsData

# TODO: consider removing
default_scope { where('expires >= ?', Time.now) }

after_save :save_file
Expand All @@ -55,6 +56,10 @@ class Download < ApplicationRecord
validates_presence_of :expires
validates_presence_of :type

def build_async(record_scope, predicate_extension_params: {})
::DwcaCreateDownloadJob.perform_later(self, core_scope: record_scope.to_sql, predicate_extension_params: predicate_extension_params)
end

# Gets the downloads storage path
def self.storage_path
STORAGE_PATH
Expand Down Expand Up @@ -95,24 +100,43 @@ def delete_file
FileUtils.rm_rf(path)
end

def api_buildable?
false
end

private

STORAGE_PATH = Rails.root.join(Rails.env.test? ? 'tmp' : '', "downloads#{ENV['TEST_ENV_NUMBER']}").freeze

def set_sha2
if @source_file_path
self.update_column(:sha2, Digest::SHA256.file(@source_file_path).to_s)
end
end

def dir_path
str = id.to_s.rjust(9, '0')
STORAGE_PATH.join(str[-str.length..-7], str[-6..-4], str[-3..-1])
end

# This is the only method to move a temporary file
# to its location on the file server.
#
# ActiveJob generating files trigger this method
# by .updating the filename attribute.
def save_file
FileUtils.mkdir_p(dir_path)
FileUtils.cp(@source_file_path, file_path) if @source_file_path
if @source_file_path
FileUtils.cp(@source_file_path, file_path)
set_sha2
end
end
end

require_dependency 'download/basic_nomenclature'
require_dependency 'download/bibtex'
require_dependency 'download/coldp'
require_dependency 'download/dwc_archive'
require_dependency 'download/dwc_archive/complete'
require_dependency 'download/sql_project_dump'
require_dependency 'download/text'
24 changes: 24 additions & 0 deletions app/models/download/dwc_archive/complete.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Only one per project. Includes the complete current contents of DwCOccurrences.
class Download::DwcArchive::Complete < Download::DwcArchive

# Can be built with/out data attributes
attr_accessor :predicate_extensions

# Default values
attribute :name, default: -> { "dwc-a_complete_#{DateTime.now}.zip" }
attribute :description, default: 'A Darwin Core archive of the complete TaxonWorks DwcOccurrence table'
attribute :filename, default: -> { "dwc-a_complete_#{DateTime.now}.zip" }
attribute :expires, default: -> { 1.month.from_now }
attribute :request, default: -> { '/api/v1/downloads/build?type=Download::DwcArchive::Complete' }
attribute :is_public, default: -> { 1 }

after_save :build, unless: :ready? # prevent infinite loop callbacks

validates_uniqueness_of :type, scope: [:project_id], message: 'Only one Download::DwcArchive::Complete is allowed. Destroy the old version first.'

def build
record_scope = ::DwcOccurrence.where(project_id: project_id)
build_async(record_scope)
end

end
Loading