Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
92 changes: 52 additions & 40 deletions app/models/presenters/pipeline_info_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,20 @@ def initialize(labware)
#
# In some cases, an intersection of these three groups might be required to accurately determine
# the pipeline and pipeline group, in others the parent purpose might be used to determine the pipeline.
def pipeline_groups
pipeline_groups = [
pipeline_groups_by_purpose, # allows matching pipeline purposes with no active requests
pipeline_groups_by_parent_purposes, # any matching pipeline MIGHT match the labware's parent purposes
pipeline_groups_by_requests_and_libraries # any matching pipeline MIGHT match the active requests and library type
]

# Remove nil values and empty arrays from the pipeline_groups array
pipeline_groups.compact!
pipeline_groups.reject!(&:empty?)

# intersect the pipeline groups to find the common pipelines
pipeline_groups = pipeline_groups.reduce { |acc, group| acc & group }

# Return the remaining pipeline groups as a sorted array, or nil if none are found.
pipeline_groups.sort if pipeline_groups&.any?
def pipeline_groups # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity,Metrics/AbcSize
# Build arrays from different methods, removing nils and empties
all_groups = [groups_by_purpose, groups_by_parent, groups_by_filters].compact.reject(&:empty?)
purpose_filter_groups = [groups_by_purpose, groups_by_filters].compact.reject(&:empty?)

# Find intersection if possible
intersected_all_groups = all_groups.reduce(&:&)
intersected_purpose_filters = purpose_filter_groups.reduce(&:&)

# Prefer full intersection, then partial, else empty array
return intersected_all_groups.sort if intersected_all_groups&.any?
return intersected_purpose_filters.sort if intersected_purpose_filters&.any?

[]
end

# Returns the pipeline group name if there is only one pipeline group, otherwise nil.
Expand Down Expand Up @@ -92,25 +90,43 @@ def child_purposes

private

def cache_key
"pipeline_info_presenter/#{@labware.barcode}"
end

def join_up_to(max_listed, array, separator = ', ')
return array.join(separator) if array.size <= max_listed

"#{array[0..(max_listed - 1)].join(separator)}, ...(#{array.size - max_listed} more)"
end

def pipeline_groups_by_purpose
# Returns the pipeline group names associated with the labware's purpose from the pipeline configuration.
# Some purposes are associated with multiple pipelines.
# Allows matching of pipeline purposes with no active requests.
#
# @return [Array<String>] Array of unique pipeline group names.
def groups_by_purpose
Settings
.pipelines
.select_pipelines_with_purpose(Settings.pipelines.list, @labware.purpose)
.map(&:pipeline_group)
.uniq
end

def pipeline_groups_by_requests_and_libraries
# Returns the pipeline group names associated with the purposes of the labware's parents.
# Any matching pipeline MIGHT match the labware's parent purposes.
# This method is most useful for cases where the parent labware purpose is more specific to the pipeline group,
# such as with 'LB Lib Pool Norm' tubes. It finds all parent labware, extracts their purposes,
# and determines the intersection of pipeline groups associated with those purposes.
# This approach could cause problems if the parent labware is in a different pipeline,
# however, in this case it is normally in the same pipeline *group*.
#
# @return [Array<String>] Array of unique pipeline group names.
def groups_by_parent
find_all_labware_parents_with_purposes
.map(&:purpose)
.map do |parent_purpose|
Settings.pipelines.select_pipelines_with_purpose(Settings.pipelines.list, parent_purpose).map(&:pipeline_group)
end
.reduce(:&)&.compact
end

# Returns the pipeline group name determined by the active pipelines for the labware,
# based on its requests and library type.
# Any matching pipeline MIGHT match the active requests and library type.
#
# @return [Array<String>] Array of unique pipeline group names.
def groups_by_filters
active_pipelines.map(&:pipeline_group).uniq
end

Expand All @@ -130,17 +146,13 @@ def find_all_labware_parents_with_purposes_uncached(labwares)
)
end

# On `LB Lib Pool Norm` tubes, it's hard to find the correct pipeline,
# because the same purpose and request type is used in many pipelines.
# This looks at the parent labware, as this is normally specific to the pipeline.
# This approach could cause problems if the parent labware is in a different pipeline,
# however, in this case it is normally in the same pipeline *group*.
def pipeline_groups_by_parent_purposes
find_all_labware_parents_with_purposes
.map(&:purpose)
.map do |parent_purpose|
Settings.pipelines.select_pipelines_with_purpose(Settings.pipelines.list, parent_purpose).map(&:pipeline_group)
end
.reduce(:&)
def cache_key
"pipeline_info_presenter/#{@labware.barcode}"
end

def join_up_to(max_listed, array, separator = ', ')
return array.join(separator) if array.size <= max_listed

"#{array[0..(max_listed - 1)].join(separator)}, ...(#{array.size - max_listed} more)"
end
end
47 changes: 45 additions & 2 deletions spec/models/presenters/pipeline_info_presenter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ def stub_labware_find_all_barcode(labwares)
context 'when no pipelines match' do
let(:pipelines_config) { {} }

it 'returns nil' do
expect(presenter.pipeline_groups).to be_nil
it 'returns an empty array' do
expect(presenter.pipeline_groups).to eq([])
end
end

Expand Down Expand Up @@ -134,6 +134,49 @@ def stub_labware_find_all_barcode(labwares)
end
end

context 'when pipelines match by purpose and library type' do
# Based on Bioscan pipeline
let(:pipelines_config) do
{
'bioscan_lysate_prep' => {
filters: {
request_type_key: 'limber_bioscan_lysate_prep'
},
relationships: {
'LILYS-96 Stock' => 'LBSN-96 Lysate'
}
},
'bioscan_library_prep' => {
filters: {
request_type_key: 'limber_bioscan_library_prep',
library_type: 'Bioscan'
},
relationships: {
'LBSN-96 Lysate' => 'LBSN-384 PCR 1'
}
}
}
end
let(:purpose) { create(:purpose, name: 'LBSN-96 Lysate') }
let(:labware) { create(:plate, purpose:) }

it 'returns the pipeline group matching the purpose' do
expect(presenter.pipeline_groups).to eq(%w[bioscan_library_prep bioscan_lysate_prep])
end

context 'with added request filter' do
let(:request_type) { create(:request_type, key: 'limber_bioscan_library_prep') }
let(:request) { [create(:request, request_type: request_type, library_type: 'Bioscan', state: 'passed')] }
let(:aliquot) { [create(:aliquot, request:)] }
let(:wells) { [create(:well, aliquots: aliquot, location: 'A1')] }
let(:labware) { create(:plate, purpose:, wells:) }

it 'returns the pipeline group matching the purpose and library type' do
expect(presenter.pipeline_groups).to eq(['bioscan_library_prep'])
end
end
end

context 'when the scenario is a linear config' do
# simple_linear_config: a simple chain of 3 purposes in a row, belonging to the same pipeline group.
# All three should return the same pipeline group.
Expand Down
Loading