-
Notifications
You must be signed in to change notification settings - Fork 5
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-149 exhaust samples moved to destroyed location #2121
base: develop
Are you sure you want to change the base?
Changes from all commits
0ab2553
ecae3df
5d9c54c
2f76ac3
6aaa5a2
0092a8a
47737a0
2f7e594
1cd5b7c
6ffb245
f77f9e8
c96544e
9546b61
54d03a4
12ea25e
c207430
f9bcd24
c99b7cb
8a46b56
995d5cc
c550d0c
df19bef
9bc7b99
217a3eb
d55f4bd
a7b7160
b468bfc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,8 @@ VITE_TRACTION_BASE_URL=http://traction | |
VITE_TRACTION_GRAPHQL_URL=http://traction/graphql | ||
VITE_PRINTMYBARCODE_BASE_URL=http://printmybarcode | ||
VITE_SAMPLEEXTRACTION_BASE_URL=http://sampleextraction | ||
VITE_LABWHERE_BASE_URL=http://labwhere | ||
VITE_LABWHERE_BASE_URL=http://localhost:3003 | ||
VITE_SEQUENCESCAPE_API_KEY=development | ||
VITE_LOG=true | ||
VITE_ENVIRONMENT=development | ||
VITE_ENVIRONMENT=development | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are youn planning to add the destroyed location to the deployment project or just the .env files? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is already added in deployment project https://github.com/sanger/deployment/pull/533 |
||
VITE_DESTROYED_LOCATION_BARCODE=lw-destroyed-217 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,18 @@ | ||
import { extractLocationsForLabwares } from './helpers.js' | ||
import { FetchWrapper } from '@/api/FetchWrapper.js' | ||
import { | ||
exhaustLibrayVolume, | ||
formatAndTransformLibraries, | ||
} from '@/stores/utilities/pacbioLibraries.js' | ||
import { getPacbioLibraryResources } from '@/services/traction/PacbioLibrary.js' | ||
|
||
const labwhereFetch = FetchWrapper(import.meta.env['VITE_LABWHERE_BASE_URL'], 'LabWhere') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Advisory: Creating the variables like this rather than using dependency injection makes it more difficult to test which is why you need to mock fetch globally I think! A different way to do it would be to pass a type into the function which could have the various bits of configuration. |
||
const destroyLocation = import.meta.env['VITE_DESTROYED_LOCATION_BARCODE'] | ||
/** | ||
* Fetches the locations of labwares from LabWhere based on provided barcodes. | ||
* | ||
* @param {string[]} labwhereBarcodes - An array of labware barcodes to search for. | ||
* @param {Object} [fetchWrapper=labwhereFetch] - The fetch wrapper to use for the request (optional). | ||
* @returns {Promise<{success: boolean, errors: string[], data: Object}>} - A promise that resolves to an object containing the success status, any errors, and the data (locations). | ||
* | ||
* @example | ||
|
@@ -18,7 +25,7 @@ const labwhereFetch = FetchWrapper(import.meta.env['VITE_LABWHERE_BASE_URL'], 'L | |
* } | ||
* }); | ||
*/ | ||
const getLabwhereLocations = async (labwhereBarcodes) => { | ||
const getLabwhereLocations = async (labwhereBarcodes, fetchWrapper = labwhereFetch) => { | ||
// If no barcodes are provided, return a failed response. | ||
if (!labwhereBarcodes || labwhereBarcodes.length === 0) { | ||
return { success: false, errors: ['No barcodes provided'], data: {} } | ||
|
@@ -28,7 +35,7 @@ const getLabwhereLocations = async (labwhereBarcodes) => { | |
params.append('barcodes[]', barcode) | ||
}) | ||
|
||
const response = await labwhereFetch.post('/api/labwares/searches', params, 'multipart/form-data') | ||
const response = await fetchWrapper.post('/api/labwares/searches', params, 'multipart/form-data') | ||
|
||
if (response.success) { | ||
response.data = extractLocationsForLabwares(response.data, labwhereBarcodes) | ||
|
@@ -46,8 +53,9 @@ const getLabwhereLocations = async (labwhereBarcodes) => { | |
* | ||
* @param {string} userCode - The user code or swipecard. | ||
* @param {string} locationBarcode - The barcode of the location where labware will be stored. | ||
* @param {string} labwareBarcodes - The barcodes of the labware to be stored, separated by newlines. | ||
* @param {string} labwareBarcodes - The barcodes of the labware (library barcode or the plate / tube barcode for samples) to be stored, separated by newlines. | ||
* @param {number|null} [startPosition=null] - The starting position for storing the labware (optional). | ||
* @param {Object} [fetchWrapper=labwhereFetch] - The fetch wrapper to use for the request (optional). | ||
* @returns {Promise<{success: boolean, errors: string[]}>} - A promise that resolves to an object containing the success status, any errors, and the data. | ||
* | ||
* @example | ||
|
@@ -68,6 +76,7 @@ const scanBarcodesInLabwhereLocation = async ( | |
locationBarcode, | ||
labwareBarcodes, | ||
startPosition, | ||
fetchWrapper = labwhereFetch, | ||
) => { | ||
if (!userCode || !labwareBarcodes) { | ||
return { success: false, errors: ['Required parameters are missing for the Scan In operation'] } | ||
|
@@ -82,12 +91,61 @@ const scanBarcodesInLabwhereLocation = async ( | |
if (startPosition) { | ||
params['scan[start_position]'] = startPosition | ||
} | ||
const response = await labwhereFetch.post( | ||
const response = await fetchWrapper.post( | ||
'/api/scans', | ||
new URLSearchParams(params).toString(), | ||
'application/x-www-form-urlencoded', | ||
) | ||
return { success: response.success, errors: response.errors, message: response.data.message } | ||
} | ||
/** | ||
* Exhausts the volume of libraries if the location barcode matches the destroy location. | ||
* | ||
* @param {string} locationBarcode - The barcode of the location. | ||
* @param {Array<string>} labwareBarcodes - The barcodes of the labware. | ||
* @returns {Promise<Object>} - An object containing the success status and the exhausted libraries. | ||
*/ | ||
const exhaustLibraryVolumeIfDestroyed = async (locationBarcode, labwareBarcodes) => { | ||
if (locationBarcode !== destroyLocation) return { success: false } | ||
let librariesToDestroy = [] | ||
|
||
//Fetch libraries by filter key | ||
const fetchAndMergeLibraries = async (barcodes, filterKey) => { | ||
const filterOptions = { filter: { [filterKey]: barcodes.join(',') } } | ||
seenanair marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
const { success, libraries, tubes, tags, requests } = | ||
await getPacbioLibraryResources(filterOptions) | ||
if (success) { | ||
librariesToDestroy = [ | ||
...librariesToDestroy, | ||
...formatAndTransformLibraries(libraries, tubes, tags, requests), | ||
] | ||
} | ||
} | ||
|
||
export { getLabwhereLocations, scanBarcodesInLabwhereLocation } | ||
//Fetch libraries by source_identifier | ||
await fetchAndMergeLibraries(labwareBarcodes, 'source_identifier') | ||
const barcodesNotFetchedAsSourceIdentifer = labwareBarcodes.filter( | ||
(barcode) => !librariesToDestroy.some((library) => library.source_identifier === barcode), | ||
) | ||
// If not all libraries are found by source_identifier, try fetching by barcode | ||
if (barcodesNotFetchedAsSourceIdentifer.length > 0) { | ||
//Fetch libraries which are not found by barcode | ||
await fetchAndMergeLibraries(barcodesNotFetchedAsSourceIdentifer, 'barcode') | ||
} | ||
// If no libraries are found, return a failed response | ||
if (!librariesToDestroy.length) return { success: false } | ||
const exhaustedLibraries = [] | ||
|
||
// Exhaust the volume of libraries | ||
await Promise.all( | ||
librariesToDestroy.map(async (library) => { | ||
const { success } = await exhaustLibrayVolume(library) | ||
if (success) { | ||
exhaustedLibraries.push(library) | ||
} | ||
}), | ||
) | ||
return { success: exhaustedLibraries.length > 0, exhaustedLibraries } | ||
} | ||
export { getLabwhereLocations, scanBarcodesInLabwhereLocation, exhaustLibraryVolumeIfDestroyed } |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
import { handleResponse } from '@/api/ResponseHelper.js' | ||
import { groupIncludedByResource, dataToObjectById } from '@/api/JsonApi.js' | ||
import useRootStore from '@/stores/index.js' | ||
|
||
/** | ||
* Fetches all libraries. | ||
* | ||
* @param {Object} getPacbioLibraryResources - The options to fetch libraries with. | ||
* The options include page, filter, and include. | ||
* e.g { page: { "size": "24", "number": "1"}, filter: { source_identifier: 'sample1' }, include: 'request,tag,tube' } | ||
*/ | ||
async function getPacbioLibraryResources(fetchOptions = {}) { | ||
const includes = new Set(fetchOptions.include ? fetchOptions.include.split(',') : []) | ||
const requiredIncludes = ['request', 'tag', 'tube'] | ||
requiredIncludes.forEach((item) => includes.add(item)) | ||
|
||
const fetchOptionsDefaultInclude = { | ||
...fetchOptions, | ||
include: Array.from(includes).join(','), | ||
} | ||
const request = useRootStore().api.traction.pacbio.libraries | ||
const response = await handleResponse(request.get(fetchOptionsDefaultInclude)) | ||
|
||
const { success, body: { data, included = [], meta = {} } = {}, errors = [] } = response | ||
let libraries = {}, | ||
tubes = {}, | ||
tags = {}, | ||
requests = {} | ||
if (success && data && data.length > 0) { | ||
const { | ||
tubes: included_tubes, | ||
tags: included_tags, | ||
requests: included_requests, | ||
} = groupIncludedByResource(included) | ||
libraries = dataToObjectById({ data, includeRelationships: true }) | ||
tubes = dataToObjectById({ data: included_tubes }) | ||
tags = dataToObjectById({ data: included_tags }) | ||
requests = dataToObjectById({ data: included_requests }) | ||
} | ||
return { success, data, errors, meta, libraries, tubes, tags, requests } | ||
} | ||
|
||
/** | ||
* | ||
* @param {Integer | String} id - id of the library | ||
* @param {Integer | String} pacbio_request_id - id of the pacbio request | ||
* @param {String} template_prep_kit_box_barcode - barcode of the template prep kit box | ||
* @param {Integer | String} tag_id - id of the tag | ||
* @param {Float} concentration - concentration of the library | ||
* @param {Float} volume - volume of the library | ||
* @param {Float} insert_size - insert size of the library | ||
* @returns {Object} - payload for creating a library | ||
* if it is an update id is added otherwise pacbio_request_id is added | ||
* | ||
*/ | ||
const buildLibraryResourcePayload = ({ | ||
id, | ||
pacbio_request_id, | ||
template_prep_kit_box_barcode, | ||
tag_id, | ||
concentration, | ||
volume, | ||
insert_size, | ||
}) => { | ||
const requiredAttributes = { | ||
template_prep_kit_box_barcode, | ||
tag_id, | ||
concentration, | ||
volume, | ||
insert_size, | ||
} | ||
|
||
const payload = { | ||
data: { | ||
type: 'libraries', | ||
attributes: { | ||
...requiredAttributes, | ||
primary_aliquot_attributes: { | ||
...requiredAttributes, | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
id ? (payload.data.id = id) : (payload.data.attributes.pacbio_request_id = pacbio_request_id) | ||
|
||
return payload | ||
} | ||
|
||
/** | ||
* Updates a library with the given fields and updates the store if successful. | ||
* | ||
* @param {Object} libraryFields - The fields of the library to update. | ||
* @returns {Promise<Object>} - An object containing the success status and any errors. | ||
*/ | ||
async function updatePacbioLibraryResource(libraryFields) { | ||
const request = useRootStore().api.traction.pacbio.libraries | ||
const promise = request.update(buildLibraryResourcePayload(libraryFields)) | ||
const { success, errors } = await handleResponse(promise) | ||
return { success, errors } | ||
} | ||
|
||
export { getPacbioLibraryResources, updatePacbioLibraryResource, buildLibraryResourcePayload } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is better to do this in .env.development.local
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is an e2e test to check for volume exhaustion when the entered location barcode matches the destroyed location barcode, as specified in the corresponding .env file. This test will fail during CI if the correct configuration is not set. Is there are any better approaches to handle this scenario?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah ok. That is a problem. How does it work for all of our other api calls. We don't set traction url to a real one? It sounds like the intercept is not being handled correctly maybe?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here, we are simply comparing the entered barcode with the value of the environment variable read within the method. I think this is different from other cases where we can intercept commands to mock network requests and responses, eliminating the need for the real ones. I tried various approaches, such as using Cypress config to define a value, but it didn't work
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand now. It is the barcode you are checking which is why it needs to be in .env