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

DPL-954: colour code wells on arraying page #1580

Merged
merged 32 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
138df61
fix: adjust spacing between card sections
StephenHulme Jan 23, 2024
9317909
fix: add header to sample arraying page
StephenHulme Jan 23, 2024
ecf096a
fix: restore missing well ID
StephenHulme Jan 26, 2024
d3a1e74
feat: add tube colour to summary
StephenHulme Feb 12, 2024
eabd1cb
feat: add tube colour to scanning
StephenHulme Feb 13, 2024
2bd15f4
feat: add tube colour to plate
StephenHulme Feb 13, 2024
9ea2290
refactor: rename well colour -> tube colour
StephenHulme Feb 13, 2024
f6412dc
style: lint
StephenHulme Feb 13, 2024
d0043ba
tests: update to reflect colour by barcode
StephenHulme Feb 13, 2024
9179b19
style: add comment
StephenHulme Feb 13, 2024
e75f7e2
refactor: move findUniqueIndex to wellHelpers
StephenHulme Feb 13, 2024
5d77c56
tests: add wellHelpers tests
StephenHulme Feb 13, 2024
e8139ba
Merge branch 'develop' into dpl-954-colour-code-wells-on-arraying-page
StephenHulme Feb 16, 2024
697040d
refactor: rename pool_index -> colour_index
StephenHulme Feb 20, 2024
6591d9c
fix: add tooltips to wells
StephenHulme Feb 20, 2024
9023d86
feat: show human barcode on tooltip label
StephenHulme Feb 20, 2024
c3cd4e6
test: update tests to reflect pool_index changes
StephenHulme Feb 20, 2024
fa4078a
test: update tests to reflect sample arraying page header changes
StephenHulme Feb 20, 2024
055aafd
test: update tests to reflect tooltip changes
StephenHulme Feb 20, 2024
e174b10
test: address some test warnings
StephenHulme Feb 20, 2024
4799042
test: add test to reflect sample arraying page header changes
StephenHulme Feb 20, 2024
176560a
fix: clean-up tube validity conditions
StephenHulme Feb 20, 2024
9735757
test: add tests to show the effect of changing tubes and validity
StephenHulme Feb 20, 2024
593210e
tests: add tests for colours in TubeArraySummary
StephenHulme Feb 20, 2024
391f769
fix: include labware state in tube array summary behaviour
StephenHulme Feb 21, 2024
d5cbfa5
Merge branch 'develop' into dpl-954-colour-code-wells-on-arraying-page
StephenHulme Feb 21, 2024
5338e75
refactor: rename value -> summary
StephenHulme Feb 22, 2024
7faf96f
refactor: use different variable in targetWells to streamline access
StephenHulme Feb 23, 2024
b4746cc
style: add some docs to functions
StephenHulme Feb 23, 2024
89b49b8
refactor: increase code-style consistency
StephenHulme Feb 23, 2024
4ded3e7
docs: add more function doc-strings
StephenHulme Feb 23, 2024
826d23c
docs: add more doc-strings
StephenHulme Feb 23, 2024
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
114 changes: 104 additions & 10 deletions app/javascript/multi-stamp-tubes/components/MultiStampTubes.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ describe('MultiStampTubes', () => {
// triggering change events on unmounted components
return shallowMount(MultiStampTubes, {
propsData: {
parentPurposeName: 'parent',
purposeName: 'purpose',
purposeUuid: 'test',
targetRows: '8',
targetColumns: '12',
sourceTubes: '4',
purposeUuid: 'test',
requestsFilter: 'null',
targetUrl: 'example/example',
locationObj: mockLocation,
Expand All @@ -33,6 +35,12 @@ describe('MultiStampTubes', () => {
})
}

it('renders the header', () => {
const wrapper = wrapperFactory()

expect(wrapper.findComponent('b-card-stub').attributes('header')).toEqual('Sample Arraying: parent → purpose')
})

it('disables creation if there are no tubes', () => {
const wrapper = wrapperFactory()

Expand Down Expand Up @@ -242,22 +250,22 @@ describe('MultiStampTubes', () => {
])
})

it('passes on the layout', () => {
it('passes on the layout, assigning colour by machine barcode', () => {
const tube1 = {
state: 'valid',
labware: tubeFactory({ uuid: 'tube-uuid-1' }),
labware: tubeFactory({ uuid: 'tube-uuid-1', labware_barcode: { machine_barcode: 'barcode-1' } }),
}
const tube2 = {
state: 'valid',
labware: tubeFactory({ uuid: 'tube-uuid-2' }),
labware: tubeFactory({ uuid: 'tube-uuid-2', labware_barcode: { machine_barcode: 'barcode-2' } }),
}
const tube3 = {
state: 'valid',
labware: tubeFactory({ uuid: 'tube-uuid-3' }),
labware: tubeFactory({ uuid: 'tube-uuid-3', labware_barcode: { machine_barcode: 'barcode-2' } }),
}
const tube4 = {
state: 'valid',
labware: tubeFactory({ uuid: 'tube-uuid-4' }),
labware: tubeFactory({ uuid: 'tube-uuid-4', labware_barcode: { machine_barcode: 'barcode-4' } }),
}
const wrapper = wrapperFactory()
wrapper.vm.updateTube(1, tube1)
Expand All @@ -266,11 +274,97 @@ describe('MultiStampTubes', () => {
wrapper.vm.updateTube(4, tube4)

expect(wrapper.vm.targetWells).toEqual({
A1: { pool_index: 1 },
B1: { pool_index: 2 },
C1: { pool_index: 3 },
D1: { pool_index: 4 },
A1: { colour_index: 1 },
B1: { colour_index: 2 },
C1: { colour_index: 2 },
D1: { colour_index: 3 },
})
})

it('recalculates the colour index when tubes are updated', () => {
const noTube = { state: 'empty', labware: null }
const tube1 = {
state: 'valid',
labware: tubeFactory({ uuid: 'tube-uuid-1', labware_barcode: { machine_barcode: 'barcode-1' } }),
}
const tube2 = {
state: 'valid',
labware: tubeFactory({ uuid: 'tube-uuid-2', labware_barcode: { machine_barcode: 'barcode-2' } }),
}
const wrapper = wrapperFactory()

// add two different tubes
wrapper.vm.updateTube(1, tube1)
wrapper.vm.updateTube(2, tube2)
expect(wrapper.vm.targetWells).toEqual({
A1: { colour_index: 1 },
B1: { colour_index: 2 },
})

// remove the first tube
wrapper.vm.updateTube(1, noTube)
expect(wrapper.vm.targetWells).toEqual({
B1: { colour_index: 1 },
})

// add the second tube again, but in the first position
wrapper.vm.updateTube(1, tube2)
expect(wrapper.vm.targetWells).toEqual({
A1: { colour_index: 1 },
B1: { colour_index: 1 },
})
})

it('renders the tube colours based on tube state', () => {
const wrapper = wrapperFactory()

const emptyTube = {
state: 'empty',
labware: null,
}
const notFoundTube = {
state: 'invalid',
labware: undefined,
}
const unknownTube = {
state: 'valid',
labware: tubeFactory({ uuid: 't-unknown', labware_barcode: { machine_barcode: 'UNKNOWN' }, state: 'unknown' }),
}
const pendingTube = {
state: 'valid',
labware: tubeFactory({ uuid: 't-pending', labware_barcode: { machine_barcode: 'PENDING' }, state: 'pending' }),
}
const passedTube = {
state: 'valid',
labware: tubeFactory({ uuid: 't-passed', labware_barcode: { machine_barcode: 'PASSED' }, state: 'passed' }),
}

wrapper.vm.updateTube(1, emptyTube)
wrapper.vm.updateTube(2, notFoundTube)
wrapper.vm.updateTube(3, unknownTube)
wrapper.vm.updateTube(4, pendingTube)
wrapper.vm.updateTube(5, passedTube)

// shows only the (valid) tubes that are passed to the plate
expect(wrapper.vm.targetWells).toEqual({
C1: { colour_index: 1 },
D1: { colour_index: 2 },
E1: { colour_index: 3 },
})

// shows only the (valid) tubes that are passed to labware-scan
expect(wrapper.vm.tubes.length).toEqual(5)
let colourIndices = []
for (let i = 0; i < 5; i++) {
let result = wrapper.vm.colourIndex(i)
colourIndices.push(result)
}

expect(colourIndices).toEqual([-1, -1, 1, 2, 3])

// shows all the tubes (valid and invalid) that are passed to tube-array-summary
// no colour index is assigned to tubes at this stage and all the tubes are passed
// as-is to the tube-array-summary component where the colour index is assigned
})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondered if there were tests for the individual Vue components.

Also wondered what happens when they remove the last instance of a barcode from the scan boxes, does it free up that colour index to be used for the next new unique barcode?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It probably shouldn't right, if that one they delete was in the middle of the colour list you don't want complications with latter ones being re-coloured.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Vue component testing is a little shaky right now, but that aside for the moment, the colours are calculated from the order that the barcodes are entered at any one moment. Deleting the last barcode will allow that colour to be reused on the subsequent barcode. Deleting the barcode will cause all other codes to be shifted up by one. This is currently a technical limitation, but could be altered if we decide it's worth the extra effort.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added tests in 9735757 to show the behaviour of changing barcodes

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deleting the barcode will cause all other codes to be shifted up by one.

As in it shifts them up into new rack locations? I think that would be bad if that's what you mean, as they are probably physically placing the tubes in a rack as they scan them.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you mean just shifted in colour index then that's acceptable I think. The colours are just a visual aid to see where the replicates are.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah sorry, yes that comment was unclear...
Removing a barcode will cause all assigned colours to shift up by 1, the second explanation is correct.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed that functionality too, the colours changing when you remove a barcode. I thought it was ok.


it('does not display an alert if no invalid transfers are present', () => {
Expand Down
48 changes: 37 additions & 11 deletions app/javascript/multi-stamp-tubes/components/MultiStampTubes.vue
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've had some trouble working out the data structure we're expecting for inputs and outputs of certain functions. Could we add docs, for example the following above the validTransfers function?

    //  validTransfers returns an array with the following structure:
    //
    //  [
    //    { tubeObj: { index: 0, tube: {...} }, targetWell: 'A1' },
    //    { tubeObj: { index: 1, tube: {...} }, targetWell: 'A2' },
    //    ...etc...
    //  ]
    //

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, started addressing in b4746cc, but more to come

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More added in 4ded3e7

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And in 826d23c

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely helps with understanding what is going on in the component!

Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@
<lb-page>
<lb-loading-modal v-if="loading" :message="progressMessage" />
<lb-main-content>
<b-card bg-variant="dark" text-variant="white">
<b-card bg-variant="dark" text-variant="white" :header="header" header-tag="h3">
<lb-plate
caption="Layout of the new plate"
:rows="targetRowsNumber"
:columns="targetColumnsNumber"
:wells="targetWells"
/>
</b-card>
<b-card bg-variant="dark" text-variant="white">
<hr />
<lb-tube-array-summary :tubes="tubes" />
</b-card>
</lb-main-content>
Expand All @@ -28,7 +27,7 @@
:includes="tubeIncludes"
:fields="tubeFields"
:validators="scanValidation"
:colour-index="i"
:colour-index="colourIndex(i - 1)"
:labware-type="'tube'"
:valid-message="''"
@change="updateTube(i, $event)"
Expand Down Expand Up @@ -60,7 +59,7 @@ import resources from 'shared/resources'
import { transferTubesCreator } from 'shared/transfersCreators'
import { transfersForTubes } from 'shared/transfersLayouts'
import { buildTubeObjs } from 'shared/tubeHelpers'
import { indexToName } from 'shared/wellHelpers'
import { findUniqueIndex, indexToName } from 'shared/wellHelpers'
import MultiStampTubesTransfers from './MultiStampTubesTransfers'
import TubeArraySummary from './TubeArraySummary'
import filterProps from './filterProps'
Expand Down Expand Up @@ -99,6 +98,12 @@ export default {
// Limber plate purpose UUID
purposeUuid: { type: String, required: true },

// Limber plate purpose name
purposeName: { type: String, required: true },

// Limber parent purpose name
parentPurposeName: { type: String, required: true },

// Limber target Asset URL for posting the transfers
targetUrl: { type: String, required: true },

Expand Down Expand Up @@ -161,6 +166,9 @@ export default {
}
},
computed: {
header() {
return `Sample Arraying: ${this.parentPurposeName} → ${this.purposeName}`
},
sourceTubeNumber() {
return Number.parseInt(this.sourceTubes)
},
Expand Down Expand Up @@ -204,13 +212,14 @@ export default {
// ...
// }
targetWells() {
const wells = {}
for (let i = 0; i < this.validTransfers.length; i++) {
wells[this.validTransfers[i].targetWell] = {
pool_index: this.validTransfers[i].tubeObj.index + 1,
return this.validTransfers.reduce((acc, transfer) => {
const tubeIndex = transfer.tubeObj.index
acc[transfer.targetWell] = {
colour_index: this.colourIndex(tubeIndex),
human_barcode: this.tubes[tubeIndex].labware.labware_barcode.human_barcode,
StephenHulme marked this conversation as resolved.
Show resolved Hide resolved
}
}
return wells
return acc
}, {})
StephenHulme marked this conversation as resolved.
Show resolved Hide resolved
},
tubeIncludes() {
return filterProps.tubeIncludes
Expand All @@ -236,6 +245,23 @@ export default {
wellIndexToName(index) {
return indexToName(index, this.targetRowsNumber)
},
colourIndex(tubeIndex) {
StephenHulme marked this conversation as resolved.
Show resolved Hide resolved
// determine the colour of the tube
let colour_index = -1

const tube = this.tubes[tubeIndex]
if (tube.state !== 'valid') return colour_index

const tube_machine_barcode = tube.labware.labware_barcode.machine_barcode
const tube_machine_barcodes = this.tubes
.filter((tube) => tube.state === 'valid')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TubeArraySummary checks against tube.labware.state === 'passed'. Without knowing the difference in the components I wanted to check that was correct.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I must admit, I'm a little fuzzy on which states mean what. As far as I can tell, tube.state refers to whether the scanned in tube exists and is "valid", and tube.labware.state refers to the processing of a certain purpose (can be pending or failed)

.map((tube) => tube.labware.labware_barcode.machine_barcode)

const barcode_index = findUniqueIndex(tube_machine_barcodes, tube_machine_barcode)
if (barcode_index !== -1) colour_index = barcode_index + 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Annoying that the css colours list doesn't start at 0.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.

I'm strongly considering refactoring the colours logic at some point soon.


return colour_index
},
updateTube(index, data) {
this.$set(this.tubes, index - 1, { ...data, index: index - 1 })
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,45 @@ describe('TubeArraySummary', () => {
}
}

const mixtureOfTubesWithDifferingStates = []
for (let i = 0; i < 96; i++) {
switch (true) {
case i < 6:
mixtureOfTubesWithDifferingStates.push({
index: i.toString(),
labware: {
labware_barcode: { human_barcode: 'PASSED', machine_barcode: '1000000000001' },
state: 'passed',
},
state: 'valid',
})
break
case i < 12:
mixtureOfTubesWithDifferingStates.push({
index: i.toString(),
labware: {
labware_barcode: { human_barcode: 'PENDING', machine_barcode: '1000000000002' },
state: 'pending',
},
state: 'valid',
})
break
case i < 18:
mixtureOfTubesWithDifferingStates.push({
index: i.toString(),
labware: {
labware_barcode: { human_barcode: 'UNKNOWN', machine_barcode: '1000000000003' },
state: 'unknown',
},
state: 'valid',
})
break
default:
mixtureOfTubesWithDifferingStates.push({ index: i.toString(), labware: null, state: 'empty' })
break
}
}

const wrapperTubeArraySummaryEmpty = function () {
return mount(TubeArraySummary, {
propsData: {
Expand All @@ -84,6 +123,16 @@ describe('TubeArraySummary', () => {
localVue,
})
}

const wrapperTubeArraySummaryWithDifferingStates = function () {
return mount(TubeArraySummary, {
propsData: {
tubes: mixtureOfTubesWithDifferingStates,
},
localVue,
})
}

it('renders the provided caption', () => {
const wrapper = wrapperTubeArraySummaryWithDuplicates()

Expand All @@ -93,6 +142,7 @@ describe('TubeArraySummary', () => {
it('renders the provided tubes summary headers', () => {
const wrapper = wrapperTubeArraySummaryWithDuplicates()

expect(wrapper.find('#header_tube_colour').text()).toEqual('Tube Colour')
expect(wrapper.find('#header_human_barcode').text()).toEqual('Human Barcode')
expect(wrapper.find('#header_machine_barcode').text()).toEqual('Machine Barcode')
expect(wrapper.find('#header_replicates').text()).toEqual('Replicates')
Expand Down Expand Up @@ -144,4 +194,32 @@ describe('TubeArraySummary', () => {
expect(wrapper.find('#row_machine_barcode_index_95').text()).toEqual('1000000000095')
expect(wrapper.find('#row_replicates_index_95').text()).toEqual('1')
})

it('renders tube summary rows based on labware state', () => {
const wrapper = wrapperTubeArraySummaryWithDifferingStates()

// row 1
expect(wrapper.find('#row_tube_colour_index_0').element.children[0].classList.contains('colour-1')).toBe(true)
expect(wrapper.find('#row_human_barcode_index_0').text()).toEqual('PASSED')
expect(wrapper.find('#row_machine_barcode_index_0').text()).toEqual('1000000000001')
expect(wrapper.find('#row_replicates_index_0').text()).toEqual('6')

// row 2
expect(wrapper.find('#row_tube_colour_index_1').element.children.length).toBe(0) // no colour should be rendered
expect(wrapper.find('#row_human_barcode_index_1').text()).toEqual('PENDING')
expect(wrapper.find('#row_machine_barcode_index_1').text()).toEqual('1000000000002')
expect(wrapper.find('#row_replicates_index_1').text()).toEqual('6')

// row 3
expect(wrapper.find('#row_tube_colour_index_2').element.children.length).toBe(0) // no colour should be rendered
expect(wrapper.find('#row_human_barcode_index_2').text()).toEqual('UNKNOWN')
expect(wrapper.find('#row_machine_barcode_index_2').text()).toEqual('1000000000003')
expect(wrapper.find('#row_replicates_index_2').text()).toEqual('6')

// row 4
expect(wrapper.find('#row_tube_colour_index_3').element.children.length).toBe(0) // empty
expect(wrapper.find('#row_human_barcode_index_3').text()).toEqual('Empty')
expect(wrapper.find('#row_machine_barcode_index_3').text()).toEqual('Empty')
expect(wrapper.find('#row_replicates_index_3').text()).toEqual('78')
})
})
Loading
Loading