From c0e8d7164ae660bb858a5a45171696e2524956eb Mon Sep 17 00:00:00 2001 From: bartcharbon Date: Fri, 1 Jul 2022 14:18:59 +0200 Subject: [PATCH] PoC: select records for download --- src/components/VariantsSampleTable.tsx | 16 +++++++++ src/components/record/RecordDownload.tsx | 25 ++++++++++++-- src/store/index.tsx | 44 ++++++++++++++++++++++-- src/utils/utils.ts | 6 ++++ src/views/SampleVariants.tsx | 8 +++++ 5 files changed, 93 insertions(+), 6 deletions(-) diff --git a/src/components/VariantsSampleTable.tsx b/src/components/VariantsSampleTable.tsx index b3ba7a04..2793ec8d 100644 --- a/src/components/VariantsSampleTable.tsx +++ b/src/components/VariantsSampleTable.tsx @@ -11,6 +11,8 @@ import { FieldMetadata } from "@molgenis/vip-report-vcf/src/MetadataParser"; import { FieldHeader } from "./FieldHeader"; import { Abbr } from "./Abbr"; import { abbreviateHeader } from "../utils/field"; +import { Checkbox, CheckboxEvent } from "./Checkbox"; +import { getRecordKey } from "../utils/utils"; export const VariantsSampleTable: Component<{ item: Item; @@ -19,6 +21,8 @@ export const VariantsSampleTable: Component<{ recordsMetadata: Metadata; nestedFields: FieldMetadata[]; htsFileMeta: HtsFileMetadata; + onSelectionChange: (event: CheckboxEvent) => void; + selected?: string[]; }> = (props) => { const samples = createMemo(() => [props.item.data, ...props.pedigreeSamples.map((item) => item.data)]); @@ -29,6 +33,9 @@ export const VariantsSampleTable: Component<{ + @@ -52,6 +59,15 @@ export const VariantsSampleTable: Component<{ {(record) => ( +
+ + Position Reference
+ {/* eslint-disable-next-line @typescript-eslint/restrict-template-expressions */} + + diff --git a/src/components/record/RecordDownload.tsx b/src/components/record/RecordDownload.tsx index 38176742..43f2c160 100644 --- a/src/components/record/RecordDownload.tsx +++ b/src/components/record/RecordDownload.tsx @@ -1,17 +1,36 @@ import { Component } from "solid-js"; import { Query, Sample } from "@molgenis/vip-report-api/src/Api"; -import { Metadata } from "@molgenis/vip-report-vcf/src/Vcf"; +import { Metadata, Record } from "@molgenis/vip-report-vcf/src/Vcf"; import api from "../../Api"; import { Filter, writeVcf } from "@molgenis/vip-report-vcf/src/VcfWriter"; +import { getRecordKey } from "../../utils/utils"; -export const RecordDownload: Component<{ recordsMetadata: Metadata; query?: Query; samples?: Sample[] }> = (props) => { +export const RecordDownload: Component<{ + recordsMetadata: Metadata; + query?: Query; + samples?: Sample[]; + selection?: string[]; +}> = (props) => { const filter = (): Filter | undefined => props.samples ? { samples: props.samples.map((sample) => sample.person.individualId) } : undefined; + function recordSelected(record: Record) { + if (props.selection !== undefined) { + return props.selection.indexOf(getRecordKey(record)) !== -1; + } + return true; + } + function onClick() { const handler = async () => { const records = await api.getRecords({ query: props.query, size: Number.MAX_SAFE_INTEGER }); - const vcf = writeVcf({ metadata: props.recordsMetadata, data: records.items.map((item) => item.data) }, filter()); + const vcf = writeVcf( + { + metadata: props.recordsMetadata, + data: records.items.filter((record) => recordSelected(record.data)).map((item) => item.data), + }, + filter() + ); const url = window.URL.createObjectURL(new Blob([vcf])); const link = document.createElement("a"); diff --git a/src/store/index.tsx b/src/store/index.tsx index c94622ac..c0b6111c 100644 --- a/src/store/index.tsx +++ b/src/store/index.tsx @@ -12,6 +12,7 @@ type AppStateVariants = { searchQuery?: string; filterQueries?: FilterQueries; sort?: SortOrder | null; // null: do not sort. undefined: sort undefined + selection: string[]; }; export type AppState = { @@ -33,6 +34,7 @@ export type AppActions = { setSampleVariantsPage(sample: Item, page: number): void; setSampleVariantsPageSize(sample: Item, pageSize: number): void; setSampleVariantsSearchQuery(sample: Item, searchQuery: string): void; + updateVariantsSelection(sample: Item, key: string, action: string): void; clearSampleVariantsSearchQuery(sample: Item): void; setSampleVariantsFilterQuery(sample: Item, query: QueryClause): void; clearSampleVariantsFilterQuery(sample: Item, selector: Selector): void; @@ -72,7 +74,10 @@ export const Provider: ParentComponent = (props) => { setState({ variants: { ...(state.variants || {}), - filterQueries: { ...(state.variants?.filterQueries || {}), [selectorKey(query.selector)]: query }, + filterQueries: { + ...((state.variants?.filterQueries as FilterQueries) || {}), + [selectorKey(query.selector)]: query, + }, }, }); }, @@ -127,7 +132,10 @@ export const Provider: ParentComponent = (props) => { [sample.id]: { variants: { ...variants, - filterQueries: { ...(variants.filterQueries || {}), [selectorKey(query.selector)]: query }, + filterQueries: { + ...((variants.filterQueries as FilterQueries) || {}), + [selectorKey(query.selector)]: query, + }, page: undefined, }, }, @@ -142,7 +150,10 @@ export const Provider: ParentComponent = (props) => { [sample.id]: { variants: { ...getVariants(sample), - filterQueries: { ...(variants.filterQueries || {}), [selectorKey(selector)]: undefined }, + filterQueries: { + ...((variants.filterQueries as FilterQueries) || {}), + [selectorKey(selector)]: undefined, + }, page: undefined, }, }, @@ -154,6 +165,33 @@ export const Provider: ParentComponent = (props) => { samples: { ...(state.samples || {}), [sample.id]: { variants: { ...getVariants(sample), sort } } }, }); }, + updateVariantsSelection(sample: Item, key: string, action: "ADD" | "REMOVE") { + let selection: string[] = state.samples ? Object.assign([], state.samples[sample.id]?.variants.selection) : []; + if (action === "ADD") { + if (selection) { + selection.push(key); + } else { + selection = [key]; + } + } else { + const index = selection.indexOf(key); + if (index === -1) { + throw new Error("Attempting to remove non-existing key from selection."); + } + selection.splice(index, 1); + } + setState({ + samples: { + ...(state.samples || {}), + [sample.id]: { + variants: { + ...getVariants(sample), + selection, + }, + }, + }, + }); + }, }; const store: AppStore = [state, actions]; diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 884ef60e..6ec25400 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,3 +1,9 @@ +import { Record } from "@molgenis/vip-report-vcf/src/Vcf"; + export function arrayEquals(a: T[], b: T[]) { return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((val, index) => val === b[index]); } + +export function getRecordKey(record: Record) { + return `${record.c}_${record.p}_${record.r}_${record.a.join("&")}`; +} diff --git a/src/views/SampleVariants.tsx b/src/views/SampleVariants.tsx index f14d827e..87d04b26 100644 --- a/src/views/SampleVariants.tsx +++ b/src/views/SampleVariants.tsx @@ -25,6 +25,7 @@ import { Metadata } from "@molgenis/vip-report-vcf/src/Vcf"; import { getSampleLabel } from "../utils/sample"; import { FilterChangeEvent, FilterClearEvent } from "../components/filter/Filter"; import { arrayEquals } from "../utils/utils"; +import { CheckboxEvent } from "../components/Checkbox"; export const SampleVariantsView: Component = () => { const { sample } = useRouteData(); @@ -122,6 +123,7 @@ export const SampleVariants: Component<{ "Consequence", "SYMBOL", "InheritanceModesGene", + "IncompletePenetrance", "HPO", "HGVSc", "HGVSp", @@ -162,6 +164,9 @@ export const SampleVariants: Component<{ actions.clearSampleVariantsFilterQuery(props.sample, event.selector); const onSortChange = (event: SortEvent) => actions.setSampleVariantsSort(props.sample, event.order); const onSortClear = () => actions.setSampleVariantsSort(props.sample, null); + const onSelectionchange = (event: CheckboxEvent) => { + actions.updateVariantsSelection(props.sample, event.value, "ADD"); + }; const params = (): Params => { return { @@ -231,6 +236,7 @@ export const SampleVariants: Component<{ recordsMetadata={props.recordsMeta} query={params().query} samples={[props.sample.data, ...props.pedigreeSamples.map((item) => item.data)]} + selection={getStateVariants()?.selection} /> @@ -247,6 +253,8 @@ export const SampleVariants: Component<{ recordsMetadata={props.recordsMeta} nestedFields={infoFields()} htsFileMeta={props.htsFileMeta} + onSelectionChange={onSelectionchange} + selected={getStateVariants() !== undefined ? getStateVariants()?.selection : []} /> )}