@genspectrum/dashboard-components 1.5.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/custom-elements.json +37 -2
- package/dist/{NumberRangeFilterChangedEvent-CQ32Qy8D.js → NumberRangeFilterChangedEvent-BnPI-Asz.js} +17 -3
- package/dist/NumberRangeFilterChangedEvent-BnPI-Asz.js.map +1 -0
- package/dist/assets/{mutationOverTimeWorker-BJ_P2T8Y.js.map → mutationOverTimeWorker-DPS3tmOd.js.map} +1 -1
- package/dist/components.d.ts +50 -29
- package/dist/components.js +155 -61
- package/dist/components.js.map +1 -1
- package/dist/util.d.ts +53 -29
- package/dist/util.js +2 -1
- package/package.json +1 -1
- package/src/preact/genomeViewer/CDSPlot.tsx +13 -2
- package/src/preact/genomeViewer/loadGff3.ts +6 -0
- package/src/preact/mutationFilter/mutation-filter-info.tsx +2 -2
- package/src/preact/mutationFilter/mutation-filter.stories.tsx +72 -1
- package/src/preact/mutationFilter/mutation-filter.tsx +75 -37
- package/src/preact/mutationFilter/parseAndValidateMutation.ts +11 -11
- package/src/preact/mutationFilter/parseMutation.spec.ts +32 -22
- package/src/preact/mutationsOverTime/mutations-over-time.tsx +7 -4
- package/src/query/queryMutationsOverTime.ts +1 -1
- package/src/types.ts +17 -1
- package/src/utilEntrypoint.ts +4 -0
- package/src/utils/mutations.spec.ts +32 -0
- package/src/utils/mutations.ts +57 -10
- package/src/web-components/input/gs-mutation-filter.stories.ts +30 -1
- package/src/web-components/input/gs-mutation-filter.tsx +25 -2
- package/standalone-bundle/assets/{mutationOverTimeWorker-CkeGpKWp.js.map → mutationOverTimeWorker-Dp-A14AP.js.map} +1 -1
- package/standalone-bundle/dashboard-components.js +8187 -8116
- package/standalone-bundle/dashboard-components.js.map +1 -1
- package/dist/NumberRangeFilterChangedEvent-CQ32Qy8D.js.map +0 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
|
|
3
3
|
import { parseAndValidateMutation } from './parseAndValidateMutation';
|
|
4
|
+
import { mutationType } from '../../types';
|
|
4
5
|
import { DeletionClass, InsertionClass, SubstitutionClass } from '../../utils/mutations';
|
|
5
6
|
|
|
6
7
|
describe('parseMutation', () => {
|
|
@@ -28,32 +29,32 @@ describe('parseMutation', () => {
|
|
|
28
29
|
{
|
|
29
30
|
name: 'should parse nucleotide insertions',
|
|
30
31
|
input: 'ins_3:ACGT',
|
|
31
|
-
expected: { type:
|
|
32
|
+
expected: { type: mutationType.nucleotideInsertions, value: new InsertionClass(undefined, 3, 'ACGT') },
|
|
32
33
|
},
|
|
33
34
|
{
|
|
34
35
|
name: 'should parse amino acid insertions',
|
|
35
36
|
input: 'ins_gene1:3:ACGT',
|
|
36
|
-
expected: { type:
|
|
37
|
+
expected: { type: mutationType.aminoAcidInsertions, value: new InsertionClass('gene1', 3, 'ACGT') },
|
|
37
38
|
},
|
|
38
39
|
{
|
|
39
40
|
name: 'should parse amino acid insertions in all upper case',
|
|
40
41
|
input: 'INS_GENE1:3:ACGT',
|
|
41
|
-
expected: { type:
|
|
42
|
+
expected: { type: mutationType.aminoAcidInsertions, value: new InsertionClass('GENE1', 3, 'ACGT') },
|
|
42
43
|
},
|
|
43
44
|
{
|
|
44
45
|
name: 'should parse amino acid insertions in all lower case',
|
|
45
46
|
input: 'ins_gene1:3:acgt',
|
|
46
|
-
expected: { type:
|
|
47
|
+
expected: { type: mutationType.aminoAcidInsertions, value: new InsertionClass('gene1', 3, 'acgt') },
|
|
47
48
|
},
|
|
48
49
|
{
|
|
49
50
|
name: 'should parse amino acid insertion with LAPIS-style wildcard',
|
|
50
51
|
input: 'ins_gene1:3:?AC?GT',
|
|
51
|
-
expected: { type:
|
|
52
|
+
expected: { type: mutationType.aminoAcidInsertions, value: new InsertionClass('gene1', 3, '?AC?GT') },
|
|
52
53
|
},
|
|
53
54
|
{
|
|
54
55
|
name: 'should parse amino acid insertion with SILO-style wildcard',
|
|
55
56
|
input: 'ins_gene1:3:.*AC.*GT',
|
|
56
|
-
expected: { type:
|
|
57
|
+
expected: { type: mutationType.aminoAcidInsertions, value: new InsertionClass('gene1', 3, '.*AC.*GT') },
|
|
57
58
|
},
|
|
58
59
|
{
|
|
59
60
|
name: 'should return null for insertion with segment not in reference genome',
|
|
@@ -71,42 +72,42 @@ describe('parseMutation', () => {
|
|
|
71
72
|
{
|
|
72
73
|
name: 'should parse nucleotide deletion in single segmented reference genome, when no segment is given',
|
|
73
74
|
input: 'A3-',
|
|
74
|
-
expected: { type:
|
|
75
|
+
expected: { type: mutationType.nucleotideMutations, value: new DeletionClass(undefined, 'A', 3) },
|
|
75
76
|
},
|
|
76
77
|
{
|
|
77
78
|
name: 'should parse nucleotide deletion without valueAtReference when no segment is given',
|
|
78
79
|
input: '3-',
|
|
79
|
-
expected: { type:
|
|
80
|
+
expected: { type: mutationType.nucleotideMutations, value: new DeletionClass(undefined, undefined, 3) },
|
|
80
81
|
},
|
|
81
82
|
{
|
|
82
83
|
name: 'should parse nucleotide deletion',
|
|
83
84
|
input: 'nuc1:A3-',
|
|
84
|
-
expected: { type:
|
|
85
|
+
expected: { type: mutationType.nucleotideMutations, value: new DeletionClass('nuc1', 'A', 3) },
|
|
85
86
|
},
|
|
86
87
|
{
|
|
87
88
|
name: 'should parse nucleotide deletion without valueAtReference',
|
|
88
89
|
input: 'nuc1:3-',
|
|
89
|
-
expected: { type:
|
|
90
|
+
expected: { type: mutationType.nucleotideMutations, value: new DeletionClass('nuc1', undefined, 3) },
|
|
90
91
|
},
|
|
91
92
|
{
|
|
92
93
|
name: 'should parse amino acid deletion',
|
|
93
94
|
input: 'gene1:A3-',
|
|
94
|
-
expected: { type:
|
|
95
|
+
expected: { type: mutationType.aminoAcidMutations, value: new DeletionClass('gene1', 'A', 3) },
|
|
95
96
|
},
|
|
96
97
|
{
|
|
97
98
|
name: 'should parse amino acid deletion in all upper case',
|
|
98
99
|
input: 'GENE1:A3-',
|
|
99
|
-
expected: { type:
|
|
100
|
+
expected: { type: mutationType.aminoAcidMutations, value: new DeletionClass('GENE1', 'A', 3) },
|
|
100
101
|
},
|
|
101
102
|
{
|
|
102
103
|
name: 'should parse amino acid deletion in all lower case',
|
|
103
104
|
input: 'gene1:a3-',
|
|
104
|
-
expected: { type:
|
|
105
|
+
expected: { type: mutationType.aminoAcidMutations, value: new DeletionClass('gene1', 'a', 3) },
|
|
105
106
|
},
|
|
106
107
|
{
|
|
107
108
|
name: 'should parse amino acid deletion without valueAtReference',
|
|
108
109
|
input: 'gene1:3-',
|
|
109
|
-
expected: { type:
|
|
110
|
+
expected: { type: mutationType.aminoAcidMutations, value: new DeletionClass('gene1', undefined, 3) },
|
|
110
111
|
},
|
|
111
112
|
{
|
|
112
113
|
name: 'should return null for deletion with segment not in reference genome',
|
|
@@ -123,45 +124,54 @@ describe('parseMutation', () => {
|
|
|
123
124
|
{
|
|
124
125
|
name: 'should parse nucleotide substitution in single segmented reference genome, when no segment is given',
|
|
125
126
|
input: 'A3T',
|
|
126
|
-
expected: {
|
|
127
|
+
expected: {
|
|
128
|
+
type: mutationType.nucleotideMutations,
|
|
129
|
+
value: new SubstitutionClass(undefined, 'A', 'T', 3),
|
|
130
|
+
},
|
|
127
131
|
},
|
|
128
132
|
{
|
|
129
133
|
name: 'should parse substitution without valueAtReference',
|
|
130
134
|
input: '3T',
|
|
131
|
-
expected: {
|
|
135
|
+
expected: {
|
|
136
|
+
type: mutationType.nucleotideMutations,
|
|
137
|
+
value: new SubstitutionClass(undefined, undefined, 'T', 3),
|
|
138
|
+
},
|
|
132
139
|
},
|
|
133
140
|
{
|
|
134
141
|
name: 'should parse substitution with neither valueAtReference not substitutionValue',
|
|
135
142
|
input: '3',
|
|
136
143
|
expected: {
|
|
137
|
-
type:
|
|
144
|
+
type: mutationType.nucleotideMutations,
|
|
138
145
|
value: new SubstitutionClass(undefined, undefined, undefined, 3),
|
|
139
146
|
},
|
|
140
147
|
},
|
|
141
148
|
{
|
|
142
149
|
name: 'should parse a "no mutation" substitution',
|
|
143
150
|
input: '3.',
|
|
144
|
-
expected: {
|
|
151
|
+
expected: {
|
|
152
|
+
type: mutationType.nucleotideMutations,
|
|
153
|
+
value: new SubstitutionClass(undefined, undefined, '.', 3),
|
|
154
|
+
},
|
|
145
155
|
},
|
|
146
156
|
{
|
|
147
157
|
name: 'should parse nucleotide substitution',
|
|
148
158
|
input: 'nuc1:A3T',
|
|
149
|
-
expected: { type:
|
|
159
|
+
expected: { type: mutationType.nucleotideMutations, value: new SubstitutionClass('nuc1', 'A', 'T', 3) },
|
|
150
160
|
},
|
|
151
161
|
{
|
|
152
162
|
name: 'should parse amino acid substitution',
|
|
153
163
|
input: 'gene1:A3T',
|
|
154
|
-
expected: { type:
|
|
164
|
+
expected: { type: mutationType.aminoAcidMutations, value: new SubstitutionClass('gene1', 'A', 'T', 3) },
|
|
155
165
|
},
|
|
156
166
|
{
|
|
157
167
|
name: 'should parse amino acid substitution in all upper case',
|
|
158
168
|
input: 'GENE1:A3T',
|
|
159
|
-
expected: { type:
|
|
169
|
+
expected: { type: mutationType.aminoAcidMutations, value: new SubstitutionClass('GENE1', 'A', 'T', 3) },
|
|
160
170
|
},
|
|
161
171
|
{
|
|
162
172
|
name: 'should parse amino acid substitution in all lower case',
|
|
163
173
|
input: 'gene1:a3t',
|
|
164
|
-
expected: { type:
|
|
174
|
+
expected: { type: mutationType.aminoAcidMutations, value: new SubstitutionClass('gene1', 'a', 't', 3) },
|
|
165
175
|
},
|
|
166
176
|
{
|
|
167
177
|
name: 'should return null for substitution with segment not in reference genome',
|
|
@@ -47,6 +47,12 @@ import { useWebWorker } from '../webWorkers/useWebWorker';
|
|
|
47
47
|
const mutationsOverTimeViewSchema = z.literal(views.grid);
|
|
48
48
|
export type MutationsOverTimeView = z.infer<typeof mutationsOverTimeViewSchema>;
|
|
49
49
|
|
|
50
|
+
const meanProportionIntervalSchema = z.object({
|
|
51
|
+
min: z.number().min(0).max(1),
|
|
52
|
+
max: z.number().min(0).max(1),
|
|
53
|
+
});
|
|
54
|
+
export type MeanProportionInterval = z.infer<typeof meanProportionIntervalSchema>;
|
|
55
|
+
|
|
50
56
|
const mutationOverTimeSchema = z.object({
|
|
51
57
|
lapisFilter: lapisFilterSchema,
|
|
52
58
|
sequenceType: sequenceTypeSchema,
|
|
@@ -55,10 +61,7 @@ const mutationOverTimeSchema = z.object({
|
|
|
55
61
|
lapisDateField: z.string().min(1),
|
|
56
62
|
useNewEndpoint: z.boolean().optional(),
|
|
57
63
|
displayMutations: displayMutationsSchema.optional(),
|
|
58
|
-
initialMeanProportionInterval:
|
|
59
|
-
min: z.number().min(0).max(1),
|
|
60
|
-
max: z.number().min(0).max(1),
|
|
61
|
-
}),
|
|
64
|
+
initialMeanProportionInterval: meanProportionIntervalSchema,
|
|
62
65
|
hideGaps: z.boolean().optional(),
|
|
63
66
|
width: z.string(),
|
|
64
67
|
height: z.string().optional(),
|
|
@@ -326,7 +326,7 @@ function parseMutationCode(code: string): SubstitutionClass | DeletionClass {
|
|
|
326
326
|
if (maybeSubstitution) {
|
|
327
327
|
return maybeSubstitution;
|
|
328
328
|
}
|
|
329
|
-
throw Error(
|
|
329
|
+
throw Error(`Given code is not valid: ${code}`);
|
|
330
330
|
}
|
|
331
331
|
|
|
332
332
|
/**
|
package/src/types.ts
CHANGED
|
@@ -44,7 +44,7 @@ export type SequenceType = z.infer<typeof sequenceTypeSchema>;
|
|
|
44
44
|
|
|
45
45
|
export type SubstitutionOrDeletion = 'substitution' | 'deletion';
|
|
46
46
|
|
|
47
|
-
export type
|
|
47
|
+
export type SubstitutionOrDeletionOrInsertion = SubstitutionOrDeletion | 'insertion';
|
|
48
48
|
|
|
49
49
|
export type SubstitutionEntry<T extends Substitution = SubstitutionClass> = {
|
|
50
50
|
type: 'substitution';
|
|
@@ -79,3 +79,19 @@ export const views = {
|
|
|
79
79
|
bubble: 'bubble',
|
|
80
80
|
map: 'map',
|
|
81
81
|
} as const;
|
|
82
|
+
|
|
83
|
+
export const mutationType = {
|
|
84
|
+
nucleotideMutations: 'nucleotideMutations',
|
|
85
|
+
nucleotideInsertions: 'nucleotideInsertions',
|
|
86
|
+
aminoAcidMutations: 'aminoAcidMutations',
|
|
87
|
+
aminoAcidInsertions: 'aminoAcidInsertions',
|
|
88
|
+
} as const;
|
|
89
|
+
|
|
90
|
+
export const mutationTypeSchema = z.enum([
|
|
91
|
+
mutationType.nucleotideMutations,
|
|
92
|
+
mutationType.nucleotideInsertions,
|
|
93
|
+
mutationType.aminoAcidMutations,
|
|
94
|
+
mutationType.aminoAcidInsertions,
|
|
95
|
+
]);
|
|
96
|
+
|
|
97
|
+
export type MutationType = z.infer<typeof mutationTypeSchema>;
|
package/src/utilEntrypoint.ts
CHANGED
|
@@ -12,6 +12,8 @@ export {
|
|
|
12
12
|
views,
|
|
13
13
|
type TemporalGranularity,
|
|
14
14
|
type MutationsFilter,
|
|
15
|
+
mutationType,
|
|
16
|
+
type MutationType,
|
|
15
17
|
} from './types';
|
|
16
18
|
|
|
17
19
|
export type { MutationComparisonView, MutationComparisonProps } from './preact/mutationComparison/mutation-comparison';
|
|
@@ -47,3 +49,5 @@ export {
|
|
|
47
49
|
NumberRangeFilterChangedEvent,
|
|
48
50
|
NumberRangeValueChangedEvent,
|
|
49
51
|
} from './preact/numberRangeFilter/NumberRangeFilterChangedEvent';
|
|
52
|
+
|
|
53
|
+
export { type MeanProportionInterval } from './preact/mutationsOverTime/mutations-over-time';
|
|
@@ -6,6 +6,18 @@ describe('SubstitutionClass', () => {
|
|
|
6
6
|
it('should be parsed from string', () => {
|
|
7
7
|
expect(SubstitutionClass.parse('A1T')).deep.equal(new SubstitutionClass(undefined, 'A', 'T', 1));
|
|
8
8
|
expect(SubstitutionClass.parse('seg1:A1T')).deep.equal(new SubstitutionClass('seg1', 'A', 'T', 1));
|
|
9
|
+
expect(SubstitutionClass.parse('1')).deep.equal(new SubstitutionClass(undefined, undefined, undefined, 1));
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should be parsed with stop codons', () => {
|
|
13
|
+
expect(SubstitutionClass.parse('S:*1247T')).deep.equal(new SubstitutionClass('S', '*', 'T', 1247));
|
|
14
|
+
expect(SubstitutionClass.parse('S:T1247*')).deep.equal(new SubstitutionClass('S', 'T', '*', 1247));
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('invalid substitution strings should return null', () => {
|
|
18
|
+
expect(SubstitutionClass.parse('A1-')).to.equal(null);
|
|
19
|
+
expect(SubstitutionClass.parse('ins_1:A')).to.equal(null);
|
|
20
|
+
expect(SubstitutionClass.parse('E34Q')).to.equal(null);
|
|
9
21
|
});
|
|
10
22
|
|
|
11
23
|
it('should render to string correctly', () => {
|
|
@@ -30,6 +42,16 @@ describe('DeletionClass', () => {
|
|
|
30
42
|
expect(DeletionClass.parse('seg1:A1-')).deep.equal(new DeletionClass('seg1', 'A', 1));
|
|
31
43
|
});
|
|
32
44
|
|
|
45
|
+
it('should be parsed with stop codons', () => {
|
|
46
|
+
expect(DeletionClass.parse('seg1:*1-')).deep.equal(new DeletionClass('seg1', '*', 1));
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('invalid deletion strings should return null', () => {
|
|
50
|
+
expect(DeletionClass.parse('seg1:A1T')).to.equal(null);
|
|
51
|
+
expect(DeletionClass.parse('ins_1:A')).to.equal(null);
|
|
52
|
+
expect(DeletionClass.parse('E34-')).to.equal(null);
|
|
53
|
+
});
|
|
54
|
+
|
|
33
55
|
it('should render to string correctly', () => {
|
|
34
56
|
const substitutions = [
|
|
35
57
|
{
|
|
@@ -61,4 +83,14 @@ describe('InsertionClass', () => {
|
|
|
61
83
|
expect(InsertionClass.parse('ins_geNe1:1:A')).deep.equal(new InsertionClass('geNe1', 1, 'A'));
|
|
62
84
|
expect(InsertionClass.parse('ins_1:aA')).deep.equal(new InsertionClass(undefined, 1, 'aA'));
|
|
63
85
|
});
|
|
86
|
+
|
|
87
|
+
it('should be parsed with stop codon insertion', () => {
|
|
88
|
+
expect(InsertionClass.parse('ins_134:*')).deep.equal(new InsertionClass(undefined, 134, '*'));
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('invalid insertion strings should return null', () => {
|
|
92
|
+
expect(InsertionClass.parse('A1-')).to.equal(null);
|
|
93
|
+
expect(InsertionClass.parse('seg1:A1T')).to.equal(null);
|
|
94
|
+
expect(InsertionClass.parse('ins_34:Q')).to.equal(null);
|
|
95
|
+
});
|
|
64
96
|
});
|
package/src/utils/mutations.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type SubstitutionOrDeletionOrInsertion, type SequenceType } from '../types';
|
|
2
2
|
|
|
3
3
|
export interface Mutation {
|
|
4
4
|
readonly position: number;
|
|
5
5
|
readonly code: string;
|
|
6
|
-
readonly type:
|
|
6
|
+
readonly type: SubstitutionOrDeletionOrInsertion;
|
|
7
7
|
readonly segment?: string;
|
|
8
8
|
}
|
|
9
9
|
|
|
@@ -13,8 +13,28 @@ export interface MutationClass extends Mutation {
|
|
|
13
13
|
toString(): string;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
// Allowed IUPAC characters: https://www.bioinformatics.org/sms/iupac.html
|
|
17
|
+
const nucleotideChars = 'ACGTRYKMSWBDHVN';
|
|
18
|
+
const aminoAcidChars = 'ACDEFGHIKLMNPQRSTVWY';
|
|
19
|
+
|
|
20
|
+
function segmentPart(type: 'nucleotide' | 'aminoAcid') {
|
|
21
|
+
return type === 'aminoAcid' ? `(?<segment>[A-Z0-9_-]+):` : `((?<segment>[A-Z0-9_-]+)(?=:):)?`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function buildSubstitutionRegex(type: 'nucleotide' | 'aminoAcid') {
|
|
25
|
+
const chars = type === 'nucleotide' ? nucleotideChars : aminoAcidChars;
|
|
26
|
+
|
|
27
|
+
return new RegExp(
|
|
28
|
+
`^${segmentPart(type)}` +
|
|
29
|
+
`(?<valueAtReference>[${chars}*])?` +
|
|
30
|
+
`(?<position>\\d+)` +
|
|
31
|
+
`(?<substitutionValue>[${chars}.*])?$`,
|
|
32
|
+
'i',
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const nucleotideSubstitutionRegex = buildSubstitutionRegex('nucleotide');
|
|
37
|
+
const aminoAcidSubstitutionRegex = buildSubstitutionRegex('aminoAcid');
|
|
18
38
|
|
|
19
39
|
export interface Substitution extends Mutation {
|
|
20
40
|
type: 'substitution';
|
|
@@ -55,7 +75,9 @@ export class SubstitutionClass implements MutationClass, Substitution {
|
|
|
55
75
|
}
|
|
56
76
|
|
|
57
77
|
static parse(mutationStr: string): SubstitutionClass | null {
|
|
58
|
-
const
|
|
78
|
+
const matchNucleotide = nucleotideSubstitutionRegex.exec(mutationStr);
|
|
79
|
+
const matchAminoAcid = aminoAcidSubstitutionRegex.exec(mutationStr);
|
|
80
|
+
const match = matchNucleotide ?? matchAminoAcid;
|
|
59
81
|
if (match?.groups === undefined) {
|
|
60
82
|
return null;
|
|
61
83
|
}
|
|
@@ -68,7 +90,17 @@ export class SubstitutionClass implements MutationClass, Substitution {
|
|
|
68
90
|
}
|
|
69
91
|
}
|
|
70
92
|
|
|
71
|
-
|
|
93
|
+
function buildDeletionRegex(type: 'nucleotide' | 'aminoAcid') {
|
|
94
|
+
const chars = type === 'nucleotide' ? nucleotideChars : aminoAcidChars;
|
|
95
|
+
|
|
96
|
+
return new RegExp(
|
|
97
|
+
`^${segmentPart(type)}` + `(?<valueAtReference>[${chars}*])?` + `(?<position>\\d+)` + `(-)$`,
|
|
98
|
+
'i',
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const nucleotideDeletionRegex = buildDeletionRegex('nucleotide');
|
|
103
|
+
const aminoAcidDeletionRegex = buildDeletionRegex('aminoAcid');
|
|
72
104
|
|
|
73
105
|
export interface Deletion extends Mutation {
|
|
74
106
|
type: 'deletion';
|
|
@@ -105,7 +137,9 @@ export class DeletionClass implements MutationClass, Deletion {
|
|
|
105
137
|
}
|
|
106
138
|
|
|
107
139
|
static parse(mutationStr: string): DeletionClass | null {
|
|
108
|
-
const
|
|
140
|
+
const matchNucleotide = nucleotideDeletionRegex.exec(mutationStr);
|
|
141
|
+
const matchAminoAcid = aminoAcidDeletionRegex.exec(mutationStr);
|
|
142
|
+
const match = matchNucleotide ?? matchAminoAcid;
|
|
109
143
|
if (match?.groups === undefined) {
|
|
110
144
|
return null;
|
|
111
145
|
}
|
|
@@ -118,8 +152,19 @@ export class DeletionClass implements MutationClass, Deletion {
|
|
|
118
152
|
}
|
|
119
153
|
}
|
|
120
154
|
|
|
121
|
-
|
|
122
|
-
|
|
155
|
+
function buildInsertionRegex(type: 'nucleotide' | 'aminoAcid') {
|
|
156
|
+
const chars = type === 'nucleotide' ? nucleotideChars : aminoAcidChars;
|
|
157
|
+
|
|
158
|
+
const wildcardToken = `(?:\\.\\*)`;
|
|
159
|
+
|
|
160
|
+
return new RegExp(
|
|
161
|
+
`^ins_${segmentPart(type)}(?<position>\\d+):(?<insertedSymbols>(?:[${chars}?*]|${wildcardToken})+)$`,
|
|
162
|
+
'i',
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const nucleotideInsertionRegex = buildInsertionRegex('nucleotide');
|
|
167
|
+
const aminoAcidInsertionRegex = buildInsertionRegex('aminoAcid');
|
|
123
168
|
|
|
124
169
|
export interface Insertion extends Mutation {
|
|
125
170
|
type: 'insertion';
|
|
@@ -154,7 +199,9 @@ export class InsertionClass implements MutationClass {
|
|
|
154
199
|
}
|
|
155
200
|
|
|
156
201
|
static parse(mutationStr: string): InsertionClass | null {
|
|
157
|
-
const
|
|
202
|
+
const matchNucleotide = nucleotideInsertionRegex.exec(mutationStr);
|
|
203
|
+
const matchAminoAcid = aminoAcidInsertionRegex.exec(mutationStr);
|
|
204
|
+
const match = matchNucleotide ?? matchAminoAcid;
|
|
158
205
|
if (match?.groups === undefined) {
|
|
159
206
|
return null;
|
|
160
207
|
}
|
|
@@ -7,6 +7,7 @@ import { previewHandles } from '../../../.storybook/preview';
|
|
|
7
7
|
import { LAPIS_URL, REFERENCE_GENOME_ENDPOINT } from '../../constants';
|
|
8
8
|
import '../gs-app';
|
|
9
9
|
import { type MutationFilterProps } from '../../preact/mutationFilter/mutation-filter';
|
|
10
|
+
import { mutationType } from '../../types';
|
|
10
11
|
import { gsEventNames } from '../../utils/gsEventNames';
|
|
11
12
|
import { withinShadowRoot } from '../withinShadowRoot.story';
|
|
12
13
|
import './gs-mutation-filter';
|
|
@@ -38,6 +39,11 @@ const meta: Meta<MutationFilterProps> = {
|
|
|
38
39
|
},
|
|
39
40
|
},
|
|
40
41
|
width: { control: 'text' },
|
|
42
|
+
enabledMutationTypes: {
|
|
43
|
+
control: {
|
|
44
|
+
type: 'object',
|
|
45
|
+
},
|
|
46
|
+
},
|
|
41
47
|
},
|
|
42
48
|
tags: ['autodocs'],
|
|
43
49
|
};
|
|
@@ -48,7 +54,11 @@ const Template: StoryObj<MutationFilterProps> = {
|
|
|
48
54
|
render: (args) => {
|
|
49
55
|
return html` <gs-app lapis="${LAPIS_URL}">
|
|
50
56
|
<div class="max-w-(--breakpoint-lg)">
|
|
51
|
-
<gs-mutation-filter
|
|
57
|
+
<gs-mutation-filter
|
|
58
|
+
.initialValue=${args.initialValue}
|
|
59
|
+
.width=${args.width}
|
|
60
|
+
.enabledMutationTypes=${args.enabledMutationTypes}
|
|
61
|
+
></gs-mutation-filter>
|
|
52
62
|
</div>
|
|
53
63
|
</gs-app>`;
|
|
54
64
|
},
|
|
@@ -104,6 +114,25 @@ export const FiresFilterChangedEvent: StoryObj<MutationFilterProps> = {
|
|
|
104
114
|
},
|
|
105
115
|
};
|
|
106
116
|
|
|
117
|
+
export const RestrictEnabledMutationTypes: StoryObj<MutationFilterProps> = {
|
|
118
|
+
...Template,
|
|
119
|
+
args: {
|
|
120
|
+
...Template.args,
|
|
121
|
+
enabledMutationTypes: [mutationType.nucleotideMutations, mutationType.aminoAcidMutations],
|
|
122
|
+
},
|
|
123
|
+
play: async ({ canvasElement }) => {
|
|
124
|
+
const canvas = await withinShadowRoot(canvasElement, 'gs-mutation-filter');
|
|
125
|
+
|
|
126
|
+
const inputField = () => canvas.getByPlaceholderText('Enter a mutation', { exact: false });
|
|
127
|
+
|
|
128
|
+
await waitFor(async () => {
|
|
129
|
+
const placeholderText = inputField().getAttribute('placeholder');
|
|
130
|
+
|
|
131
|
+
await expect(placeholderText).toEqual('Enter a mutation (e.g. 23T, E:57Q)');
|
|
132
|
+
});
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
|
|
107
136
|
export const MultiSegmentedReferenceGenomes: StoryObj<MutationFilterProps> = {
|
|
108
137
|
...Template,
|
|
109
138
|
args: {
|
|
@@ -3,7 +3,7 @@ import type { DetailedHTMLProps, HTMLAttributes } from 'react';
|
|
|
3
3
|
|
|
4
4
|
import { ReferenceGenomesAwaiter } from '../../preact/components/ReferenceGenomesAwaiter';
|
|
5
5
|
import { MutationFilter, type MutationFilterProps } from '../../preact/mutationFilter/mutation-filter';
|
|
6
|
-
import type { MutationsFilter } from '../../types';
|
|
6
|
+
import type { MutationType, MutationsFilter } from '../../types';
|
|
7
7
|
import { type gsEventNames } from '../../utils/gsEventNames';
|
|
8
8
|
import type { Equals, Expect } from '../../utils/typeAssertions';
|
|
9
9
|
import { PreactLitAdapter } from '../PreactLitAdapter';
|
|
@@ -43,6 +43,11 @@ import { PreactLitAdapter } from '../PreactLitAdapter';
|
|
|
43
43
|
*
|
|
44
44
|
* Examples: `ins_S:614:G`, `ins_614:G`
|
|
45
45
|
*
|
|
46
|
+
* ### Enabled mutation types
|
|
47
|
+
*
|
|
48
|
+
* After parsing, the entered mutation/insertion also has to match the enabled mutation types,
|
|
49
|
+
* which are configured with the `enabledMutationTypes` attribute.
|
|
50
|
+
*
|
|
46
51
|
* @fires {CustomEvent<{
|
|
47
52
|
* nucleotideMutations: string[],
|
|
48
53
|
* aminoAcidMutations: string[],
|
|
@@ -81,10 +86,28 @@ export class MutationFilterComponent extends PreactLitAdapter {
|
|
|
81
86
|
@property({ type: String })
|
|
82
87
|
width: string = '100%';
|
|
83
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Which mutation types this input will accept.
|
|
91
|
+
* Any (or all) of the following can be given in a list:
|
|
92
|
+
*
|
|
93
|
+
* - `nucleotideMutations`
|
|
94
|
+
* - `nucleotideInsertions`
|
|
95
|
+
* - `aminoAcidMutations`
|
|
96
|
+
* - `aminoAcidInsertions`
|
|
97
|
+
*
|
|
98
|
+
* By default or if none are given, all types are accepted.
|
|
99
|
+
*/
|
|
100
|
+
@property({ type: Object })
|
|
101
|
+
enabledMutationTypes: MutationType[] | undefined = undefined;
|
|
102
|
+
|
|
84
103
|
override render() {
|
|
85
104
|
return (
|
|
86
105
|
<ReferenceGenomesAwaiter>
|
|
87
|
-
<MutationFilter
|
|
106
|
+
<MutationFilter
|
|
107
|
+
initialValue={this.initialValue}
|
|
108
|
+
width={this.width}
|
|
109
|
+
enabledMutationTypes={this.enabledMutationTypes}
|
|
110
|
+
/>
|
|
88
111
|
</ReferenceGenomesAwaiter>
|
|
89
112
|
);
|
|
90
113
|
}
|