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

Y24-190: Support v2 searches #4616

Open
wants to merge 32 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
d136efd
WIP amending submission resources to be creatable via API v2
sdjmchattie Dec 11, 2024
342d7ef
Implement remaining attributes and behaviour on submission_resource
sdjmchattie Dec 12, 2024
5dbddfb
Make TagLayoutProcessor use callbacks instead of overriding create_re…
sdjmchattie Dec 12, 2024
281502a
Add core tests for submissions
sdjmchattie Dec 12, 2024
56c042a
Finish the tests for submissions
sdjmchattie Dec 12, 2024
eb27efe
Address Rubocop concern
sdjmchattie Dec 12, 2024
c27f698
Add some documentation
StephenHulme Dec 13, 2024
e06ecce
Reduce the complexity of TransferProcessor
sdjmchattie Dec 16, 2024
40ab30b
Mark which methods are private in the TransferProcessor
sdjmchattie Dec 16, 2024
9430bc5
Move template associated attributes into a block in the TagLayoutReso…
sdjmchattie Dec 16, 2024
76f5251
Use the context to pass QcFile information to the default self.create…
sdjmchattie Dec 16, 2024
653d03b
Fix Rubocop issues
sdjmchattie Dec 16, 2024
eade045
Remove the unnecessary documentation of the self.create method
sdjmchattie Dec 16, 2024
24f6c08
Merge branch 'develop-Y24-190' into Y24-190-refactor-processors
sdjmchattie Dec 16, 2024
2e9c54f
Merge branch 'develop-Y24-190' into Y24-190-support-limber-v2-submiss…
sdjmchattie Dec 16, 2024
8348f31
Update documentation on SubmissionResource
sdjmchattie Dec 17, 2024
f995dd3
feat: add work_completions to api v2
StephenHulme Dec 17, 2024
c322520
tests: add work_completions tests
StephenHulme Dec 17, 2024
33ced44
Allow QcAbles to filter by UUID on API v2
sdjmchattie Dec 18, 2024
b64b85f
Create tests for QcableResource and add lots of documentation
sdjmchattie Dec 18, 2024
cf5814b
Fix labware relationships
sdjmchattie Dec 18, 2024
474eabb
Make all factory created Qcables have a Sanger Ean13 barcode
sdjmchattie Dec 18, 2024
bfccde3
Merge pull request #4569 from sanger/Y24-190-support-limber-v2-qcable
sdjmchattie Jan 8, 2025
70bbbef
Merge branch 'develop-Y24-190' into Y24-190-support-limber-v2-submiss…
sdjmchattie Jan 8, 2025
0b22fe1
Use a common shared example for simple filter tests
sdjmchattie Jan 8, 2025
36c3450
Merge pull request #4562 from sanger/Y24-190-refactor-processors
sdjmchattie Jan 8, 2025
5cb53a7
Merge pull request #4567 from sanger/y24-190-support-limber-v2-work-c…
sdjmchattie Jan 8, 2025
073c786
Merge pull request #4555 from sanger/Y24-190-support-limber-v2-submis…
sdjmchattie Jan 8, 2025
72c68af
Merge branch 'develop' into develop-Y24-190
sdjmchattie Jan 9, 2025
f1b2dde
Merge branch 'develop' into develop-Y24-190
sdjmchattie Jan 13, 2025
0d37651
Add a state filter for labware
sdjmchattie Jan 13, 2025
d86470e
Fix a typo in the state.rb file comments
sdjmchattie Jan 13, 2025
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
41 changes: 17 additions & 24 deletions app/controllers/api/v2/qc_files_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,32 @@ class QcFilesController < JSONAPI::ResourceController
end

class QcFileProcessor < JSONAPI::Processor
before_create_resource :prepare_temporary_file

private

def prepare_temporary_file
filename, contents = required_attributes
tempfile = create_temporary_file(filename, contents)

context.merge!(filename:, tempfile:)
end

# Validate that attributes contains both the filename and the contents of the QcFile.
def validate_required_attributes(attributes)
errors = []
def required_attributes
attributes = params[:data][:attributes]

filename = attributes[:filename]
errors += JSONAPI::Exceptions::ParameterMissing.new('filename').errors if filename.nil?
raise JSONAPI::Exceptions::ParameterMissing, 'filename' if filename.nil?

contents = attributes[:contents]
errors += JSONAPI::Exceptions::ParameterMissing.new('contents').errors if contents.nil?
raise JSONAPI::Exceptions::ParameterMissing, 'contents' if contents.nil?

[filename, contents, errors]
[filename, contents]
end

# Create a temporary file with the contents.
def create_tempfile(filename, contents)
def create_temporary_file(filename, contents)
# The filename for a Tempfile is passed as an array with the basename and extension.
# e.g. [ 'file', '.csv' ]
filename_components = [File.basename(filename, '.*'), File.extname(filename)]
Expand All @@ -33,24 +44,6 @@ def create_tempfile(filename, contents)
return file
end
end

# Override the default behaviour for a JSONAPI::Processor when creating a new resource.
# We need to parse the filename and contents attributes so that we can generate a temporary file for the new
# QcFile model object. Replacing the fields with these values after the new QcFile was created does not generate
# the file correctly in the database. The CarrierWave library needs these values sooner.
def create_resource
data = params[:data]
attributes = data[:attributes]
filename, contents, errors = validate_required_attributes(attributes)

return JSONAPI::ErrorsOperationResult.new(JSONAPI::BAD_REQUEST, errors) unless errors.empty?

tempfile = create_tempfile(filename, contents)
resource = QcFileResource.create_with_tempfile(context, tempfile, filename)
result = resource.replace_fields(data)

JSONAPI::ResourceOperationResult.new((result == :completed ? :created : :accepted), resource)
end
end
end
end
61 changes: 28 additions & 33 deletions app/controllers/api/v2/tag_layouts_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,49 +10,37 @@ class TagLayoutsController < JSONAPI::ResourceController
end

class TagLayoutProcessor < JSONAPI::Processor
# Override the default behaviour for a JSONAPI::Processor when creating a new resource.
around_create_resource :apply_template

private

# We need to check whether a template UUID was given, and, if so, copy its data into this
# new TagLayoutResource. The creation will be prevented if any data from the template is also
# included in the create request as additional values. e.g. a request has a template UUID and
# also specifies a direction or a tag_group. In this case, the error will indicate that the
# template UUID was not an allowed attribute.
def create_resource
errors = []
template = find_template { |new_errors| errors += new_errors }
merge_template_data(template) { |new_errors| errors += new_errors } unless template.nil?

return JSONAPI::ErrorsOperationResult.new(JSONAPI::BAD_REQUEST, errors) unless errors.empty?

# Perform the usual create actions.
resource = TagLayoutResource.create(context)
result = resource.replace_fields(params[:data])

record_template_use(template, resource)

JSONAPI::ResourceOperationResult.new((result == :completed ? :created : :accepted), resource)
def apply_template
template = merge_template_data
yield
record_template_use(template) unless template.nil?
end

private

def find_template
template_uuid = params[:data][:attributes][:tag_layout_template_uuid]
return nil if template_uuid.nil? # No errors -- we just don't have a template.

template = TagLayoutTemplate.with_uuid(template_uuid).first

if template.nil?
yield JSONAPI::Exceptions::InvalidFieldValue.new(:tag_layout_template_uuid, template_uuid).errors
end
raise JSONAPI::Exceptions::InvalidFieldValue.new(:tag_layout_template_uuid, template_uuid) if template.nil?

template
end

def errors_if_key_present(data, key)
return [] if data[key.to_sym].blank?
def raise_if_key_present(data, key)
return if data[key.to_sym].blank?

JSONAPI::Exceptions::BadRequest.new(
"Cannot provide '#{key}' while also providing 'tag_layout_template_uuid'."
).errors
raise JSONAPI::Exceptions::BadRequest,
"Cannot provide '#{key}' while also providing 'tag_layout_template_uuid'."
end

def merge_template_attributes(template)
Expand All @@ -61,7 +49,7 @@ def merge_template_attributes(template)
%i[walking_by direction].each do |attr_key|
next if template.send(attr_key).blank?

yield errors_if_key_present(data[:attributes], attr_key)
raise_if_key_present(data[:attributes], attr_key)

data[:attributes][attr_key] = template.send(attr_key)
end
Expand All @@ -73,16 +61,22 @@ def merge_template_to_one_relationships(template)
%i[tag_group tag2_group].each do |rel_key|
next if template.send(rel_key).blank?

yield errors_if_key_present(data[:attributes], "#{rel_key}_uuid")
yield errors_if_key_present(data[:to_one], rel_key)
raise_if_key_present(data[:attributes], "#{rel_key}_uuid")
raise_if_key_present(data[:to_one], rel_key)

data[:to_one][rel_key] = template.send(rel_key).id
end
end

def merge_template_data(template, &)
merge_template_attributes(template, &)
merge_template_to_one_relationships(template, &)
def merge_template_data
template = find_template

unless template.nil?
merge_template_attributes(template)
merge_template_to_one_relationships(template)
end

template
end

def enforce_uniqueness?
Expand All @@ -93,8 +87,9 @@ def enforce_uniqueness?
attributes.fetch(:enforce_uniqueness, tag2_group_present)
end

def record_template_use(template, resource)
template&.record_template_use(TagLayout.find(resource.id).plate, enforce_uniqueness?)
def record_template_use(template)
plate = Plate.with_uuid(params.dig(:data, :attributes, :plate_uuid)).first
template.record_template_use(plate, enforce_uniqueness?)
end
end
end
Expand Down
37 changes: 10 additions & 27 deletions app/controllers/api/v2/transfers_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,40 +10,23 @@ class TransfersController < JSONAPI::ResourceController
end

class TransferProcessor < JSONAPI::Processor
# Get the Transfer model type needed to be created from the transfer template in the attributes.
# Also put the transfers from the template into the attributes if they exist.
def extract_template_data(attributes)
errors = []
before_create_resource :extract_template_data

private

# Put the Transfer model_type needed to be created from the transfer template in the context.
# Also put the transfers from the template into the attributes if they exist.
def extract_template_data
attributes = params[:data][:attributes]
template_uuid = attributes[:transfer_template_uuid]
errors += JSONAPI::Exceptions::ParameterMissing.new('transfer_template_uuid').errors if template_uuid.nil?
raise JSONAPI::Exceptions::ParameterMissing, 'transfer_template_uuid' if template_uuid.nil?

template = TransferTemplate.with_uuid(template_uuid).first
errors +=
JSONAPI::Exceptions::InvalidFieldValue.new('transfer_template_uuid', template_uuid).errors if template.nil?

return nil, errors if errors.present?
raise JSONAPI::Exceptions::InvalidFieldValue.new('transfer_template_uuid', template_uuid) if template.nil?

# Modify attributes according to what we've found in the template.
attributes[:transfers] = template.transfers if template.transfers.present?

[template.transfer_class_name.constantize, errors]
end

# Override the default behaviour for a JSONAPI::Processor when creating a new resource.
# We need to parse for a transfer_template_uuid attribute so that it can be used to determine the polymorphic
# type of the Transfer represented by a template. The create method is then passed the type to create.
def create_resource
data = params[:data]
attributes = data[:attributes]
model_type, errors = extract_template_data(attributes)

return JSONAPI::ErrorsOperationResult.new(JSONAPI::BAD_REQUEST, errors) unless errors.empty?

resource = TransferResource.create_with_model(context, model_type)
result = resource.replace_fields(data)

JSONAPI::ResourceOperationResult.new((result == :completed ? :created : :accepted), resource)
context[:model_type] = template.transfer_class_name.constantize
end
end
end
Expand Down
12 changes: 12 additions & 0 deletions app/controllers/api/v2/work_completions_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# frozen_string_literal: true

module Api
module V2
# Provides a JSON API controller for work completion
# See: http://jsonapi-resources.com/ for JSONAPI::Resource documentation
class WorkCompletionsController < JSONAPI::ResourceController
# By default JSONAPI::ResourceController provides most the standard
# behaviour, and in many cases this file may be left empty.
end
end
end
2 changes: 1 addition & 1 deletion app/models/transfer/state.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def self.included(base) # rubocop:todo Metrics/MethodLength
states = Array(states).map(&:to_s)

# If all of the states are present there is no point in actually adding this set of conditions because
# we're basically looking for all of the plates.
# we're basically looking for all of the tubes.
if states.sort != ALL_STATES.sort
join_options = [
# rubocop:todo Layout/LineLength
Expand Down
56 changes: 20 additions & 36 deletions app/resources/api/v2/qc_file_resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,36 +49,10 @@ class QcFileResource < BaseResource
# @return [String] The content type, or MIME type, of the QC file.
attribute :content_type, readonly: true

# @!attribute [rw] contents
# @return [String] The String contents of the QC file.
# This is usually the CSV data for the QC file.
# This can only be written once on creation.
attribute :contents, write_once: true

def contents
# The contents comes from the uploaded_data managed by CarrierWave.
@model.current_data
end

def contents=(value)
# Do not update the model.
# File contents is set via the uploaded_data hash supplied during QcFile creation.
end

# @!attribute [r] created_at
# @return [DateTime] The date and time the QC file was created.
attribute :created_at, readonly: true

# @!attribute [rw] filename
# @return [String] The filename of the QC file.
# This can only be written once on creation.
attribute :filename, write_once: true

def filename=(value)
# Do not update the model.
# Filename is set via the uploaded_data hash supplied during QcFile creation.
end

# @!attribute [r] size
# @return [Integer] The size of the QC file in bytes.
attribute :size, readonly: true
Expand Down Expand Up @@ -109,16 +83,26 @@ def filename=(value)
# Create method
###

# @!method create_with_tempfile
# Create a new QcFile resource with the uploaded data from a temporary file. This is called by the controller
# when a create request for a QcFile is made. It ensures the contents of the file have been written to a
# new TempFile instance.
# @param context [Hash] The context for the request.
# @param tempfile [Tempfile] A temporary file containing the uploaded data.
# @param filename [String] The filename for the uploaded data.
# @return [QcFileResource] The new QcFile resource.
def self.create_with_tempfile(context, tempfile, filename)
opts = { uploaded_data: { tempfile:, filename: } }
# @!attribute [rw] contents
# @return [String] The String contents of the QC file.
# This is usually the CSV data for the QC file.
# This can only be written once on creation.
attribute :contents, write_once: true
attr_writer :contents # Do not store the value on the model. It is stored by the CarrierWave gem via a Tempfile.

def contents
# The contents comes from the uploaded_data managed by CarrierWave.
@model.current_data
end

# @!attribute [rw] filename
# @return [String] The filename of the QC file.
# This can only be written once on creation.
attribute :filename, write_once: true
attr_writer :filename # Do not store the value on the model. This value is consumed by the QcFileProcessor.

def self.create(context)
opts = { uploaded_data: context.slice(:filename, :tempfile) }
new(QcFile.new(opts), context)
end
end
Expand Down
Loading
Loading