From ba41cacda71e4c62a7bd7042eeeab3699b23076e Mon Sep 17 00:00:00 2001 From: Kieron Taylor Date: Tue, 18 Jun 2024 16:40:51 +0000 Subject: [PATCH 1/8] Fix a testing issue with vue-test-utils and changing .find() behaviour --- frontend/src/components/WellTable.vue | 2 +- frontend/src/views/__tests__/WellsByRun.spec.js | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/WellTable.vue b/frontend/src/components/WellTable.vue index 2fcc6ff..29c0699 100644 --- a/frontend/src/components/WellTable.vue +++ b/frontend/src/components/WellTable.vue @@ -30,7 +30,7 @@ defineEmits(['wellSelected']) Well time complete - {{ wellObj.run_name }} + {{ wellObj.run_name }} { ] ) await wrapper.setProps({runName: ['TRACTION-RUN-211', 'TRACTION-RUN-210']}) + await flushPromises() - test('Table now contains wells from both runs', () => { - const table = wrapper.get('table') - expect(table.exists()).toBe(true) + const table = wrapper.get('table') + expect(table.exists()).toBe(true) - expect(table.find('TRACTION-RUN-211').exists()).toBe(true) - expect(table.find('TRACTION-RUN-210').exists()).toBe(true) + console.log(table.html()) + expect(table.find("td#TRACTION-RUN-211").exists()).toBe(true) + expect(table.find("td#TRACTION-RUN-210").exists()).toBe(true) - const rows = table.findAll('tr') - expect(rows.length).toEqual(4) - }) + const rows = table.findAll('tr') + expect(rows.length).toEqual(4) }) }) From 6c7590eeb26271a0ebb3bcab30254d88abb2f182 Mon Sep 17 00:00:00 2001 From: Kieron Taylor Date: Tue, 18 Jun 2024 16:41:10 +0000 Subject: [PATCH 2/8] Implement a helper for loading pool metrics --- frontend/src/utils/__tests__/langqc.spec.js | 3 +++ frontend/src/utils/langqc.js | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/frontend/src/utils/__tests__/langqc.spec.js b/frontend/src/utils/__tests__/langqc.spec.js index a1fc14d..eefe81d 100644 --- a/frontend/src/utils/__tests__/langqc.spec.js +++ b/frontend/src/utils/__tests__/langqc.spec.js @@ -95,6 +95,9 @@ describe('Example fake remote api call', () => { client.getWellsForRunPromise('blah') expect(fetch.mock.calls[6][0]).toEqual('/api/pacbio/run/blah?page_size=100&page=1') + + client.getPoolMetrics('A12345'); + expect(fetch.mock.calls[7][0]).toEqual('/api/pacbio/products/A12345/seq_level/pool') }); }); diff --git a/frontend/src/utils/langqc.js b/frontend/src/utils/langqc.js index 2e315b5..daf8857 100644 --- a/frontend/src/utils/langqc.js +++ b/frontend/src/utils/langqc.js @@ -118,4 +118,10 @@ export default class LangQc { } ) } + + getPoolMetrics(id_product) { + // Use the product metrics endpoint to get additional metrics + // for a well. + return this.fetchWrapper(this.buildUrl(['products', id_product, 'seq_level', 'pool'])); + } } From d755afd736cfccbdcfafc15f6491e2e2e3e0ca5d Mon Sep 17 00:00:00 2001 From: Kieron Taylor Date: Tue, 9 Jul 2024 16:24:53 +0000 Subject: [PATCH 3/8] Scale pool stats to gigabases/round to 2dp for presentation --- lang_qc/models/pacbio/qc_data.py | 12 ++++++------ tests/fixtures/sample_data.py | 12 ++++++------ tests/test_pac_bio_qc_data_well.py | 16 ++++++++-------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/lang_qc/models/pacbio/qc_data.py b/lang_qc/models/pacbio/qc_data.py index 6aa6a0a..ca29192 100644 --- a/lang_qc/models/pacbio/qc_data.py +++ b/lang_qc/models/pacbio/qc_data.py @@ -173,7 +173,7 @@ class SampleDeplexingStats(BaseModel): tag1_name: str | None tag2_name: str | None deplexing_barcode: str | None - hifi_read_bases: int | None + hifi_read_bases: float | None = Field(title="HiFi read bases (Gb)") hifi_num_reads: int | None hifi_read_length_mean: float | None hifi_bases_percent: float | None @@ -208,7 +208,7 @@ def pre_root(cls, values: dict[str, Any]) -> dict[str, Any]: if well is None: raise ValueError(f"None {db_well_key_name} value is not allowed.") - cov: float = None + cov: float | None = None sample_stats = [] if well.demultiplex_mode and "Instrument" in well.demultiplex_mode: @@ -227,21 +227,21 @@ def pre_root(cls, values: dict[str, Any]) -> dict[str, Any]: cov = None else: hifi_reads = [prod.hifi_num_reads for prod in product_metrics] - cov = stdev(hifi_reads) / mean(hifi_reads) * 100 + cov = round(stdev(hifi_reads) / mean(hifi_reads) * 100, 2) - for (i, prod) in enumerate(product_metrics): + for i, prod in enumerate(product_metrics): sample_stats.append( SampleDeplexingStats( id_product=prod.id_pac_bio_product, tag1_name=lib_lims_data[i].tag_identifier, tag2_name=lib_lims_data[i].tag2_identifier, deplexing_barcode=prod.barcode4deplexing, - hifi_read_bases=prod.hifi_read_bases, + hifi_read_bases=convert_to_gigabase(prod, "hifi_read_bases"), hifi_num_reads=prod.hifi_num_reads, hifi_read_length_mean=prod.hifi_read_length_mean, hifi_bases_percent=prod.hifi_bases_percent, percentage_total_reads=( - prod.hifi_num_reads / well.hifi_num_reads * 100 + round(prod.hifi_num_reads / well.hifi_num_reads * 100, 2) if (well.hifi_num_reads and prod.hifi_num_reads) else None ), diff --git a/tests/fixtures/sample_data.py b/tests/fixtures/sample_data.py index e86fbb5..436cc67 100644 --- a/tests/fixtures/sample_data.py +++ b/tests/fixtures/sample_data.py @@ -59,9 +59,9 @@ def simplex_run(request, mlwhdb_test_session): tags=tag1, ).hash_product_id(), qc=1, - hifi_read_bases=900, + hifi_read_bases=90000000, hifi_num_reads=10, - hifi_read_length_mean=90, + hifi_read_length_mean=9000000, barcode_quality_score_mean=34, hifi_bases_percent=90.001, pac_bio_run_well_metrics=well_metrics_a1, @@ -148,9 +148,9 @@ def multiplexed_run(mlwhdb_test_session): tags=tag1, ).hash_product_id(), qc=1, - hifi_read_bases=900, + hifi_read_bases=90000000, hifi_num_reads=20, - hifi_read_length_mean=45, + hifi_read_length_mean=4500000, barcode_quality_score_mean=34, hifi_bases_percent=90.001, pac_bio_run_well_metrics=well_metrics_b1, @@ -180,9 +180,9 @@ def multiplexed_run(mlwhdb_test_session): tags=tag1_2, ).hash_product_id(), qc=1, - hifi_read_bases=100, + hifi_read_bases=10000000, hifi_num_reads=10, - hifi_read_length_mean=10, + hifi_read_length_mean=1000000, barcode_quality_score_mean=34, hifi_bases_percent=100.00, pac_bio_run_well_metrics=well_metrics_b1, diff --git a/tests/test_pac_bio_qc_data_well.py b/tests/test_pac_bio_qc_data_well.py index 772a9cb..fb83be2 100644 --- a/tests/test_pac_bio_qc_data_well.py +++ b/tests/test_pac_bio_qc_data_well.py @@ -132,19 +132,19 @@ def test_pool_metrics_from_well(mlwhdb_test_session, multiplexed_run): for metrics in [metrics_via_db, metrics_direct]: assert ( int(metrics.pool_coeff_of_variance) == 47 - ), "Variance between 20 and 10 is ~47%" + ), "Variance between 20 reads and 10 reads is ~47%" - assert metrics.products[0].hifi_read_bases == 100 + assert metrics.products[0].hifi_read_bases == 0.01 assert ( - metrics.products[1].hifi_read_bases == 900 - ), "hifi read base counts are faithfully copied" + metrics.products[1].hifi_read_bases == 0.09 + ), "hifi read base counts are scaled to Gigabases" assert ( - int(metrics.products[0].percentage_total_reads) == 33 - ), "10 of 30 reads is 33.3%" + metrics.products[0].percentage_total_reads == 33.33 + ), "10Mb of 30Mb reads is 33.33% (2 d.p.)" assert ( - int(metrics.products[1].percentage_total_reads) == 66 - ), "20 of 30 reads is 66.6%" + metrics.products[1].percentage_total_reads == 66.67 + ), "20Mb of 30Mb reads is 66.67% (2 d.p.)" def test_errors_instantiating_pool_metrics(mlwhdb_test_session): From 389f51fcf569b33e0c348e0dc65e321dba3379e3 Mon Sep 17 00:00:00 2001 From: Kieron Taylor Date: Tue, 9 Jul 2024 16:26:22 +0000 Subject: [PATCH 4/8] App was relying on nested components importing ElToolTip --- frontend/src/App.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/App.vue b/frontend/src/App.vue index ca6dfc2..1e4c61c 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -1,7 +1,7 @@ + + diff --git a/frontend/src/components/__tests__/PoolStats.spec.js b/frontend/src/components/__tests__/PoolStats.spec.js new file mode 100644 index 0000000..fe8d33d --- /dev/null +++ b/frontend/src/components/__tests__/PoolStats.spec.js @@ -0,0 +1,60 @@ +import { describe, expect, test } from 'vitest' +import { mount } from '@vue/test-utils' +import ElementPlus from 'element-plus' + +import PoolStats from '../PoolStats.vue' + +const wrapper = mount(PoolStats, { + global: { + plugins: [ElementPlus], + }, + props: { + pool: { + pool_coeff_of_variance: 47.2, + products: [{ + id_product: 'A'.repeat(64), + tag1_name: 'TTTTTTTT', + tag2_name: null, + deplexing_barcode: 'bc10--bc10', + hifi_read_bases: 900, + hifi_num_reads: 20, + hifi_read_length_mean: 45, + hifi_bases_percent: 90.001, + percentage_total_reads: 66.6 + },{ + id_product: 'B'.repeat(64), + tag1_name: 'GGGGGGGG', + tag2_name: null, + deplexing_barcode: 'bc11--bc11', + hifi_read_bases: 100, + hifi_num_reads: 10, + hifi_read_length_mean: 10, + hifi_bases_percent: 100, + percentage_total_reads: 33.3 + }] + } + } +}) + +describe('Create poolstats table with good data', () => { + test('Component is "folded" by default', () => { + expect(wrapper.getComponent('transition-stub').attributes()['appear']).toEqual('false') + }) + + test('Coefficient of variance showing', async () => { + let topStat = wrapper.find('p') + await topStat.trigger('focus') + expect(topStat.classes('el-tooltip__trigger')).toBeTruthy() + + expect(topStat.text()).toEqual('Coefficient of Variance: 47.2') + }) + + test('Table looks about right', () => { + let rows = wrapper.findAll('tr') + expect(rows.length).toEqual(3) + + // Check tag 1 has been set + expect(rows[1].find('td').text()).toEqual('TTTTTTTT') + expect(rows[2].find('td').text()).toEqual('GGGGGGGG') + }) +}) \ No newline at end of file From 074b2a4dc5015cc1ab898b9d7ed1ada97568bdba Mon Sep 17 00:00:00 2001 From: Kieron Taylor Date: Wed, 10 Jul 2024 11:34:11 +0000 Subject: [PATCH 7/8] Add PoolStats component to QC View and load data on-demand. Fake some HTTP responses in testing --- frontend/src/components/QcView.vue | 33 +++++++++++++++++-- .../src/components/__tests__/QcView.spec.js | 5 +++ .../src/views/__tests__/WellsByRun.spec.js | 6 ++-- 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/QcView.vue b/frontend/src/components/QcView.vue index 0ab4c6a..ce0a84c 100644 --- a/frontend/src/components/QcView.vue +++ b/frontend/src/components/QcView.vue @@ -3,15 +3,36 @@ * An information view containing run data and metrics useful for QC assessment */ - import { computed } from "vue"; + import { computed, ref, watch } from "vue"; import groupMetrics from "../utils/metrics.js"; import { combineLabelWithPlate } from "../utils/text.js" + import PoolStats from "./PoolStats.vue"; + import LangQc from "../utils/langqc"; + + + const dataClient = new LangQc() const props = defineProps({ // Well object representing one prepared input for the instrument // Expects content in the form of lang_qc/models/pacbio/well.py:PacBioWellFull well: Object, - }); + }) + + const poolStats = ref(null) + watch(() => props.well, () => { + poolStats.value = null // empty in case next well doesn't have a pool + dataClient.getPoolMetrics(props.well.id_product).then( + (response) => { poolStats.value = response } + ).catch((error) => { + if (error.message.match("Conflict")) { + // Nothing to do + } else { + console.log(error) + // make a banner show this error? + } + }) + }, { immediate: true} + ) const slURL = computed(() => { let hostname = props.well.metrics.smrt_link.hostname @@ -97,7 +118,6 @@ } return '' }) -