diff --git a/app/assets/javascripts/download_releases.js b/app/assets/javascripts/download_releases.js new file mode 100644 index 0000000..9d7726a --- /dev/null +++ b/app/assets/javascripts/download_releases.js @@ -0,0 +1,20 @@ +$(document).on("click", "#download_releases", function(event) { + event.preventDefault(); + + const releasable_ids = JSON.parse($("#selected_releases_form").attr('data-releasable-ids')); + const total_entries = $('#total_entries').val(); + + const input_ids = $('').attr({ type: 'hidden', name: 'release_ids', value: JSON.stringify(releasable_ids) }); + const search_query = $('').attr({ type: 'hidden', name: 'search_query', value: $("form input[type='search']").val() }); + const type_filter = $('').attr({ type: 'hidden', name: 'type_filter', value: $('#type_filter_value').val() }); + + const download_count = releasable_ids.length > 0 ? releasable_ids.length : total_entries; + + $(this).parent().append(input_ids); + $(this).parent().append(search_query); + $(this).parent().append(type_filter); + + if (confirm(`${download_count} release(s) will be downloaded. Is this correct?`)){ + Rails.fire($(this).parent()[0], 'submit'); + } +}); \ No newline at end of file diff --git a/app/controllers/contract_downloads_controller.rb b/app/controllers/contract_downloads_controller.rb index b8bade4..af242dc 100644 --- a/app/controllers/contract_downloads_controller.rb +++ b/app/controllers/contract_downloads_controller.rb @@ -7,28 +7,41 @@ class ContractDownloadsController < ApplicationController def create authorize policy_scope(Download).create - fetch_releases - - download = @project.downloads.create!(release_type: params[:release_type]) + + download = @project.downloads.create!(release_type: release_type) other_downloads_in_progress = @project.downloads.unfinished_desc_order.offset(1) if other_downloads_in_progress.any? - in_progress_downloads_details = render_to_string "_other_pending_downloads", locals: { downloads: other_downloads_in_progress, release_type: params[:release_type] }, :layout => false + in_progress_downloads_details = render_to_string "_other_pending_downloads", locals: { downloads: other_downloads_in_progress, release_type: release_type }, :layout => false ProjectsChannel.broadcast_download_generation_update(download, in_progress_downloads_details) else - ProjectsChannel.broadcast_download_generation_update(download, I18n.t("contract_downloads.download.pending", release_type: params[:release_type].titleize)) + ProjectsChannel.broadcast_download_generation_update(download, I18n.t("contract_downloads.download.pending", release_type: release_type.titleize)) end - GenerateContractsZipJob.perform_later(@project, download, params[:release_type], @releases.ids) + GenerateContractsZipJob.perform_later(@project, download, release_type, release_ids, search_query, type_filter) end private - def fetch_releases - @releases = policy_scope(@project.public_send(releases)) + def release_type + params[:release_type] end - def releases - params[:release_type].constantize.model_name.plural + def search_query + params[:search_query] + end + + def type_filter + params[:type_filter] + end + + def release_ids + JSON.parse(params[:release_ids]) + rescue StandardError + [] + end + + def release_name(release_type) + release_type.constantize.model_name.plural end end diff --git a/app/helpers/tags_helper.rb b/app/helpers/tags_helper.rb index 5da02e0..c67e47a 100644 --- a/app/helpers/tags_helper.rb +++ b/app/helpers/tags_helper.rb @@ -17,6 +17,7 @@ module TagsHelper disable_with: disabled_content, }, form: { + id: "selected_releases_form", data: { releasable_ids: [], }, diff --git a/app/jobs/generate_contracts_zip_job.rb b/app/jobs/generate_contracts_zip_job.rb index 65bc376..43c5857 100644 --- a/app/jobs/generate_contracts_zip_job.rb +++ b/app/jobs/generate_contracts_zip_job.rb @@ -7,13 +7,14 @@ class GenerateContractsZipJob < ApplicationJob @project = job.arguments.first @download = job.arguments.second @release_type = job.arguments.third - @folder_name = "#{@project.name.parameterize}_#{get_release_name(@release_type).gsub('_', '-')}" + @release_ids = job.arguments.fourth + @search_query = job.arguments.fifth + @type_filter = job.arguments[5] + @folder_name = "#{@project.name.parameterize}_#{release_name.gsub('_', '-')}" @download.update!(name: @folder_name, status: :pending) end - def perform(project, download, release_type, release_ids) - releases = project.public_send(get_release_name(release_type)).where(id: release_ids) - + def perform(project, download, release_type, release_ids, search_query, type_filter) ::ReleaseContractCollectionService.new(releases, @folder_name).build do |dir, files| zipfile_name = "#{dir}/#{@folder_name}.zip" Zip::File.open(zipfile_name, Zip::File::CREATE) do |zipfile| @@ -31,7 +32,7 @@ class GenerateContractsZipJob < ApplicationJob end rescue StandardError => e Rails.logger.error("Failed to generate download for project (##{project.id}) with release type #{release_type}\n" + e.message) - + @download.failure! ProjectsChannel.broadcast_download_generation_update(@download, I18n.t("contract_downloads.download.failure")) end @@ -61,7 +62,32 @@ class GenerateContractsZipJob < ApplicationJob end end - def get_release_name(release_type) - release_type.constantize.model_name.plural + def release_name + @release_type.constantize.model_name.plural + end + + def all_releases + @project.public_send(release_name) + end + + def releases + if @release_ids.any? + return all_releases.where(id: @release_ids) + end + + results = all_releases + if all_releases.respond_to?(:complete, :incomplete) + results = case @type_filter + when 'complete' + all_releases.complete + when 'incomplete' + all_releases.incomplete + else + all_releases + end + end + + results = results.search(@search_query) if @search_query.present? + results end end diff --git a/app/models/appearance_release.rb b/app/models/appearance_release.rb index c1888f6..5d913bd 100644 --- a/app/models/appearance_release.rb +++ b/app/models/appearance_release.rb @@ -164,6 +164,10 @@ class AppearanceRelease < ApplicationRecord "#{project.name.parameterize}_#{contract_template.release_type}_#{(signed_at || created_at).strftime('%Y.%m.%d')}_#{release_number}_#{filename_suffix.parameterize}" end + def complete? + person_photo.attached? && contract.attached? + end + private # Validates the quality of the person photo diff --git a/app/services/release_contract_collection_service.rb b/app/services/release_contract_collection_service.rb index 898b42b..1b6b4a0 100644 --- a/app/services/release_contract_collection_service.rb +++ b/app/services/release_contract_collection_service.rb @@ -24,7 +24,7 @@ class ReleaseContractCollectionService end files = Dir.entries("#{dir}/").select { |f| !File.directory? f } - raise StandardError.new "Contracts or Contract Templates not found." unless files.any? + # raise StandardError.new "Contracts or Contract Templates not found." unless files.any? yield(dir, files) } end diff --git a/app/views/appearance_releases/index.html.erb b/app/views/appearance_releases/index.html.erb index 65b60ff..92f5ca5 100644 --- a/app/views/appearance_releases/index.html.erb +++ b/app/views/appearance_releases/index.html.erb @@ -1,5 +1,6 @@
+ />
<% if policy(AppearanceRelease).new? %> @@ -16,7 +17,7 @@ <% end %> <% if @appearance_releases.any? && policy(AppearanceRelease).download_multiple? %> - <%= link_to "Download All", [@project, :contract_downloads, release_type: @appearance_releases.name], method: :post, remote: true, class: "btn btn-light border mr-2 mb-2", data: { disable_with: "Please wait..." } %> + <%= button_to "Download", [@project, :contract_downloads, release_type: @appearance_releases.name], id: "download_releases", method: :post, remote: true, class: "btn btn-light border mr-2 mb-2", data: { disable_with: "Please wait..." } %> <% end %>
diff --git a/app/views/appearance_releases/index.js.erb b/app/views/appearance_releases/index.js.erb index ebc1c7c..e76d092 100644 --- a/app/views/appearance_releases/index.js.erb +++ b/app/views/appearance_releases/index.js.erb @@ -3,3 +3,5 @@ $("form input[type='search']").val("<%= params[:query] %>"); $("#type_filter_actions").html("<%= j render 'type_filter_actions' %>"); $("#appearance_releases_pagination").html("<%= j will_paginate(@appearance_releases) %>"); $('#type_filter_value').val("<%= params[:type_filter] %>"); +$("#selected_releases_form").attr('data-releasable-ids', JSON.stringify([])); +$("#total_entries").val(<%= @appearance_releases.total_entries %>); \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 23b141d..562c785 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -1492,6 +1492,7 @@ CREATE TABLE public.settings ( -- CREATE SEQUENCE public.settings_id_seq + AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE @@ -1527,6 +1528,7 @@ CREATE TABLE public.taggings ( -- CREATE SEQUENCE public.taggings_id_seq + AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE @@ -1557,6 +1559,7 @@ CREATE TABLE public.tags ( -- CREATE SEQUENCE public.tags_id_seq + AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE diff --git a/spec/jobs/generate_contracts_zip_job_spec.rb b/spec/jobs/generate_contracts_zip_job_spec.rb index 68bf43e..48e9cfc 100644 --- a/spec/jobs/generate_contracts_zip_job_spec.rb +++ b/spec/jobs/generate_contracts_zip_job_spec.rb @@ -19,7 +19,7 @@ describe GenerateContractsZipJob do describe ".perform_later" do it "enqueues a background job for generating zip file" do expect { - GenerateContractsZipJob.perform_later(project, download, "AppearanceRelease", project.appearance_releases.ids) + GenerateContractsZipJob.perform_later(project, download, "AppearanceRelease", project.appearance_releases.ids, '', '') }.to have_enqueued_job end end @@ -28,7 +28,7 @@ describe GenerateContractsZipJob do shared_examples "generates ZIP containig CSV file with all releases data" do it "generates ZIP containing CSV file with all releases data for all release types" do lowercase_plural = subject.constantize.model_name.plural - GenerateContractsZipJob.perform_now(project, download, subject, project.public_send(lowercase_plural).ids) + GenerateContractsZipJob.perform_now(project, download, subject, project.public_send(lowercase_plural).ids, '', '') generated_zip = download.file.blob.download csv_file_name = "#{project.name.parameterize}_#{lowercase_plural.gsub('_', '-')}.csv" @@ -50,8 +50,111 @@ describe GenerateContractsZipJob do end end + shared_examples "generates ZIP containig CSV file with specific releases data" do + it "generates ZIP containing CSV file with all selected releases data for selected releases" do + lowercase_plural = subject.constantize.model_name.plural + all_releases = project.public_send(lowercase_plural) + included_releases = all_releases.where(id: all_releases.ids[0..1]) + not_included_releases = all_releases.where.not(id: all_releases.ids[0..1]) + GenerateContractsZipJob.perform_now(project, download, subject, included_releases.ids, '', '') + + generated_zip = download.file.blob.download + csv_file_name = "#{project.name.parameterize}_#{lowercase_plural.gsub('_', '-')}.csv" + Zip::InputStream.open(StringIO.new(generated_zip)) do |io| + while entry = io.get_next_entry + next unless entry.name == csv_file_name + + csv_file = entry.get_input_stream.read + + release_class = Object.const_get subject + release_headers = release_class.csv_headers + + release_headers.each do |header| + expect(csv_file).to match header + expect(csv_file).not_to match translation_missing + end + + included_releases.each do |release| + expect(csv_file).to match release.person_first_name + end + + not_included_releases.each do |release| + expect(csv_file).not_to match release.person_first_name + end + end + end + end + + it "generates ZIP containing CSV file with all filtered releases data for filtered releases" do + lowercase_plural = subject.constantize.model_name.plural + GenerateContractsZipJob.perform_now(project, download, subject, [], '', 'complete') + + complete_releases = project.public_send(lowercase_plural).complete + incomplete_releases = project.public_send(lowercase_plural).incomplete + + generated_zip = download.file.blob.download + csv_file_name = "#{project.name.parameterize}_#{lowercase_plural.gsub('_', '-')}.csv" + Zip::InputStream.open(StringIO.new(generated_zip)) do |io| + while entry = io.get_next_entry + next unless entry.name == csv_file_name + + csv_file = entry.get_input_stream.read + + release_class = Object.const_get subject + release_headers = release_class.csv_headers + + release_headers.each do |header| + expect(csv_file).to match header + expect(csv_file).not_to match translation_missing + end + + complete_releases.each do |release| + expect(csv_file).to match release.person_first_name + end + + incomplete_releases.each do |release| + expect(csv_file).not_to match release.person_first_name + end + end + end + end + + it "generates ZIP containing CSV file with all search query matching releases" do + lowercase_plural = subject.constantize.model_name.plural + matched_releases = project.public_send(lowercase_plural).search('Brad') + not_matched_releases = project.public_send(lowercase_plural).where.not(id: matched_releases.ids) + GenerateContractsZipJob.perform_now(project, download, subject, [], 'Brad', '') + + generated_zip = download.file.blob.download + csv_file_name = "#{project.name.parameterize}_#{lowercase_plural.gsub('_', '-')}.csv" + Zip::InputStream.open(StringIO.new(generated_zip)) do |io| + while entry = io.get_next_entry + next unless entry.name == csv_file_name + + csv_file = entry.get_input_stream.read + + release_class = Object.const_get subject + release_headers = release_class.csv_headers + + release_headers.each do |header| + expect(csv_file).to match header + expect(csv_file).not_to match translation_missing + end + + matched_releases.each do |release| + expect(csv_file).to match release.person_first_name + end + + not_matched_releases.each do |release| + expect(csv_file).not_to match release.person_first_name + end + end + end + end + end + it "updates a download record and creates attachment for it" do - GenerateContractsZipJob.perform_now(project, download, "AppearanceRelease", project.appearance_releases.ids) + GenerateContractsZipJob.perform_now(project, download, "AppearanceRelease", project.appearance_releases.ids, '', '') expect(download.project).to eq project expect(download.release_type).to eq "AppearanceRelease" @@ -69,9 +172,12 @@ describe GenerateContractsZipJob do context "generates ZIP for appearance releases" do let(:release) { create(:appearance_release_with_contract_template, :native, project: project, person_name: "John Doe") } + let(:incomplete_release) { create(:appearance_release_with_contract_template, project: project, person_name: "Jane Doe") } + let(:complete_release) { create(:appearance_release_with_contract_template, :non_native, project: project, person_name: "Brad Doe") } subject { 'AppearanceRelease' } it_behaves_like "generates ZIP containig CSV file with all releases data" + it_behaves_like "generates ZIP containig CSV file with specific releases data" end context "generates ZIP for location releases" do @@ -125,7 +231,7 @@ describe GenerateContractsZipJob do end it "updates status to 'failure' and sends user a notification" do - GenerateContractsZipJob.perform_now(project, download, "AppearanceRelease", project.appearance_releases.ids) + GenerateContractsZipJob.perform_now(project, download, "AppearanceRelease", project.appearance_releases.ids, '', '') expect(download.status).to eq "failure" expect(ProjectsChannel).to have_received(:broadcast_download_generation_update).with(download, I18n.t("contract_downloads.download.failure"))