Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
3565a76
feat(labwhere): add new versions of location scanning functions
stevieing Apr 15, 2025
b325b89
feat(labwhere): add scanBarcodesInLabwhereLocationV2 function and cor…
dasunpubudumal Apr 15, 2025
88c6073
fix(labwhere): streamline request body construction in scanBarcodesIn…
dasunpubudumal Apr 15, 2025
879c25e
refactor(labwhere): remove unused getLabwhereLocationsV2 function
dasunpubudumal Apr 15, 2025
5575ead
feat(labwhere): add getLabwhereLocationsV2 function and corresponding…
borhara May 8, 2025
28cf457
Merge branch 'develop' of github.com:sanger/traction-ui into develop
stevieing May 12, 2025
848f1c5
Merge branch 'develop' of github.com:sanger/traction-ui into develop
stevieing May 12, 2025
86e374c
merged develop and fixed cliengt tests.
stevieing May 12, 2025
d3606f7
Fixing scanBarcodesInLabwhereLocationV2 tests.
dasunpubudumal May 19, 2025
e164fb5
Update tests to add feature flags.
dasunpubudumal May 19, 2025
f1841f1
Linting
dasunpubudumal May 19, 2025
adacb38
[skip ci] Linting
dasunpubudumal May 19, 2025
d775617
Fix fn order
dasunpubudumal May 19, 2025
6b7b5e7
Remove unnecessary console log for feature flag checks in LabWhereRec…
dasunpubudumal May 19, 2025
48d9330
[skip ci] Fix fn order
dasunpubudumal May 19, 2025
be0e5a1
Add feature flag support for location fetching and update tests
stevieing May 29, 2025
085945d
Refactor tests to include feature flag setup in beforeEach for consis…
stevieing May 29, 2025
f6f3a72
Add feature flag setup for rust_labwhere_service in beforeEach
stevieing May 29, 2025
04ef268
Add feature flag setup for rust_labwhere_service in Pacbio tests
stevieing May 29, 2025
495c72b
Refactor tests to move feature flag setup into beforeEach for Pacbio …
stevieing May 29, 2025
9d703cc
Remove debug logging from getLabwhereLocationsV2 function
stevieing May 29, 2025
0aff16c
Add feature flag setup for rust_labwhere_service in beforeEach for On…
stevieing May 29, 2025
942c174
Update scanBarcodesInLabwhereLocationV2 to stringify request payload
stevieing Jun 6, 2025
67be17f
Add VITE_LABWHERE_V2_BASE_URL
yoldas Jun 13, 2025
27f52c7
Stringfy the post payload for searches end point
yoldas Jun 13, 2025
0801dd8
Add VITE_LABWHERE_V2_BASE_URL to environment configuration files
stevieing Jun 13, 2025
83530da
Merge branch 'develop' into add-rust-labwhere-service
stevieing Aug 18, 2025
b5e709e
Merge remote-tracking branch 'origin/develop' into add-rust-labwhere-…
dasunpubudumal Aug 26, 2025
0b85ab3
Merge remote-tracking branch 'origin/develop' into add-rust-labwhere-…
dasunpubudumal Oct 13, 2025
2e2a356
Merge remote-tracking branch 'origin/develop' into rust-demo-presenta…
dasunpubudumal Nov 12, 2025
0b42143
try to fix tests
dasunpubudumal Nov 12, 2025
516585c
try to fix tests
dasunpubudumal Nov 12, 2025
4347401
try to fix tests
dasunpubudumal Nov 12, 2025
7751eed
try to fix tests
dasunpubudumal Nov 12, 2025
b771417
Merge remote-tracking branch 'origin/develop' into rust-demo-presenta…
dasunpubudumal Nov 28, 2025
cb794df
Merge remote-tracking branch 'origin/develop' into rust-demo-presenta…
dasunpubudumal Dec 2, 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
1 change: 1 addition & 0 deletions .env.development
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
VITE_TRACTION_BASE_URL=http://traction
VITE_PRINTMYBARCODE_BASE_URL=http://printmybarcode
VITE_LABWHERE_BASE_URL=http://localhost:3003
VITE_LABWHERE_V2_BASE_URL=http://localhost:3000
VITE_SEQUENCESCAPE_API_KEY=development
VITE_LOG=true
VITE_ENVIRONMENT=development
Expand Down
1 change: 1 addition & 0 deletions .env.production
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ VITE_TRACTION_BASE_URL=REPLACE_VITE_TRACTION_BASE_URL
VITE_PRINTMYBARCODE_BASE_URL=REPLACE_VITE_PRINTMYBARCODE_BASE_URL
VITE_SEQUENCESCAPE_BASE_URL=REPLACE_VITE_SEQUENCESCAPE_BASE_URL
VITE_LABWHERE_BASE_URL=REPLACE_VITE_LABWHERE_BASE_URL
VITE_LABWHERE_V2_BASE_URL=REPLACE_VITE_LABWHERE_V2_BASE_URL
VITE_SEQUENCESCAPE_API_KEY=REPLACE_VITE_SEQUENCESCAPE_API_KEY
VITE_ENVIRONMENT=REPLACE_VITE_ENVIRONMENT
VITE_DESTROYED_LOCATION_BARCODE=REPLACE_VITE_LABWHERE_DESTROY_LOCATION_BARCODE
1 change: 1 addition & 0 deletions .env.uat
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ VITE_TRACTION_BASE_URL=REPLACE_VITE_TRACTION_BASE_URL
VITE_PRINTMYBARCODE_BASE_URL=REPLACE_VITE_PRINTMYBARCODE_BASE_URL
VITE_SEQUENCESCAPE_BASE_URL=REPLACE_VITE_SEQUENCESCAPE_BASE_URL
VITE_LABWHERE_BASE_URL=REPLACE_VITE_LABWHERE_BASE_URL
VITE_LABWHERE_V2_BASE_URL=REPLACE_VITE_LABWHERE_V2_BASE_URL
VITE_SEQUENCESCAPE_API_KEY=REPLACE_VITE_SEQUENCESCAPE_API_KEY
VITE_ENVIRONMENT=REPLACE_VITE_ENVIRONMENT
VITE_DESTROYED_LOCATION_BARCODE=REPLACE_VITE_LABWHERE_DESTROY_LOCATION_BARCODE
15 changes: 15 additions & 0 deletions src/api/FeatureFlag.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* Checks if a feature flag is enabled.
* @param {string} featureFlag - The name of the feature flag to check.
* @returns {boolean} - A true or false status on whether the feature flag is enabled.
*/

const checkFeatureFlag = async (featureFlag) => {
const response = await fetch(`${import.meta.env.VITE_TRACTION_BASE_URL}/flipper/api/actors/User`)
const flags = await response.json()
return flags?.features?.[featureFlag]?.enabled == true
}

export { checkFeatureFlag }

export default checkFeatureFlag
9 changes: 7 additions & 2 deletions src/composables/useLocationFetcher.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { getLabwhereLocations } from '@/services/labwhere/client.js'
import { getLabwhereLocations, getLabwhereLocationsV2 } from '@/services/labwhere/client.js'
import { formatLocations } from '@/services/labwhere/helpers.js'
import { checkFeatureFlag } from '@/api/FeatureFlag.js'

/**
* A composable function to fetch and format location data for given barcodes.
Expand All @@ -23,12 +24,16 @@ export default function useLocationFetcher() {
name: '-', // Default name for missing barcodes
}))

const fn = (await checkFeatureFlag('rust_labwhere_service'))
? getLabwhereLocationsV2
: getLabwhereLocations

try {
const trimmedBarcodes = barcodes.map((barcode) =>
barcode.includes(':') ? barcode.split(':')[0] : barcode,
)

const extractedLocations = await getLabwhereLocations(trimmedBarcodes)
const extractedLocations = await fn(trimmedBarcodes)
locationData = formatLocations(extractedLocations)
} catch (error) {
console.error('Error fetching locations:', error)
Expand Down
97 changes: 96 additions & 1 deletion src/services/labwhere/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import { getPacbioLibraryResources } from '@/services/traction/PacbioLibrary.js'

const labwhereFetch = FetchWrapper(import.meta.env['VITE_LABWHERE_BASE_URL'], 'LabWhere')
const labwhereFetchV2 = FetchWrapper(import.meta.env['VITE_LABWHERE_V2_BASE_URL'], 'LabWhere')
const destroyLocation = import.meta.env['VITE_DESTROYED_LOCATION_BARCODE']
/**
* Fetches the locations of labwares from LabWhere based on provided barcodes.
Expand Down Expand Up @@ -146,4 +147,98 @@ const exhaustLibraryVolumeIfDestroyed = async (locationBarcode, labwareBarcodes)
)
return { success: exhaustedLibraries.length > 0, exhaustedLibraries }
}
export { getLabwhereLocations, scanBarcodesInLabwhereLocation, exhaustLibraryVolumeIfDestroyed }

/**
* Scans labware barcodes into a specified location in LabWhere (V2 API with Rust).
*
* @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 {Object} [fetchWrapper=labwhereFetchV2] - The fetch wrapper to use for the request (optional).
* @returns {Promise<{success: boolean, errors: string[], message: string}>} - A promise that resolves to an object containing:
* - `success` (boolean): Whether the operation was successful.
* - `errors` (string[]): An array of error messages, if any.
* - `message` (string): A message describing the result of the operation.
*
* @example
* const locationBarcode = 'location123';
* const labwareBarcodes = 'barcode1\nbarcode2';
* scanBarcodesInLabwhereLocationV2(locationBarcode, labwareBarcodes).then(response => {
* if (response.success) {
* console.log('Barcodes scanned successfully:', response.message);
* } else {
* console.error('Errors:', response.errors);
* }
* });
*/
const scanBarcodesInLabwhereLocationV2 = async (
userCode,
locationBarcode,
labwareBarcodes,
startPosition,
fetchWrapper = labwhereFetchV2,
) => {
if (!labwareBarcodes) {
return { success: false, errors: ['Required parameters are missing for the Scan In operation'] }
}
const response = await fetchWrapper.post(
'/scan',
JSON.stringify({
labware_barcodes: labwareBarcodes,
location_barcode: locationBarcode,
}),
)

return { success: response.success, errors: response.errors, message: response.data.message }
}

/**
* Fetches labware locations from Labwhere using the provided barcodes.
*
* @async
* @function getLabwhereLocationsV2
* @param {string[]} labwhereBarcodes - An array of labware barcodes to search for.
* @param {Object} [fetchWrapper=labwhereFetchv2] - An optional fetch wrapper for making HTTP requests.
* @param {Function} fetchWrapper.post - A function to perform POST requests.
* @returns {Promise<Object>} A promise that resolves to an object containing:
* - `success` {boolean}: Indicates whether the request was successful.
* - `errors` {string[]}: An array of error messages, if any.
* - `data` {Object}: The response data, including extracted locations for the provided barcodes.
*
* @throws {Error} Throws an error if the fetch request fails.
*
* @example
* const barcodes = ['ABC123', 'DEF456'];
* const result = await getLabwhereLocationsV2(barcodes);
* if (result.success) {
* console.log('Locations:', result.data);
* } else {
* console.error('Errors:', result.errors);
* }
*/
const getLabwhereLocationsV2 = async (labwhereBarcodes, fetchWrapper = labwhereFetchV2) => {
// If no barcodes are provided, return a failed response.
if (!labwhereBarcodes || labwhereBarcodes.length === 0) {
return { success: false, errors: ['No barcodes provided'], data: {} }
}

const response = await fetchWrapper.post(
'/searches',
JSON.stringify({
labware_barcodes: labwhereBarcodes.join('\n'),
}),
'application/json',
)

if (response.success) {
response.data = extractLocationsForLabwares(response.data, labwhereBarcodes)
}
return response
}

export {
getLabwhereLocations,
getLabwhereLocationsV2,
scanBarcodesInLabwhereLocation,
exhaustLibraryVolumeIfDestroyed,
scanBarcodesInLabwhereLocationV2,
}
8 changes: 7 additions & 1 deletion src/views/LabwhereReception.vue
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,10 @@ import { ref, reactive, computed } from 'vue'
import {
scanBarcodesInLabwhereLocation,
exhaustLibraryVolumeIfDestroyed,
scanBarcodesInLabwhereLocationV2,
} from '@/services/labwhere/client.js'
import useAlert from '@/composables/useAlert.js'
import { checkFeatureFlag } from '@/api/FeatureFlag.js'

const user_code = ref('') // User code or swipecard
const location_barcode = ref('') // Location barcode
Expand Down Expand Up @@ -166,8 +168,12 @@ const uniqueBarcodesArray = computed(() => {
*
*/
const scanBarcodesToLabwhere = async () => {
const fn = (await checkFeatureFlag('rust_labwhere_service'))
? scanBarcodesInLabwhereLocationV2
: scanBarcodesInLabwhereLocation

if (validateForm()) {
const response = await scanBarcodesInLabwhereLocation(
const response = await fn(
user_code.value,
location_barcode.value,
uniqueBarcodesArray.value.join('\n'),
Expand Down
5 changes: 5 additions & 0 deletions tests/e2e/specs/ont/ont_pools_view.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import OntPoolFactory from '../../../factories/OntPoolFactory.js'
import PrinterFactory from '../../../factories/PrinterFactory.js'

describe('Ont pools view', () => {
beforeEach(() => {
cy.withFlags({
rust_labwhere_service: { enabled: false },
})
})
it('Visits the ont pools url', () => {
cy.wrap(OntPoolFactory.all()).as('ontPoolFactory')
cy.get('@ontPoolFactory').then((ontPoolFactory) => {
Expand Down
3 changes: 3 additions & 0 deletions tests/e2e/specs/ont/ont_samples_view.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ describe('Ont samples view', () => {
body: ontRequestFactory.content,
})
})
cy.withFlags({
rust_labwhere_service: { enabled: false },
})
// Stub labwhere request
cy.get('@ontRequestFactory').then((ontRequestFactory) => {
const labwhereUrl = Cypress.env('VITE_LABWHERE_BASE_URL')
Expand Down
3 changes: 3 additions & 0 deletions tests/e2e/specs/pacbio/pacbio_libraries_view.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import PacbioLibraryFactory from '../../../factories/PacbioLibraryFactory.js'

describe('Pacbio Libraries view', () => {
beforeEach(() => {
cy.withFlags({
rust_labwhere_service: { enabled: false },
})
cy.wrap(PacbioTagSetFactory()).as('pacbioTagSetFactory')
cy.wrap(PrinterFactory()).as('printerFactory')
cy.wrap(PacbioLibraryFactory()).as('pacbioLibraryFactory')
Expand Down
3 changes: 3 additions & 0 deletions tests/e2e/specs/pacbio/pacbio_pool_edit.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import PacbioPlateFactory from '../../../factories/PacbioPlateFactory.js'

describe('Pacbio Pool Edit', () => {
beforeEach(() => {
cy.withFlags({
rust_labwhere_service: { enabled: false },
})
cy.wrap(PacbioTagSetFactory()).as('pacbioTagSetFactory')
cy.wrap(PrinterFactory()).as('printerFactory')

Expand Down
5 changes: 5 additions & 0 deletions tests/e2e/specs/pacbio/pacbio_pools_view.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import PrinterFactory from '../../../factories/PrinterFactory.js'
import PacbioPoolFactory from '../../../factories/PacbioPoolFactory.js'

describe('Pacbio Pools view', () => {
beforeEach(() => {
cy.withFlags({
rust_labwhere_service: { enabled: false },
})
})
it('Visits the pacbio pools url', () => {
cy.wrap(PacbioPoolFactory()).as('pacbioPoolFactory')
cy.get('@pacbioPoolFactory').then((pacbioPoolFactory) => {
Expand Down
3 changes: 3 additions & 0 deletions tests/e2e/specs/pacbio/pacbio_samples_view.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import LibraryTypeFactory from '../../../factories/LibraryTypeFactory.js'

describe('Pacbio samples view', () => {
beforeEach(() => {
cy.withFlags({
rust_labwhere_service: { enabled: false },
})
cy.wrap(PacbioTagSetFactory()).as('pacbioTagSetFactory')
cy.wrap(PrinterFactory()).as('printerFactory')
cy.wrap(LibraryTypeFactory()).as('libraryTypeFactory')
Expand Down
3 changes: 3 additions & 0 deletions tests/e2e/specs/visit_labwhere_reception_page.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ describe('Labware Reception page', () => {
const labwhereUrl = Cypress.env('VITE_LABWHERE_BASE_URL')

beforeEach(() => {
cy.withFlags({
rust_labwhere_service: { enabled: false },
})
cy.visit('#/labwhere-reception')
})

Expand Down
4 changes: 4 additions & 0 deletions tests/unit/composables/useLocationFetcher.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ vi.mock('@/services/labwhere/helpers.js', () => ({
formatLocations: vi.fn(),
}))

vi.mock('@/api/FeatureFlag.js', () => ({
checkFeatureFlag: vi.fn().mockReturnValue(false),
}))

describe('useLocationFetcher', () => {
const { fetchLocations } = useLocationFetcher()

Expand Down
78 changes: 77 additions & 1 deletion tests/unit/services/labwhere/client.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import {
getLabwhereLocations,
getLabwhereLocationsV2,
scanBarcodesInLabwhereLocation,
scanBarcodesInLabwhereLocationV2,
exhaustLibraryVolumeIfDestroyed,
} from '@/services/labwhere/client.js'
import * as pacbioLibraryUtilities from '@/stores/utilities/pacbioLibraries.js'
Expand Down Expand Up @@ -51,6 +53,44 @@ describe('getLabwhereLocations', () => {
})
})

describe('getLabwhereLocationsV2', () => {
it('should return an error if no barcodes are provided', async () => {
const result = await getLabwhereLocationsV2([], mockFetchWrapper)
expect(result).toEqual({ success: false, errors: ['No barcodes provided'], data: {} })
})

it('should not call extractLocationsForLabwares if post fails', async () => {
const response = {
success: false,
errors: ['Failed to access LabWhere: Network error'],
data: {},
}
mockFetchWrapper.post.mockResolvedValue(response)
const result = await getLabwhereLocationsV2(['barcode1'], mockFetchWrapper)
expect(result).toEqual(response)
})

it('should call extractLocationsForLabwares if post succeeds', async () => {
const data = [
{
barcode: 'barcode1',
location: 'location1',
},
{
barcode: 'barcode2',
location: 'location2',
},
]
const mockResponse = { success: true, errors: [], data }
mockFetchWrapper.post.mockResolvedValue(mockResponse)
const result = await getLabwhereLocationsV2(['barcode1', 'barcode2'], mockFetchWrapper)
expect(result).toEqual({
...mockResponse,
data: { barcode1: 'location1', barcode2: 'location2' },
})
})
})

describe('scanBarcodesInLabwhereLocation', () => {
it('should return an error if required parameters are missing', async () => {
const result = await scanBarcodesInLabwhereLocation('', '', '', null, mockFetchWrapper)
Expand Down Expand Up @@ -100,7 +140,43 @@ describe('scanBarcodesInLabwhereLocation', () => {
})
})

// TODO: remove mocks
describe('scanBarcodesInLabwhereLocationV2', () => {
it('should return an error if required parameters are missing', async () => {
const result = await scanBarcodesInLabwhereLocationV2('', '', '', null, mockFetchWrapper)
expect(result).toEqual({
success: false,
errors: ['Required parameters are missing for the Scan In operation'],
})
})

it('should return formatted result for post response', async () => {
mockFetchWrapper.post.mockResolvedValue({
success: true,
errors: [],
data: { message: 'Labware stored to location 1' },
})
const result = await scanBarcodesInLabwhereLocationV2(
'',
'location1',
'labware1',
null,
mockFetchWrapper,
)
expect(mockFetchWrapper.post).toHaveBeenCalledWith(
'/scan',
JSON.stringify({
labware_barcodes: 'labware1',
location_barcode: 'location1',
}),
)
expect(result).toEqual({
success: true,
errors: [],
message: 'Labware stored to location 1',
})
})
})

describe('exhaustLibraryVolumeIfDestroyed', () => {
const destroyLocation = import.meta.env['VITE_DESTROYED_LOCATION_BARCODE']
const labwareBarcodes = ['barcode1', 'barcode2', 'barcode3']
Expand Down
Loading