From 6d320a613c9f9fd6dd1f4ed6a9b45748eb5aba2b Mon Sep 17 00:00:00 2001 From: Oliver Stolpe Date: Tue, 28 May 2024 17:37:38 +0200 Subject: [PATCH] feat: show flags comments from other cases in variant result table (#1564) --- .../src/components/SvFilterResultsTable.vue | 8 +-- svs/vueapp/src/stores/strucvarFlags/store.ts | 63 ++++++++++++------- svs/vueapp/src/stores/svComments.ts | 51 ++++++++++----- .../src/components/FilterResultsTable.vue | 32 ++++++++-- variants/vueapp/src/stores/variantComments.ts | 57 +++++++++++++---- variants/vueapp/src/stores/variantFlags.ts | 52 +++++++++++---- 6 files changed, 194 insertions(+), 69 deletions(-) diff --git a/svs/vueapp/src/components/SvFilterResultsTable.vue b/svs/vueapp/src/components/SvFilterResultsTable.vue index d1bd61565..abc19c775 100644 --- a/svs/vueapp/src/components/SvFilterResultsTable.vue +++ b/svs/vueapp/src/components/SvFilterResultsTable.vue @@ -479,14 +479,14 @@ watch( /> diff --git a/svs/vueapp/src/stores/strucvarFlags/store.ts b/svs/vueapp/src/stores/strucvarFlags/store.ts index ff907ca01..9419f202b 100644 --- a/svs/vueapp/src/stores/strucvarFlags/store.ts +++ b/svs/vueapp/src/stores/strucvarFlags/store.ts @@ -5,7 +5,7 @@ import { defineStore } from 'pinia' import { ref, reactive } from 'vue' import { StoreState, State } from '@varfish/storeUtils' -import { SvClient } from '@svs/api/strucvarClient' +import { SvClient, SvFlags } from '@svs/api/strucvarClient' import { bndInsOverlap, reciprocalOverlap } from '@varfish/helpers' import { useCaseDetailsStore } from '@cases/stores/caseDetails' import { Strucvar } from '@bihealth/reev-frontend-lib/lib/genomicVars' @@ -64,6 +64,8 @@ export const useSvFlagsStore = defineStore('svFlags', () => { const caseFlags = ref>(new Map()) /** The project-wide variant flags. */ const projectWideVariantFlags = ref>([]) + /** The project-wide flags. */ + const projectWideFlags = ref>([]) /** Promise for initialization of the store. */ const initializeRes = ref | null>(null) @@ -118,22 +120,26 @@ export const useSvFlagsStore = defineStore('svFlags', () => { const svClient = new SvClient(csrfToken.value ?? 'undefined-csrf-token') - initializeRes.value = svClient - .listFlags(caseUuid.value) - .then((flags) => { + initializeRes.value = Promise.all([ + svClient.listFlags(caseUuid.value).then((flags) => { caseFlags.value.clear() for (const flag of flags) { caseFlags.value.set(flag.sodar_uuid, flag) } + }), + svClient + .listProjectFlags(projectUuid.value, caseUuid.value) + .then((result) => { + projectWideFlags.value = result + }), + ]).catch((err) => { + console.error('Problem initializing variantFlags store', err) + storeState.serverInteractions -= 1 + storeState.state = State.Error + }) - storeState.serverInteractions -= 1 - storeState.state = State.Active - }) - .catch((err) => { - console.error('Problem initializing svFlags store', err) - storeState.serverInteractions -= 1 - storeState.state = State.Error - }) + storeState.serverInteractions -= 1 + storeState.state = State.Active return initializeRes.value } @@ -299,17 +305,13 @@ export const useSvFlagsStore = defineStore('svFlags', () => { flags.value = null } - /** - * Return first matching flag for the given `sv`. - */ - const getFlags = (sv: StructuralVariant): StructuralVariantFlags | null => { - if (!caseFlags.value) { - return null - } - + const _getFlags = ( + sv: StructuralVariant, + flagList: Array, + ): StructuralVariantFlags | null => { const bndInsRadius = 50 const minReciprocalOverlap = 0.8 - for (const flag of caseFlags.value.values()) { + for (const flag of flagList) { if ( ['BND', 'INS'].includes(flag.sv_type) && flag.sv_type === sv.sv_type && @@ -323,7 +325,22 @@ export const useSvFlagsStore = defineStore('svFlags', () => { return flag } } - return null + return false + } + + /** + * Return first matching flag for the given `sv`. + */ + const getFlags = (sv: StructuralVariant): StructuralVariantFlags | null => { + if (!caseFlags.value) { + return null + } + return _getFlags(sv, Array.from(caseFlags.value.values())) + } + + const hasProjectWideFlags = (sv: Strucvar): boolean => { + const flag = _getFlags(sv, projectWideFlags.value) + return flag ? true : false } /** @@ -373,6 +390,7 @@ export const useSvFlagsStore = defineStore('svFlags', () => { emptyFlagsTemplate, initialFlagsTemplate, projectWideVariantFlags, + projectWideFlags, // functions initialize, retrieveFlags, @@ -381,5 +399,6 @@ export const useSvFlagsStore = defineStore('svFlags', () => { deleteFlags, getFlags, retrieveProjectWideVariantFlags, + hasProjectWideFlags, } }) diff --git a/svs/vueapp/src/stores/svComments.ts b/svs/vueapp/src/stores/svComments.ts index db3842d87..5dedb275b 100644 --- a/svs/vueapp/src/stores/svComments.ts +++ b/svs/vueapp/src/stores/svComments.ts @@ -45,6 +45,8 @@ export const useSvCommentsStore = defineStore('svComments', () => { const caseComments = ref>(new Map()) /** The project-wide variant comments. */ const projectWideVariantComments = ref>([]) + /** The project-wide comments. */ + const projectWideComments = ref>([]) /** Promise for initialization of the store. */ const initializeRes = ref | null>(null) @@ -97,22 +99,26 @@ export const useSvCommentsStore = defineStore('svComments', () => { const svClient = new SvClient(csrfToken.value ?? 'undefined-csrf-token') - initializeRes.value = svClient - .listComment(caseUuid.value) - .then((comments) => { + initializeRes.value = Promise.all([ + svClient.listComment(caseUuid.value).then((comments) => { caseComments.value.clear() for (const comment of comments) { caseComments.value.set(comment.sodar_uuid, comment) } + }), + svClient + .listProjectComment(projectUuid.value, caseUuid.value) + .then((result) => { + projectWideVariantComments.value = result + }), + ]).catch((err) => { + console.error('Problem initializing svComments store', err) + storeState.serverInteractions -= 1 + storeState.state = State.Error + }) - storeState.serverInteractions -= 1 - storeState.state = State.Active - }) - .catch((err) => { - console.error('Problem initializing svComments store', err) - storeState.serverInteractions -= 1 - storeState.state = State.Error - }) + storeState.serverInteractions -= 1 + storeState.state = State.Active return initializeRes.value } @@ -273,12 +279,12 @@ export const useSvCommentsStore = defineStore('svComments', () => { ) } - /** - * Return whether there is a comment for the given variant. - */ - const hasComment = (strucvar$: Strucvar): boolean => { + const _hasComment = ( + strucvar$: Strucvar, + comments: StructuralVariantComment, + ): boolean => { const minReciprocalOverlap = 0.8 - for (const comment of caseComments.value.values()) { + for (const comment of comments) { let end if (strucvar$.svType === 'INS' || strucvar$.svType === 'BND') { end = strucvar$.start @@ -299,6 +305,17 @@ export const useSvCommentsStore = defineStore('svComments', () => { return false } + /** + * Return whether there is a comment for the given variant. + */ + const hasComment = (strucvar$: Strucvar): boolean => { + return _hasComment(strucvar$, caseComments.value.values()) + } + + const hasProjectWideComments = (strucvar$: Strucvar): boolean => { + return _hasComment(strucvar$, projectWideVariantComments.value) + } + /** * Retrieve project-wide variant comments. */ @@ -344,6 +361,7 @@ export const useSvCommentsStore = defineStore('svComments', () => { comments, caseComments, projectWideVariantComments, + projectWideComments, initializeRes, // functions initialize, @@ -352,6 +370,7 @@ export const useSvCommentsStore = defineStore('svComments', () => { updateComment, deleteComment, hasComment, + hasProjectWideComments, retrieveProjectWideVariantComments, } }) diff --git a/variants/vueapp/src/components/FilterResultsTable.vue b/variants/vueapp/src/components/FilterResultsTable.vue index 012d56106..99f88f4e8 100644 --- a/variants/vueapp/src/components/FilterResultsTable.vue +++ b/variants/vueapp/src/components/FilterResultsTable.vue @@ -262,6 +262,30 @@ const hasComments = (payload) => { ) } +const hasProjectComments = (payload) => { + return commentsStore.hasProjectWideComments( + new SeqvarImpl( + payload.release === 'GRCh37' ? 'grch37' : 'grch38', + payload.chromosome, + payload.start, + payload.reference, + payload.alternative, + ), + ) +} + +const hasProjectFlags = (payload) => { + return flagsStore.hasProjectWideFlags( + new SeqvarImpl( + payload.release === 'GRCh37' ? 'grch37' : 'grch38', + payload.chromosome, + payload.start, + payload.reference, + payload.alternative, + ), + ) +} + /** * Configuration for the row to color them based on flags. */ @@ -636,14 +660,14 @@ watch( /> diff --git a/variants/vueapp/src/stores/variantComments.ts b/variants/vueapp/src/stores/variantComments.ts index d54509bd6..acdb4405b 100644 --- a/variants/vueapp/src/stores/variantComments.ts +++ b/variants/vueapp/src/stores/variantComments.ts @@ -43,6 +43,10 @@ export const useVariantCommentsStore = defineStore('variantComments', () => { const caseComments = ref>(new Map()) /** The project-wide variant comments. */ const projectWideVariantComments = ref>([]) + /** The project-wide comments. */ + const projectWideComments = ref>>( + new Map(), + ) /** Promise for initialization of the store. */ const initializeRes = ref | null>(null) @@ -101,22 +105,39 @@ export const useVariantCommentsStore = defineStore('variantComments', () => { csrfToken.value ?? 'undefined-csrf-token', ) - initializeRes.value = variantClient - .listComment(caseUuid.value) - .then((result) => { + initializeRes.value = Promise.all([ + variantClient.listComment(caseUuid.value).then((result) => { caseComments.value.clear() for (const comment of result) { caseComments.value.set(comment.sodar_uuid, comment) } + }), + variantClient + .listProjectComment(projectUuid.value, caseUuid.value) + .then((result) => { + for (const comment of result) { + const key = `${comment.chromosome}-${comment.start}-${comment.reference}-${comment.alternative}` + if (!projectWideComments.value.has(key)) { + projectWideComments.value.set(key, [comment]) + } else { + let comments = projectWideComments.value.get(key) + if (comments) { + comments.push(comment) + } else { + comments = [comment] + } + projectWideComments.value.set(key, comments) + } + } + }), + ]).catch((err) => { + console.error('Problem initializing variantComments store', err) + storeState.serverInteractions -= 1 + storeState.state = State.Error + }) - storeState.serverInteractions -= 1 - storeState.state = State.Active - }) - .catch((err) => { - console.error('Problem initializing variantComments store', err) - storeState.serverInteractions -= 1 - storeState.state = State.Error - }) + storeState.serverInteractions -= 1 + storeState.state = State.Active return initializeRes.value } @@ -301,8 +322,18 @@ export const useVariantCommentsStore = defineStore('variantComments', () => { return false } + const hasProjectWideComments = (seqvar: Seqvar): boolean => { + const val = projectWideComments.value.get( + `${seqvar.chrom}-${seqvar.pos}-${seqvar.del}-${seqvar.ins}`, + ) + if (val) { + return val.length > 0 + } + return false + } + /** - * Retrieve project-wide variant comments. + * Retrieve project-wide comments. */ const retrieveProjectWideVariantComments = async (seqvar$: Seqvar) => { if (!caseUuid.value) { @@ -362,6 +393,7 @@ export const useVariantCommentsStore = defineStore('variantComments', () => { comments, caseComments, projectWideVariantComments, + projectWideComments, initializeRes, // functions initialize, @@ -371,6 +403,7 @@ export const useVariantCommentsStore = defineStore('variantComments', () => { updateComment, deleteComment, hasComments, + hasProjectWideComments, $reset, } }) diff --git a/variants/vueapp/src/stores/variantFlags.ts b/variants/vueapp/src/stores/variantFlags.ts index 814a83a9f..f7de904bd 100644 --- a/variants/vueapp/src/stores/variantFlags.ts +++ b/variants/vueapp/src/stores/variantFlags.ts @@ -45,6 +45,8 @@ export const useVariantFlagsStore = defineStore('variantFlags', () => { /** For whole project: flags for all variants of the project with the given `projectUuid`. */ const projectWideVariantFlags = ref>([]) + /** The project-wide flags. */ + const projectWideFlags = ref>>(new Map()) /** Template object to use for for empty flags. */ const emptyFlagsTemplate = Object.freeze({ @@ -126,22 +128,39 @@ export const useVariantFlagsStore = defineStore('variantFlags', () => { csrfToken.value ?? 'undefined-csrf-token', ) - initializeRes.value = variantClient - .listFlags(caseUuid.value) - .then((flags) => { + initializeRes.value = Promise.all([ + variantClient.listFlags(caseUuid.value).then((flags) => { caseFlags.value.clear() for (const flag of flags) { caseFlags.value.set(flag.sodar_uuid, flag) } + }), + variantClient + .listProjectFlags(projectUuid.value, caseUuid.value) + .then((result) => { + for (const flags of result) { + const key = `${flags.chromosome}-${flags.start}-${flags.reference}-${flags.alternative}` + if (!projectWideFlags.value.has(key)) { + projectWideFlags.value.set(key, [flags]) + } else { + let flags = projectWideFlags.value.get(key) + if (flags) { + flags.push(flags) + } else { + flags = [flags] + } + projectWideFlags.value.set(key, flags) + } + } + }), + ]).catch((err) => { + console.error('Problem initializing variantFlags store', err) + storeState.serverInteractions -= 1 + storeState.state = State.Error + }) - storeState.serverInteractions -= 1 - storeState.state = State.Active - }) - .catch((err) => { - console.error('Problem initializing variantFlags store', err) - storeState.serverInteractions -= 1 - storeState.state = State.Error - }) + storeState.serverInteractions -= 1 + storeState.state = State.Active return initializeRes.value } @@ -389,6 +408,16 @@ export const useVariantFlagsStore = defineStore('variantFlags', () => { } } + const hasProjectWideFlags = (seqvar: Seqvar): boolean => { + const val = projectWideFlags.value.get( + `${seqvar.chrom}-${seqvar.pos}-${seqvar.del}-${seqvar.ins}`, + ) + if (val) { + return val.length > 0 + } + return false + } + const $reset = () => { storeState.state = State.Initial storeState.serverInteractions = 0 @@ -425,6 +454,7 @@ export const useVariantFlagsStore = defineStore('variantFlags', () => { getFlags, flagAsArtifact, retrieveProjectWideVariantFlags, + hasProjectWideFlags, $reset, } })