diff --git a/cases/vueapp/src/common.ts b/cases/vueapp/src/common.ts
new file mode 100644
index 000000000..f4a3f2cb9
--- /dev/null
+++ b/cases/vueapp/src/common.ts
@@ -0,0 +1,96 @@
+import {
+ useCaseDetailsStore,
+ CaseVariantStatsEntry,
+} from '@cases/stores/caseDetails'
+import { useCaseListStore } from '@cases/stores/caseList'
+import { State } from '@varfish/storeUtils'
+import { computed } from 'vue'
+
+export const overlayShow = computed(() => {
+ const caseListStore = useCaseListStore()
+ const caseDetailsStore = useCaseDetailsStore()
+ return (
+ (caseListStore?.storeState?.serverInteractions ?? 0) > 0 ||
+ (caseDetailsStore?.storeState?.serverInteractions ?? 0) > 0
+ )
+})
+
+export const overlayMessage = computed(() => {
+ const caseListStore = useCaseListStore()
+ switch (caseListStore.storeState.state) {
+ case State.Initial:
+ return 'initializing...'
+ case State.Fetching:
+ return 'communication with server...'
+ case State.Active:
+ return 'all data has been loaded successfully'
+ case State.Error:
+ return 'an error occured'
+ default:
+ console.error('unknown store state', caseListStore.storeState.state)
+ return 'UNKNOWN STATE'
+ }
+})
+
+export const tsTvRatio = (entry: CaseVariantStatsEntry): number => {
+ if (!entry.ontarget_transversions) {
+ return 0.0
+ } else {
+ return entry.ontarget_transitions / entry.ontarget_transversions
+ }
+}
+
+export const downloadFile = (
+ filename: string,
+ contents: any,
+ mimeType: string = 'application/octet-stream',
+) => {
+ const element = document.createElement('a')
+ element.setAttribute('href', 'data:' + mimeType + ';base64,' + btoa(contents))
+ element.setAttribute('download', filename)
+ element.style.display = 'none'
+
+ document.body.appendChild(element)
+ element.click()
+ document.body.removeChild(element)
+}
+
+export const downloadPerSampleMetrics = (varStats: CaseVariantStatsEntry[]) => {
+ const result = [
+ ['Sample', 'Ts', 'Tv', 'Ts/Tv', 'SNVs', 'InDels', 'MNVs', 'X hom./het.'],
+ ]
+ for (const entry of varStats) {
+ result.push(
+ [
+ entry.sample_name,
+ entry.ontarget_transitions,
+ entry.ontarget_transversions,
+ tsTvRatio(entry),
+ entry.ontarget_snvs,
+ entry.ontarget_indels,
+ entry.ontarget_mnvs,
+ entry.chrx_het_hom,
+ ].map((x) => x.toString()),
+ )
+ }
+ const text = result.map((row) => row.map(String).join('\t')).join('\n')
+ downloadFile('per-sample-metrics.tsv', text)
+}
+
+export interface Relatedness {
+ sample0: string
+ sample1: string
+ ibs0: number
+ ibs1: number
+ rel: number
+}
+
+export const downloadRelatedness = (relData: Relatedness[]) => {
+ const result = [['Sample 1', 'Sample 2', 'IBS0', 'Relatedness']]
+ for (const entry of relData) {
+ const { sample0, sample1, ibs0, rel } = entry
+ result.push([sample0, sample1, ibs0, rel].map((x) => x.toString()))
+ }
+ const text = result.map((row) => row.map(String).join('\t')).join('\n')
+ downloadFile('relatedness.tsv', text)
+}
diff --git a/cases/vueapp/src/components/CaseDetail/PaneAnnotations.vue b/cases/vueapp/src/components/CaseDetail/PaneAnnotations.vue
index 5365dc818..9632cc4d1 100644
--- a/cases/vueapp/src/components/CaseDetail/PaneAnnotations.vue
+++ b/cases/vueapp/src/components/CaseDetail/PaneAnnotations.vue
@@ -20,17 +20,6 @@ const props = defineProps({
caseUuid: String,
})
-const caseDetailsStore = useCaseDetailsStore()
-
-/** The details columns to show. */
-const displayDetails = ref(DisplayDetails.Coordinates.value)
-/** The frequency columns to show. */
-const displayFrequency = ref(DisplayFrequencies.GnomadExomes.value)
-/** The constraint columns to show. */
-const displayConstraint = ref(DisplayConstraints.GnomadPli.value)
-/** The additional columns to display. */
-const displayColumns = ref([DisplayColumns.Effect.value])
-
const showSmallVariantDetails = async (event) => {
router.push({
name: 'variant-details',
diff --git a/variants/vueapp/src/api/variantClient.ts b/variants/vueapp/src/api/variantClient.ts
index 99652c987..e285caf0c 100644
--- a/variants/vueapp/src/api/variantClient.ts
+++ b/variants/vueapp/src/api/variantClient.ts
@@ -335,7 +335,7 @@ export class VariantClient extends ClientBase {
)
}
- async fetchExtraAnnoFields(csrfToken: string) {
+ async fetchExtraAnnoFields() {
return await this.fetchHelper(`/variants/ajax/extra-anno-fields/`, 'GET')
}
diff --git a/variants/vueapp/src/components/FilterApp.vue b/variants/vueapp/src/components/FilterApp.vue
index 88e3ba3a2..6fa8906c2 100644
--- a/variants/vueapp/src/components/FilterApp.vue
+++ b/variants/vueapp/src/components/FilterApp.vue
@@ -78,7 +78,7 @@ const displayConstraint = ref(
const displayColumns = ref(
variantResultSetStore.displayColumns === null
? [DisplayColumns.Effect.value]
- : variantResultSetStore.displayColumns.split(','),
+ : variantResultSetStore.displayColumns,
)
// Toggle visibility of the form.
@@ -160,37 +160,13 @@ const refreshStores = async () => {
appContext.project?.sodar_uuid,
caseDetailsStore.caseObj.sodar_uuid,
),
- variantQueryStore
- .initialize(
- appContext.csrf_token,
- appContext?.project?.sodar_uuid,
- props.caseUuid,
- appContext,
- )
- .then(() => {
- // Show the SpliceAI comments by default.
- if (!props.displayColumns) {
- const maybeAdd = [
- 'SpliceAI-acc-gain',
- 'SpliceAI-acc-loss',
- 'SpliceAI-don-loss',
- 'SpliceAI-don-gain',
- 'CADD-PHRED',
- ]
- variantQueryStore.extraAnnoFields
- .filter((value) => maybeAdd.includes(value.label))
- .forEach((value) => {
- displayColumns.value.push(`extra_anno${value.field}`)
- })
- }
- variantResultSetStore
- .initialize(appContext.csrf_token)
- .then(async () => {
- await variantResultSetStore.loadResultSetViaQuery(
- variantQueryStore.queryUuid,
- )
- })
- }),
+ variantQueryStore.initialize(
+ appContext.csrf_token,
+ appContext?.project?.sodar_uuid,
+ props.caseUuid,
+ appContext,
+ ),
+ variantResultSetStore.initialize(appContext.csrf_token),
])
}
diff --git a/variants/vueapp/src/components/FilterResultsTable.vue b/variants/vueapp/src/components/FilterResultsTable.vue
index 2c3cf18da..50d46d52f 100644
--- a/variants/vueapp/src/components/FilterResultsTable.vue
+++ b/variants/vueapp/src/components/FilterResultsTable.vue
@@ -56,8 +56,6 @@ const commentsStore = useVariantCommentsStore()
const acmgRatingStore = useVariantAcmgRatingStore()
const variantResultSetStore = useVariantResultSetStore()
-const router = useRouter()
-
/** The details columns to show. */
const displayDetails = computed({
get() {
@@ -99,7 +97,7 @@ const displayConstraint = computed({
/** The additional columns to display. */
const displayColumns = computed({
get() {
- return variantResultSetStore.displayColumns || [DisplayColumns.Effect.value]
+ return variantResultSetStore.displayColumns ?? [DisplayColumns.Effect.value]
},
set(newValue) {
variantResultSetStore.displayColumns = newValue
@@ -146,9 +144,6 @@ const coordinatesClinvarColumns = () => {
const optionalColumns = () => {
const optionalColumnTexts = copy(DisplayColumnsToText)
- if (!extraAnnoFields.value) {
- return []
- }
for (const { field, label } of extraAnnoFields.value) {
optionalColumnTexts[`extra_anno${field}`] = label
}
@@ -464,14 +459,12 @@ const loadFromServer = async () => {
}
}
-const extraAnnoFields = ref()
+const extraAnnoFields = computed(
+ () => variantResultSetStore.extraAnnoFields ?? [],
+)
/** Load data when mounted. */
onBeforeMount(async () => {
- const variantClient = new VariantClient(caseDetailsStore.csrfToken)
- extraAnnoFields.value = await variantClient.fetchExtraAnnoFields(
- caseDetailsStore.csrfToken,
- )
if (variantResultSetStore.resultSetUuid) {
await loadFromServer()
}
@@ -504,11 +497,11 @@ watch(
diff --git a/variants/vueapp/src/stores/variantResultSet.ts b/variants/vueapp/src/stores/variantResultSet.ts
index 9f41037c7..83eddfab1 100644
--- a/variants/vueapp/src/stores/variantResultSet.ts
+++ b/variants/vueapp/src/stores/variantResultSet.ts
@@ -7,6 +7,7 @@ import { ref, reactive } from 'vue'
import { StoreState, State } from '@varfish/storeUtils'
import { VariantClient } from '@variants/api/variantClient'
+import { DisplayColumns } from '@variants/enums'
export const useVariantResultSetStore = defineStore('variantResultSet', () => {
// no store dependencies
@@ -19,6 +20,8 @@ export const useVariantResultSetStore = defineStore('variantResultSet', () => {
const caseUuid = ref(null)
/** The current application state. */
const storeState = reactive(new StoreState())
+ /** Extra annotation fields available. */
+ const extraAnnoFields = ref(null)
// other data (loaded via REST API or computed)
@@ -56,8 +59,19 @@ export const useVariantResultSetStore = defineStore('variantResultSet', () => {
storeState.serverInteractions = 0
storeState.message = null
+ resultRow.value = null
resultSetUuid.value = null
resultSet.value = null
+ query.value = null
+
+ tablePageNo.value = null
+ tablePageSize.value = null
+ tableSortBy.value = null
+ tableSortType.value = null
+ displayDetails.value = null
+ displayFrequency.value = null
+ displayConstraint.value = null
+ displayColumns.value = null
}
/**
@@ -66,7 +80,10 @@ export const useVariantResultSetStore = defineStore('variantResultSet', () => {
* @param csrfToken$ CSRF token to use.
* @param forceReload Whether to force reload.
*/
- const initialize = (csrfToken$: string, forceReload: boolean = false) => {
+ const initialize = async (
+ csrfToken$: string,
+ forceReload: boolean = false,
+ ) => {
// Initialize only once.
if (!forceReload && storeState.state !== State.Initial) {
return initializeRes.value
@@ -74,6 +91,12 @@ export const useVariantResultSetStore = defineStore('variantResultSet', () => {
$reset()
+ // Load extra annotation fields, if necessary.
+ if (!extraAnnoFields.value) {
+ const variantClient = new VariantClient(csrfToken$)
+ extraAnnoFields.value = await variantClient.fetchExtraAnnoFields()
+ }
+
// Set simple properties.
csrfToken.value = csrfToken$
@@ -84,6 +107,22 @@ export const useVariantResultSetStore = defineStore('variantResultSet', () => {
return initializeRes.value
}
+ const refreshDisplayColumns = () => {
+ const maybeAdd = [
+ 'SpliceAI-acc-gain',
+ 'SpliceAI-acc-loss',
+ 'SpliceAI-don-loss',
+ 'SpliceAI-don-gain',
+ 'CADD-PHRED',
+ ]
+ displayColumns.value = [DisplayColumns.Effect.value]
+ extraAnnoFields.value
+ .filter((value) => maybeAdd.includes(value.label))
+ .forEach((value) => {
+ displayColumns.value.push(`extra_anno${value.field}`)
+ })
+ }
+
const loadResultSetViaQuery = async (queryUuid$: string) => {
// Once query is finished, load results, if still for the same query.
const variantClient = new VariantClient(csrfToken.value)
@@ -96,6 +135,7 @@ export const useVariantResultSetStore = defineStore('variantResultSet', () => {
// Still fetching the same query; push to query result set.
resultSet.value = responseResultSetList[0]
resultSetUuid.value = responseResultSetList[0].sodar_uuid
+ refreshDisplayColumns()
}
}
@@ -106,6 +146,7 @@ export const useVariantResultSetStore = defineStore('variantResultSet', () => {
if (case$.smallvariantqueryresultset) {
resultSet.value = case$.smallvariantqueryresultset
resultSetUuid.value = case$.smallvariantqueryresultset.sodar_uuid
+ refreshDisplayColumns()
} else {
console.error('ERROR: no result set in case response')
}
@@ -142,6 +183,8 @@ export const useVariantResultSetStore = defineStore('variantResultSet', () => {
)
query.value = query$
caseUuid.value = query$.case
+
+ refreshDisplayColumns()
} else {
// Otherwise, it really should provide us with a Case UUID.
if (!resultSet$.case) {
@@ -167,6 +210,7 @@ export const useVariantResultSetStore = defineStore('variantResultSet', () => {
// data / state
csrfToken,
storeState,
+ extraAnnoFields,
resultRow,
resultSetUuid,
resultSet,