From ccc5cd36dac787be0f7a6353167fa3facfbd4a53 Mon Sep 17 00:00:00 2001 From: bartcharbon Date: Fri, 4 Dec 2020 13:13:22 +0100 Subject: [PATCH 1/3] Feature add inheritance filtering --- src/components/RecordTable.vue | 31 ++++++++++++++++-- src/components/RecordTableControls.vue | 28 ++++++++++------ src/locales/en.json | 3 +- src/store/actions.ts | 3 ++ src/store/getters.ts | 13 ++++++++ src/store/mutations.ts | 3 ++ src/store/state.ts | 1 + src/types/State.ts | 1 + src/views/Samples.vue | 2 +- tests/unit/store/actions.spec.ts | 7 ++++ tests/unit/store/getters.spec.ts | 45 ++++++++++++++++++++++++++ tests/unit/store/mutations.spec.ts | 6 ++++ 12 files changed, 128 insertions(+), 15 deletions(-) diff --git a/src/components/RecordTable.vue b/src/components/RecordTable.vue index fa6de868..f9cd71cc 100644 --- a/src/components/RecordTable.vue +++ b/src/components/RecordTable.vue @@ -266,9 +266,10 @@ export default Vue.extend({ 'hasCapice', 'getAnnotation', 'isAnnotationEnabled', - 'isRecordsContainPhenotypes' + 'isRecordsContainPhenotypes', + 'isSamplesContainInheritance' ]), - ...mapState(['metadata', 'records', 'annotations', 'selectedSamplePhenotypes', 'filterRecordsByPhenotype']), + ...mapState(['metadata', 'records', 'annotations', 'selectedSamplePhenotypes', 'filterRecordsByPhenotype', 'filterRecordsByInheritance']), genomeAssembly(): string { return this.metadata.htsFile.genomeAssembly; }, @@ -349,13 +350,16 @@ export default Vue.extend({ this.infoModal.record = null; }, createQuery(): Query | ComposedQuery | undefined { - const queries: Query[] = []; + const queries: (Query|ComposedQuery)[] = []; if (this.sample) { queries.push(this.createSampleQuery()); } if (this.isRecordsContainPhenotypes && this.hasSamplePhenotypes() && this.filterRecordsByPhenotype) { queries.push(this.createSamplePhenotypesQuery()); } + if (this.isSamplesContainInheritance && this.filterRecordsByInheritance) { + queries.push(this.createSampleInheritanceQuery()); + } // todo: translate ctx filter param to query let query: Query | ComposedQuery | undefined; @@ -378,6 +382,23 @@ export default Vue.extend({ args: ['het', 'hom_a', 'part'] }; }, + createSampleInheritanceQuery(): ComposedQuery { + return { + operator: 'or', + args: [ + { + selector: ['s', this.sample.index, 'f', 'VIM'], + operator: '==', + args: "1" + }, + { + selector: ['s', this.sample.index, 'f', 'VID'], + operator: '==', + args: 1 + } + ], + }; + }, hasSamplePhenotypes(): boolean { return this.selectedSamplePhenotypes !== null && this.selectedSamplePhenotypes.page.totalElements > 0; }, @@ -484,6 +505,10 @@ export default Vue.extend({ '$store.state.filterRecordsByPhenotype'() { this.page.currentPage = 1; (this.$refs.table as BTable).refresh(); + }, + '$store.state.filterRecordsByInheritance'() { + this.page.currentPage = 1; + (this.$refs.table as BTable).refresh(); } } }); diff --git a/src/components/RecordTableControls.vue b/src/components/RecordTableControls.vue index 2692df99..e5344b90 100644 --- a/src/components/RecordTableControls.vue +++ b/src/components/RecordTableControls.vue @@ -1,23 +1,31 @@ diff --git a/src/locales/en.json b/src/locales/en.json index f0ddde27..a76d23a8 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -53,5 +53,6 @@ "open": "Open...", "save": "Save...", "exit": "Exit", - "matchPhenotype": "Only display records that match sample phenotype(s)" + "matchPhenotype": "Only display records that match sample phenotype(s)", + "matchInheritance": "Only display records that match inheritance or are denovo" } \ No newline at end of file diff --git a/src/store/actions.ts b/src/store/actions.ts index e1f1a96a..562590e5 100644 --- a/src/store/actions.ts +++ b/src/store/actions.ts @@ -83,6 +83,9 @@ export default { }, setFilterRecordsByPhenotype({ commit }: ActionContext, value: boolean) { commit('setFilterRecordsByPhenotype', value); + }, + setFilterRecordsByInheritance({ commit }: ActionContext, value: boolean) { + commit('setFilterRecordsByInheritance', value); } }; diff --git a/src/store/getters.ts b/src/store/getters.ts index e881936c..98bd0bc9 100644 --- a/src/store/getters.ts +++ b/src/store/getters.ts @@ -119,5 +119,18 @@ export default { } } return hasPhenotypeAssociations; + }, + /** + * Returns whether records contain inheritance matching information. + */ + isSamplesContainInheritance: (state: State): boolean => { + let hasSampleInheritance; + if (state.metadata === null) { + hasSampleInheritance = false; + } else { + const vimFormat = state.metadata.records.format.find(item => item.id === 'VIM'); + hasSampleInheritance = vimFormat !== undefined + } + return hasSampleInheritance; } }; diff --git a/src/store/mutations.ts b/src/store/mutations.ts index 5a294eae..8a27ec03 100644 --- a/src/store/mutations.ts +++ b/src/store/mutations.ts @@ -32,5 +32,8 @@ export default { }, setFilterRecordsByPhenotype(state: State, value: boolean) { state.filterRecordsByPhenotype = value; + }, + setFilterRecordsByInheritance(state: State, value: boolean) { + state.filterRecordsByInheritance = value; } }; diff --git a/src/store/state.ts b/src/store/state.ts index 4dbd16c1..44e2f416 100644 --- a/src/store/state.ts +++ b/src/store/state.ts @@ -8,6 +8,7 @@ const state: State = { selectedSamplePhenotypes: null, records: null, filterRecordsByPhenotype: true, + filterRecordsByInheritance: false, annotations: null }; diff --git a/src/types/State.ts b/src/types/State.ts index d831928c..93aa92f1 100644 --- a/src/types/State.ts +++ b/src/types/State.ts @@ -10,5 +10,6 @@ export type State = { selectedSamplePhenotypes: PagedItems | null; records: PagedItems | null; filterRecordsByPhenotype: boolean; + filterRecordsByInheritance: boolean; annotations: Annotations | null; }; diff --git a/src/views/Samples.vue b/src/views/Samples.vue index 91548ba1..2c992736 100644 --- a/src/views/Samples.vue +++ b/src/views/Samples.vue @@ -20,7 +20,7 @@ export default Vue.extend({ components: { SampleNavigation, SampleReport }, computed: { ...mapGetters(['samples', 'getSampleById']), - ...mapState(['selectedSample', 'selectedSamplePhenotypes']) + ...mapState(['selectedSample', 'selectedSamplePhenotypes', 'selectedSampleInheritance']) }, methods: { ...mapActions(['loadMetadata', 'loadSamples', 'selectSample']), diff --git a/tests/unit/store/actions.spec.ts b/tests/unit/store/actions.spec.ts index 353a3546..32105ad8 100644 --- a/tests/unit/store/actions.spec.ts +++ b/tests/unit/store/actions.spec.ts @@ -136,3 +136,10 @@ test('set filter records by phenotype', async done => { expect(commit).toHaveBeenCalledWith('setFilterRecordsByPhenotype', true); done(); }); + +test('set filter records by inheritance', async done => { + const commit = jest.fn() as Commit; + actions.setFilterRecordsByInheritance({ commit } as ActionContext, true); + expect(commit).toHaveBeenCalledWith('setFilterRecordsByInheritance', true); + done(); +}); diff --git a/tests/unit/store/getters.spec.ts b/tests/unit/store/getters.spec.ts index d28e0d32..d1958c09 100644 --- a/tests/unit/store/getters.spec.ts +++ b/tests/unit/store/getters.spec.ts @@ -4,6 +4,7 @@ import { State } from '@/types/State'; import { GenomeBrowserDb } from '@/types/GenomeBrowserDb'; import { mock } from 'ts-mockito'; import { + FormatMetadata, HtsFileMetadata, InfoMetadata, Metadata, @@ -403,3 +404,47 @@ test('whether records contain phenotype associations without VEP', () => { }; expect(getters.isRecordsContainPhenotypes(testState)).toEqual(false); }); + +test('whether records contain inheritance matching information. true.', () => { + const formatMetadata = mock(); + formatMetadata.id = 'VIM'; + + const recordsMetadata = mock(); + recordsMetadata.format = [formatMetadata]; + + const metadata = mock(); + metadata.records = recordsMetadata; + + const testState: State = { + ...initialState, + metadata: metadata + }; + expect(getters.isSamplesContainInheritance(testState)).toEqual(true); +}); + +test('whether records contain inheritance matching information. false.', () => { + const formatMetadata = mock(); + formatMetadata.id = 'OTHER'; + + const recordsMetadata = mock(); + recordsMetadata.format = [formatMetadata]; + + const metadata = mock(); + metadata.records = recordsMetadata; + + const testState: State = { + ...initialState, + metadata: metadata + }; + expect(getters.isSamplesContainInheritance(testState)).toEqual(false); +}); + +test('whether records contain inheritance matching information. null.', () => { + const metadata =null; + + const testState: State = { + ...initialState, + metadata: metadata + }; + expect(getters.isSamplesContainInheritance(testState)).toEqual(false); +}); \ No newline at end of file diff --git a/tests/unit/store/mutations.spec.ts b/tests/unit/store/mutations.spec.ts index 53d883fa..2fd302f9 100644 --- a/tests/unit/store/mutations.spec.ts +++ b/tests/unit/store/mutations.spec.ts @@ -52,3 +52,9 @@ test('set filter records by phenotype', () => { mutations.setFilterRecordsByPhenotype(testState, false); expect(testState.filterRecordsByPhenotype).toBe(false); }); + +test('set filter records by inheritance', () => { + const testState: State = { ...initialState }; + mutations.setFilterRecordsByInheritance(testState, false); + expect(testState.filterRecordsByInheritance).toBe(false); +}); From 9c11a4a1988f7a52fd19da3e2e098b77c42ea9ea Mon Sep 17 00:00:00 2001 From: bartcharbon Date: Mon, 7 Dec 2020 07:27:50 +0100 Subject: [PATCH 2/3] Split denovo and inheritance filters --- src/components/RecordTable.vue | 41 +++++++++++++----------- src/components/RecordTableControls.vue | 12 +++++-- src/locales/en.json | 3 +- src/store/actions.ts | 3 ++ src/store/getters.ts | 13 ++++++++ src/store/mutations.ts | 3 ++ src/store/state.ts | 1 + src/types/State.ts | 1 + src/views/Samples.vue | 2 +- tests/unit/store/actions.spec.ts | 7 ++++ tests/unit/store/getters.spec.ts | 44 ++++++++++++++++++++++++++ tests/unit/store/mutations.spec.ts | 6 ++++ 12 files changed, 113 insertions(+), 23 deletions(-) diff --git a/src/components/RecordTable.vue b/src/components/RecordTable.vue index f9cd71cc..83004e86 100644 --- a/src/components/RecordTable.vue +++ b/src/components/RecordTable.vue @@ -267,9 +267,10 @@ export default Vue.extend({ 'getAnnotation', 'isAnnotationEnabled', 'isRecordsContainPhenotypes', - 'isSamplesContainInheritance' + 'isSamplesContainInheritance', + 'isSamplesContainDenovo' ]), - ...mapState(['metadata', 'records', 'annotations', 'selectedSamplePhenotypes', 'filterRecordsByPhenotype', 'filterRecordsByInheritance']), + ...mapState(['metadata', 'records', 'annotations', 'selectedSamplePhenotypes', 'filterRecordsByPhenotype', 'filterRecordsByInheritance', 'filterRecordsByDenovo']), genomeAssembly(): string { return this.metadata.htsFile.genomeAssembly; }, @@ -350,7 +351,7 @@ export default Vue.extend({ this.infoModal.record = null; }, createQuery(): Query | ComposedQuery | undefined { - const queries: (Query|ComposedQuery)[] = []; + const queries: Query[] = []; if (this.sample) { queries.push(this.createSampleQuery()); } @@ -360,6 +361,9 @@ export default Vue.extend({ if (this.isSamplesContainInheritance && this.filterRecordsByInheritance) { queries.push(this.createSampleInheritanceQuery()); } + if (this.isSamplesContainDenovo && this.filterRecordsByDenovo) { + queries.push(this.createSampleDenovoQuery()); + } // todo: translate ctx filter param to query let query: Query | ComposedQuery | undefined; @@ -382,22 +386,19 @@ export default Vue.extend({ args: ['het', 'hom_a', 'part'] }; }, - createSampleInheritanceQuery(): ComposedQuery { + createSampleInheritanceQuery(): Query { return { - operator: 'or', - args: [ - { - selector: ['s', this.sample.index, 'f', 'VIM'], - operator: '==', - args: "1" - }, - { - selector: ['s', this.sample.index, 'f', 'VID'], - operator: '==', - args: 1 - } - ], - }; + selector: ['s', this.sample.index, 'f', 'VIM'], + operator: '==', + args: "1" + }; + }, + createSampleDenovoQuery(): Query { + return { + selector: ['s', this.sample.index, 'f', 'VID'], + operator:'==', + args:1 + }; }, hasSamplePhenotypes(): boolean { return this.selectedSamplePhenotypes !== null && this.selectedSamplePhenotypes.page.totalElements > 0; @@ -509,6 +510,10 @@ export default Vue.extend({ '$store.state.filterRecordsByInheritance'() { this.page.currentPage = 1; (this.$refs.table as BTable).refresh(); + }, + '$store.state.filterRecordsByDenovo'() { + this.page.currentPage = 1; + (this.$refs.table as BTable).refresh(); } } }); diff --git a/src/components/RecordTableControls.vue b/src/components/RecordTableControls.vue index e5344b90..c4b27d2b 100644 --- a/src/components/RecordTableControls.vue +++ b/src/components/RecordTableControls.vue @@ -12,6 +12,12 @@ @change="this.setFilterRecordsByInheritance" >{{ $t('matchInheritance') }} + {{ $t('matchDenovo') }} + @@ -21,11 +27,11 @@ import {mapActions, mapGetters, mapState} from 'vuex'; export default Vue.extend({ computed: { - ...mapGetters(['isRecordsContainPhenotypes', 'isSamplesContainInheritance']), - ...mapState(['filterRecordsByPhenotype', 'filterRecordsByInheritance']) + ...mapGetters(['isRecordsContainPhenotypes', 'isSamplesContainInheritance', 'isSamplesContainDenovo']), + ...mapState(['filterRecordsByPhenotype', 'filterRecordsByInheritance', 'filterRecordsByDenovo']) }, methods: { - ...mapActions(['setFilterRecordsByPhenotype', 'setFilterRecordsByInheritance']) + ...mapActions(['setFilterRecordsByPhenotype', 'setFilterRecordsByInheritance', 'setFilterRecordsByDenovo']) } }); diff --git a/src/locales/en.json b/src/locales/en.json index a76d23a8..84655aeb 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -54,5 +54,6 @@ "save": "Save...", "exit": "Exit", "matchPhenotype": "Only display records that match sample phenotype(s)", - "matchInheritance": "Only display records that match inheritance or are denovo" + "matchInheritance": "Only display records that match inheritance", + "matchDenovo": "Only display records that are denovo" } \ No newline at end of file diff --git a/src/store/actions.ts b/src/store/actions.ts index 562590e5..70f51c24 100644 --- a/src/store/actions.ts +++ b/src/store/actions.ts @@ -86,6 +86,9 @@ export default { }, setFilterRecordsByInheritance({ commit }: ActionContext, value: boolean) { commit('setFilterRecordsByInheritance', value); + }, + setFilterRecordsByDenovo({ commit }: ActionContext, value: boolean) { + commit('setFilterRecordsByDenovo', value); } }; diff --git a/src/store/getters.ts b/src/store/getters.ts index 98bd0bc9..a0199c77 100644 --- a/src/store/getters.ts +++ b/src/store/getters.ts @@ -132,5 +132,18 @@ export default { hasSampleInheritance = vimFormat !== undefined } return hasSampleInheritance; + }, + /** + * Returns whether records contain denovo information. + */ + isSamplesContainDenovo: (state: State): boolean => { + let hasSampleInheritance; + if (state.metadata === null) { + hasSampleInheritance = false; + } else { + const vimFormat = state.metadata.records.format.find(item => item.id === 'VID'); + hasSampleInheritance = vimFormat !== undefined + } + return hasSampleInheritance; } }; diff --git a/src/store/mutations.ts b/src/store/mutations.ts index 8a27ec03..72441b87 100644 --- a/src/store/mutations.ts +++ b/src/store/mutations.ts @@ -35,5 +35,8 @@ export default { }, setFilterRecordsByInheritance(state: State, value: boolean) { state.filterRecordsByInheritance = value; + }, + setFilterRecordsByDenovo(state: State, value: boolean) { + state.filterRecordsByDenovo = value; } }; diff --git a/src/store/state.ts b/src/store/state.ts index 44e2f416..cf2ce17f 100644 --- a/src/store/state.ts +++ b/src/store/state.ts @@ -9,6 +9,7 @@ const state: State = { records: null, filterRecordsByPhenotype: true, filterRecordsByInheritance: false, + filterRecordsByDenovo: false, annotations: null }; diff --git a/src/types/State.ts b/src/types/State.ts index 93aa92f1..4ccf666a 100644 --- a/src/types/State.ts +++ b/src/types/State.ts @@ -11,5 +11,6 @@ export type State = { records: PagedItems | null; filterRecordsByPhenotype: boolean; filterRecordsByInheritance: boolean; + filterRecordsByDenovo: boolean; annotations: Annotations | null; }; diff --git a/src/views/Samples.vue b/src/views/Samples.vue index 2c992736..5cd4844b 100644 --- a/src/views/Samples.vue +++ b/src/views/Samples.vue @@ -20,7 +20,7 @@ export default Vue.extend({ components: { SampleNavigation, SampleReport }, computed: { ...mapGetters(['samples', 'getSampleById']), - ...mapState(['selectedSample', 'selectedSamplePhenotypes', 'selectedSampleInheritance']) + ...mapState(['selectedSample', 'selectedSamplePhenotypes', 'selectedSampleInheritance', 'selectedSampleDenovo']) }, methods: { ...mapActions(['loadMetadata', 'loadSamples', 'selectSample']), diff --git a/tests/unit/store/actions.spec.ts b/tests/unit/store/actions.spec.ts index 32105ad8..ca6d281c 100644 --- a/tests/unit/store/actions.spec.ts +++ b/tests/unit/store/actions.spec.ts @@ -143,3 +143,10 @@ test('set filter records by inheritance', async done => { expect(commit).toHaveBeenCalledWith('setFilterRecordsByInheritance', true); done(); }); + +test('set filter records by denovo status', async done => { + const commit = jest.fn() as Commit; + actions.setFilterRecordsByDenovo({ commit } as ActionContext, true); + expect(commit).toHaveBeenCalledWith('setFilterRecordsByDenovo', true); + done(); +}); \ No newline at end of file diff --git a/tests/unit/store/getters.spec.ts b/tests/unit/store/getters.spec.ts index d1958c09..d92a9c40 100644 --- a/tests/unit/store/getters.spec.ts +++ b/tests/unit/store/getters.spec.ts @@ -447,4 +447,48 @@ test('whether records contain inheritance matching information. null.', () => { metadata: metadata }; expect(getters.isSamplesContainInheritance(testState)).toEqual(false); +}); + +test('whether records contain denovo information. true.', () => { + const formatMetadata = mock(); + formatMetadata.id = 'VID'; + + const recordsMetadata = mock(); + recordsMetadata.format = [formatMetadata]; + + const metadata = mock(); + metadata.records = recordsMetadata; + + const testState: State = { + ...initialState, + metadata: metadata + }; + expect(getters.isSamplesContainDenovo(testState)).toEqual(true); +}); + +test('whether records contain denovo information. false.', () => { + const formatMetadata = mock(); + formatMetadata.id = 'OTHER'; + + const recordsMetadata = mock(); + recordsMetadata.format = [formatMetadata]; + + const metadata = mock(); + metadata.records = recordsMetadata; + + const testState: State = { + ...initialState, + metadata: metadata + }; + expect(getters.isSamplesContainDenovo(testState)).toEqual(false); +}); + +test('whether records contain denovo information. null.', () => { + const metadata =null; + + const testState: State = { + ...initialState, + metadata: metadata + }; + expect(getters.isSamplesContainDenovo(testState)).toEqual(false); }); \ No newline at end of file diff --git a/tests/unit/store/mutations.spec.ts b/tests/unit/store/mutations.spec.ts index 2fd302f9..38177219 100644 --- a/tests/unit/store/mutations.spec.ts +++ b/tests/unit/store/mutations.spec.ts @@ -58,3 +58,9 @@ test('set filter records by inheritance', () => { mutations.setFilterRecordsByInheritance(testState, false); expect(testState.filterRecordsByInheritance).toBe(false); }); + +test('set filter records by denovo', () => { + const testState: State = { ...initialState }; + mutations.setFilterRecordsByDenovo(testState, false); + expect(testState.filterRecordsByDenovo).toBe(false); +}); From bce199b2a009c87344d67ed9449ab7e5860642ed Mon Sep 17 00:00:00 2001 From: bartcharbon Date: Mon, 7 Dec 2020 13:14:50 +0100 Subject: [PATCH 3/3] Update voor vip-inheritance-matcher issue fix #5 --- src/components/RecordTable.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/RecordTable.vue b/src/components/RecordTable.vue index 83004e86..92ed5c58 100644 --- a/src/components/RecordTable.vue +++ b/src/components/RecordTable.vue @@ -390,7 +390,7 @@ export default Vue.extend({ return { selector: ['s', this.sample.index, 'f', 'VIM'], operator: '==', - args: "1" + args:1 }; }, createSampleDenovoQuery(): Query {