diff --git a/app/controllers/appearance_release_imports_controller.rb b/app/controllers/appearance_release_imports_controller.rb index 40a0857..3dee19b 100644 --- a/app/controllers/appearance_release_imports_controller.rb +++ b/app/controllers/appearance_release_imports_controller.rb @@ -3,7 +3,6 @@ class AppearanceReleaseImportsController < ApplicationController include AppearanceReleaseContext include ProjectContext - include CreateReleasableJobs before_action :set_project, only: [:create] @@ -11,24 +10,16 @@ class AppearanceReleaseImportsController < ApplicationController def create authorize AppearanceRelease - @failed_files = [] attachments = appearance_release_params if attachments.nil? alert_message = t 'appearance_releases.create.no_attachments' + redirect_to [@project, :appearance_releases], alert: alert_message else - attachments.each do |attachment| - create_imported_appearance_release attachment - end + filtered_attachments = filter_attachments attachments + MatchAppearanceReleasesJob.perform_later(@project, filtered_attachments) + notice_message = t 'appearance_releases.create.matching_started' + redirect_to [@project, :appearance_releases], notice: notice_message end - - unless @failed_files.empty? - alert_message = t 'appearance_releases.create.failed_import' - alert_message += '
' - end - - redirect_to [@project, :appearance_releases], alert: alert_message end private @@ -45,45 +36,18 @@ class AppearanceReleaseImportsController < ApplicationController params.require(:attachments) end - def build_appearance_release(params = {}) - authorize appearance_releases.build(params) + def filter_attachments(attachments) + filtered_attachments = [] + attachments.each do |attachment| + blob = ActiveStorage::Blob.find_signed attachment + next if blob.nil? + + extension = blob.filename.extension + filtered_attachments << attachment if blob.image? || extension == 'pdf' + end end - def log_create_analytics - TrackAnalyticsJob.perform_later(Current.user, Current.account, :track_create_non_native_release, release_type: AppearanceRelease.to_s, user_agent: request.user_agent, user_ip: request.remote_ip) - end - - def create_imported_appearance_release(attachment) - blob = ActiveStorage::Blob.find_signed(attachment) - return if blob.nil? - - extension = blob.filename.extension_with_delimiter - unless AppearanceRelease.acceptable_import_file_extensions.include? extension - blob.purge - @failed_files << blob.filename - return - end - - random_contract_no = AppearanceRelease.random_contract_number.to_s - appearance_release_params = { - person_last_name: random_contract_no - } - - if blob.image? - appearance_release_params[:person_photo] = attachment - appearance_release_params[:person_first_name] = I18n.t('appearance_releases.shared.imported_appearance_release_headshot_name') - elsif extension == '.pdf' - appearance_release_params[:contract] = attachment - appearance_release_params[:person_first_name] = I18n.t('appearance_releases.shared.imported_appearance_release_contract_name') - end - - appearance_release = build_appearance_release(appearance_release_params) - - if appearance_release.save(context: :non_native) - log_create_analytics - after_create appearance_release - else - @failed_files << blob.filename - end + def acceptable_extensions + AppearanceRelease.acceptable_import_file_extensions end end diff --git a/app/jobs/match_appearance_releases_job.rb b/app/jobs/match_appearance_releases_job.rb new file mode 100644 index 0000000..b3fdd71 --- /dev/null +++ b/app/jobs/match_appearance_releases_job.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +class MatchAppearanceReleasesJob < ApplicationJob + queue_as :default + + def perform(project, attachments) + matching_request = MatchingRequest.create project: project, attachments: attachments + + payload = { request_id: matching_request.id, bucket: aws_bucket_name, files: attachments} + response = BrayniacAI::AppearanceReleaseMatching.match_attachments payload + + matches = response[:matches] || [] + errors = response[:errors] || [] + + handle_matches matches, project + handle_errors errors + end + + private + + def handle_matches(matches, project) + matches.each do |match| + contract = match[:contract] + headshot = match[:headshot] + identifier = match[:identifier] + + identified_release = identifier.blank? ? nil : AppearanceRelease.find_by(identifier: identifier) + if identified_release.nil? + create_release project, contract, headshot, identifier + else + update_release identified_release, contract, headshot + end + end + end + + def handle_errors(errors) + logger.error "== MATCHING ERRORS ==" unless errors.empty? + errors.each do |error| + logger.error "[#{error[:error_code]}] #{error[:file]}" + end + end + + def create_release(project, contract, headshot, identifier) + random_contract_no = AppearanceRelease.random_contract_number.to_s + is_incomplete = contract.nil? || headshot.nil? + params = { + project: project, + person_first_name: appearance_first_name(is_incomplete), + person_last_name: random_contract_no + } + + params[:person_photo] = headshot unless headshot.nil? + params[:contract] = contract unless contract.nil? + params[:identifier] = identifier unless blank? + + return if AppearanceRelease.create(params) + + logger.error "Failed to create AppearanceRelease with params : \r\n#{params}" + end + + def update_release(release, contract, headshot) + release.contract = contract unless contract.nil? + release.person_photo = headshot unless headshot.nil? + + release.save + end + + def appearance_first_name(incomplete) + if incomplete + I18n.t('appearance_releases.shared.incomplete_match') + else + I18n.t('appearance_releases.shared.matched_import') + end + end + + def aws_bucket_name + ENV.fetch 'AWS_BUCKET' + end +end diff --git a/app/models/concerns/attachable.rb b/app/models/concerns/attachable.rb new file mode 100644 index 0000000..7d4860a --- /dev/null +++ b/app/models/concerns/attachable.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module Attachable + extend ActiveSupport::Concern + + included do + has_many_attached :attachments + end +end diff --git a/app/models/matching_request.rb b/app/models/matching_request.rb new file mode 100644 index 0000000..c719d57 --- /dev/null +++ b/app/models/matching_request.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class MatchingRequest < ApplicationRecord + include Attachable + + belongs_to :project +end diff --git a/config/locales/en.yml b/config/locales/en.yml index a45e867..7073de2 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -112,6 +112,7 @@ en: no_photos: Needs Photo create: failed_import: Failed to create appearance release for files listed below + matching_started: Matching started no_attachments: Failed to import - no attachments edit: heading: Edit Appearance Release @@ -141,6 +142,8 @@ en: shared: imported_appearance_release_contract_name: Imported Contract imported_appearance_release_headshot_name: Imported Headshot + incomplete_match: Incomplete Match + matched_import: Complete Match type_filter_actions: all_releases: All Releases complete_releases: Complete Releases diff --git a/config/locales/es.yml b/config/locales/es.yml index 674a7f0..be353f4 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -25,6 +25,7 @@ es: appearance_releases: create: failed_import: Failed to create appearance release for files listed below (ES) + matching_started: Matching started (ES) no_attachments: Failed to import - no attachments (ES) form: photos: @@ -37,6 +38,8 @@ es: shared: imported_appearance_release_contract_name: Contrato Importado imported_appearance_release_headshot_name: Retrato Importado + incomplete_match: Incomplete Match (ES) + matched_import: Complete Match (ES) type_filter_actions: all_releases: All Releases (ES) complete_releases: Complete Releases (ES) diff --git a/db/migrate/20200430151828_create_matching_requests.rb b/db/migrate/20200430151828_create_matching_requests.rb new file mode 100644 index 0000000..cc4e4cb --- /dev/null +++ b/db/migrate/20200430151828_create_matching_requests.rb @@ -0,0 +1,9 @@ +class CreateMatchingRequests < ActiveRecord::Migration[6.0] + def change + create_table :matching_requests do |t| + t.belongs_to :project, foreign_key: true + + t.timestamps + end + end +end diff --git a/db/migrate/20200430190412_add_identifier_to_appearance_releases.rb b/db/migrate/20200430190412_add_identifier_to_appearance_releases.rb new file mode 100644 index 0000000..a1ff2fa --- /dev/null +++ b/db/migrate/20200430190412_add_identifier_to_appearance_releases.rb @@ -0,0 +1,5 @@ +class AddIdentifierToAppearanceReleases < ActiveRecord::Migration[6.0] + def change + add_column :appearance_releases, :identifier, :string, default: nil + end +end diff --git a/lib/brayniac_ai.rb b/lib/brayniac_ai.rb index ce9bc72..7e02af0 100644 --- a/lib/brayniac_ai.rb +++ b/lib/brayniac_ai.rb @@ -3,6 +3,7 @@ require_relative "./brayniac_ai/aws_request_signing" require_relative "./brayniac_ai/aws_signed_connection" require_relative "./brayniac_ai/base" +require_relative "./brayniac_ai/appearance_release_matching" require_relative "./brayniac_ai/audio_recognition" require_relative "./brayniac_ai/collection" require_relative "./brayniac_ai/document_analysis" diff --git a/lib/brayniac_ai/appearance_release_matching.rb b/lib/brayniac_ai/appearance_release_matching.rb new file mode 100644 index 0000000..4f89c69 --- /dev/null +++ b/lib/brayniac_ai/appearance_release_matching.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +module BrayniacAI + class AppearanceReleaseMatching + class << self + def match_attachments(request_data) + # TODO: Send request with request_data and receive real response + mock_response request_data + end + + private + + def mock_response(request_data) + attachments = request_data[:files] + pdfs = [] + images = [] + matches = [] + errors = [] + + # Use first file for the error entry + first_attachment = attachments.shift + unless first_attachment.nil? + errors << { file: first_attachment, error_code: 1 } + end + + attachments.each do |attachment| + blob = ActiveStorage::Blob.find_signed attachment + next if blob.nil? + + if blob.image? + images << attachment + else + pdfs << attachment + end + end + + # Create pairs of matches and single headshot/contract after pairs are exhausted + more_pdfs = pdfs.length > images.length + pairs = more_pdfs ? pdfs.zip(images) : images.zip(pdfs) + pairs.each do |pair_element1, pair_element2| + if more_pdfs + pdf = pair_element1 + image = pair_element2 + else + pdf = pair_element2 + image = pair_element1 + end + if pdf.nil? || image.nil? + matches << { headshot: image } if pdf.nil? + matches << { contract: pdf } if image.nil? + else + matches << { headshot: image, contract: pdf, identifier: '' } + end + end + + { + request_id: request_data[:request_id], + matches: matches, + errors: errors + } + end + end + end +end diff --git a/spec/features/user_managing_appearance_releases_spec.rb b/spec/features/user_managing_appearance_releases_spec.rb index 17e2ae1..728757e 100644 --- a/spec/features/user_managing_appearance_releases_spec.rb +++ b/spec/features/user_managing_appearance_releases_spec.rb @@ -148,6 +148,7 @@ feature 'User managing appearance releases' do end scenario 'importing a releases works when image is selected', js: true do + skip 'Will be changed according to new matching capability' visit project_appearance_releases_path(project) expect(page).to have_content submit_create_button @@ -160,6 +161,7 @@ feature 'User managing appearance releases' do end scenario 'importing a releases works when pdf is selected', js: true do + skip 'Will be changed according to new matching capability' visit project_appearance_releases_path(project) expect(page).to have_content submit_create_button @@ -172,6 +174,7 @@ feature 'User managing appearance releases' do end scenario 'importing a releases fails when file other than image or pdf is selected', js: true do + skip 'Will be changed according to new matching capability' visit project_appearance_releases_path(project) expect(page).to have_content submit_create_button