@genspectrum/dashboard-components 1.0.1 → 1.1.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.
@@ -1,7 +1,7 @@
1
1
  import { describe, expect, it } from 'vitest';
2
2
 
3
3
  import { BaseMutationOverTimeDataMap } from './MutationOverTimeData';
4
- import { getFilteredMutationOverTimeData } from './getFilteredMutationsOverTimeData';
4
+ import { getFilteredMutationOverTimeData, type MutationFilter } from './getFilteredMutationsOverTimeData';
5
5
  import { type MutationOverTimeMutationValue } from '../../query/queryMutationsOverTime';
6
6
  import { type DeletionEntry, type SubstitutionEntry } from '../../types';
7
7
  import { type Deletion, type Substitution } from '../../utils/mutations';
@@ -28,7 +28,7 @@ describe('getFilteredMutationOverTimeData', () => {
28
28
  displayedMutationTypes: [],
29
29
  proportionInterval,
30
30
  displayMutations: undefined,
31
- mutationFilterValue: '',
31
+ mutationFilterValue: { textFilter: '', annotationNameFilter: new Set() },
32
32
  sequenceType: 'nucleotide',
33
33
  annotationProvider: () => {
34
34
  return [];
@@ -62,7 +62,7 @@ describe('getFilteredMutationOverTimeData', () => {
62
62
  },
63
63
  ],
64
64
  proportionInterval,
65
- mutationFilterValue: '',
65
+ mutationFilterValue: { textFilter: '', annotationNameFilter: new Set() },
66
66
  sequenceType: 'nucleotide',
67
67
  annotationProvider: () => {
68
68
  return [];
@@ -85,7 +85,7 @@ describe('getFilteredMutationOverTimeData', () => {
85
85
  displayedSegments: [],
86
86
  displayedMutationTypes: [],
87
87
  proportionInterval,
88
- mutationFilterValue: '',
88
+ mutationFilterValue: { textFilter: '', annotationNameFilter: new Set() },
89
89
  sequenceType: 'nucleotide',
90
90
  annotationProvider: () => {
91
91
  return [];
@@ -108,7 +108,7 @@ describe('getFilteredMutationOverTimeData', () => {
108
108
  displayedSegments: [],
109
109
  displayedMutationTypes: [],
110
110
  proportionInterval,
111
- mutationFilterValue: '',
111
+ mutationFilterValue: { textFilter: '', annotationNameFilter: new Set() },
112
112
  sequenceType: 'nucleotide',
113
113
  annotationProvider: () => {
114
114
  return [];
@@ -132,7 +132,7 @@ describe('getFilteredMutationOverTimeData', () => {
132
132
  displayedSegments: [],
133
133
  displayedMutationTypes: [],
134
134
  proportionInterval,
135
- mutationFilterValue: '',
135
+ mutationFilterValue: { textFilter: '', annotationNameFilter: new Set() },
136
136
  sequenceType: 'nucleotide',
137
137
  annotationProvider: () => {
138
138
  return [];
@@ -156,7 +156,7 @@ describe('getFilteredMutationOverTimeData', () => {
156
156
  displayedSegments: [],
157
157
  displayedMutationTypes: [],
158
158
  proportionInterval,
159
- mutationFilterValue: '',
159
+ mutationFilterValue: { textFilter: '', annotationNameFilter: new Set() },
160
160
  sequenceType: 'nucleotide',
161
161
  annotationProvider: () => {
162
162
  return [];
@@ -178,7 +178,7 @@ describe('getFilteredMutationOverTimeData', () => {
178
178
  displayedSegments: [],
179
179
  displayedMutationTypes: [],
180
180
  proportionInterval,
181
- mutationFilterValue: '',
181
+ mutationFilterValue: { textFilter: '', annotationNameFilter: new Set() },
182
182
  sequenceType: 'nucleotide',
183
183
  annotationProvider: () => {
184
184
  return [];
@@ -201,7 +201,7 @@ describe('getFilteredMutationOverTimeData', () => {
201
201
  displayedSegments: [],
202
202
  displayedMutationTypes: [],
203
203
  proportionInterval,
204
- mutationFilterValue: '',
204
+ mutationFilterValue: { textFilter: '', annotationNameFilter: new Set() },
205
205
  sequenceType: 'nucleotide',
206
206
  annotationProvider: () => {
207
207
  return [];
@@ -225,7 +225,7 @@ describe('getFilteredMutationOverTimeData', () => {
225
225
  displayedMutationTypes: [],
226
226
  proportionInterval,
227
227
  displayMutations: [anotherSubstitution.code, someDeletion.code],
228
- mutationFilterValue: '',
228
+ mutationFilterValue: { textFilter: '', annotationNameFilter: new Set() },
229
229
  sequenceType: 'nucleotide',
230
230
  annotationProvider: () => {
231
231
  return [];
@@ -235,7 +235,7 @@ describe('getFilteredMutationOverTimeData', () => {
235
235
  expect(result.getFirstAxisKeys()).to.deep.equal([anotherSubstitution, someDeletion]);
236
236
  });
237
237
 
238
- it('should filter by mutation filter value', () => {
238
+ it('should filter by mutation filter text value', () => {
239
239
  const { data, overallMutationData } = prepareMutationOverTimeData([
240
240
  someSubstitutionEntry,
241
241
  anotherSubstitutionEntry,
@@ -248,7 +248,7 @@ describe('getFilteredMutationOverTimeData', () => {
248
248
  displayedSegments: [],
249
249
  displayedMutationTypes: [],
250
250
  proportionInterval,
251
- mutationFilterValue: '23T',
251
+ mutationFilterValue: { textFilter: '23T', annotationNameFilter: new Set() },
252
252
  sequenceType: 'nucleotide',
253
253
  annotationProvider: () => {
254
254
  return [];
@@ -265,7 +265,7 @@ describe('getFilteredMutationOverTimeData', () => {
265
265
  someDeletionEntry,
266
266
  ]);
267
267
 
268
- const expectFilteredValue = (filterValue: string, annotations: MutationAnnotations) => {
268
+ const expectFilteredValue = (filterValue: MutationFilter, annotations: MutationAnnotations) => {
269
269
  const annotationProvider = getMutationAnnotationsProvider(getMutationAnnotationsContext(annotations));
270
270
 
271
271
  const result = getFilteredMutationOverTimeData({
@@ -283,7 +283,7 @@ describe('getFilteredMutationOverTimeData', () => {
283
283
  };
284
284
 
285
285
  it('with filter value in symbol', () => {
286
- expectFilteredValue('#', [
286
+ expectFilteredValue({ textFilter: '#', annotationNameFilter: new Set() }, [
287
287
  {
288
288
  name: 'Annotation 1',
289
289
  description: 'Description 1',
@@ -294,7 +294,7 @@ describe('getFilteredMutationOverTimeData', () => {
294
294
  });
295
295
 
296
296
  it('with filter value in name', () => {
297
- expectFilteredValue('Annota', [
297
+ expectFilteredValue({ textFilter: 'Annota', annotationNameFilter: new Set() }, [
298
298
  {
299
299
  name: 'Annotation 1 #',
300
300
  description: 'Description 1',
@@ -305,7 +305,18 @@ describe('getFilteredMutationOverTimeData', () => {
305
305
  });
306
306
 
307
307
  it('with filter value in name', () => {
308
- expectFilteredValue('Descr', [
308
+ expectFilteredValue({ textFilter: 'Descr', annotationNameFilter: new Set() }, [
309
+ {
310
+ name: 'Annotation 1',
311
+ description: 'Description 1',
312
+ symbol: '#',
313
+ nucleotideMutations: ['A123T'],
314
+ },
315
+ ]);
316
+ });
317
+
318
+ it('with annotation name filter', () => {
319
+ expectFilteredValue({ textFilter: '', annotationNameFilter: new Set(['Annotation 1']) }, [
309
320
  {
310
321
  name: 'Annotation 1',
311
322
  description: 'Description 1',
@@ -11,6 +11,11 @@ import type { DisplayedSegment } from '../components/segment-selector';
11
11
  export const displayMutationsSchema = z.array(z.string()).min(1);
12
12
  export type DisplayMutations = z.infer<typeof displayMutationsSchema>;
13
13
 
14
+ export type MutationFilter = {
15
+ textFilter: string;
16
+ annotationNameFilter: Set<string>;
17
+ };
18
+
14
19
  export type GetFilteredMutationOverTimeDataArgs = {
15
20
  data: MutationOverTimeDataMap;
16
21
  overallMutationData: SubstitutionOrDeletionEntry<Substitution, Deletion>[];
@@ -18,7 +23,7 @@ export type GetFilteredMutationOverTimeDataArgs = {
18
23
  displayedMutationTypes: DisplayedMutationType[];
19
24
  proportionInterval: { min: number; max: number };
20
25
  displayMutations?: DisplayMutations;
21
- mutationFilterValue: string;
26
+ mutationFilterValue: MutationFilter;
22
27
  sequenceType: SequenceType;
23
28
  annotationProvider: ReturnType<typeof useMutationAnnotationsProvider>;
24
29
  };
@@ -72,25 +77,54 @@ export function getFilteredMutationOverTimeData({
72
77
  export function mutationOrAnnotationDoNotMatchFilter(
73
78
  mutation: Mutation,
74
79
  sequenceType: SequenceType,
75
- filterValue: string,
80
+ mutationFilter: MutationFilter,
76
81
  annotationProvider: ReturnType<typeof useMutationAnnotationsProvider>,
77
82
  ) {
78
- if (filterValue === '') {
79
- return false;
83
+ return !(
84
+ mutationOrAnnotationMatchesTextFilter(mutation, sequenceType, mutationFilter.textFilter, annotationProvider) &&
85
+ mutationMatchesAnnotationFilter(mutation, sequenceType, mutationFilter.annotationNameFilter, annotationProvider)
86
+ );
87
+ }
88
+
89
+ function mutationOrAnnotationMatchesTextFilter(
90
+ mutation: Mutation,
91
+ sequenceType: SequenceType,
92
+ textFilter: string,
93
+ annotationProvider: ReturnType<typeof useMutationAnnotationsProvider>,
94
+ ) {
95
+ if (textFilter === '') {
96
+ return true;
80
97
  }
81
98
 
82
- if (mutation.code.includes(filterValue)) {
83
- return false;
99
+ if (mutation.code.includes(textFilter)) {
100
+ return true;
84
101
  }
85
102
 
86
103
  const mutationAnnotations = annotationProvider(mutation, sequenceType);
87
104
  if (mutationAnnotations === undefined || mutationAnnotations.length === 0) {
88
- return true;
105
+ return false;
89
106
  }
90
- return !mutationAnnotations.some(
107
+ return mutationAnnotations.some(
91
108
  (annotation) =>
92
- annotation.description.includes(filterValue) ||
93
- annotation.name.includes(filterValue) ||
94
- annotation.symbol.includes(filterValue),
109
+ annotation.description.includes(textFilter) ||
110
+ annotation.name.includes(textFilter) ||
111
+ annotation.symbol.includes(textFilter),
95
112
  );
96
113
  }
114
+
115
+ function mutationMatchesAnnotationFilter(
116
+ mutation: Mutation,
117
+ sequenceType: SequenceType,
118
+ annotationNameFilter: Set<string>,
119
+ annotationProvider: ReturnType<typeof useMutationAnnotationsProvider>,
120
+ ) {
121
+ if (annotationNameFilter.size === 0) {
122
+ return true;
123
+ }
124
+
125
+ const mutationAnnotations = annotationProvider(mutation, sequenceType);
126
+ if (mutationAnnotations === undefined || mutationAnnotations.length === 0) {
127
+ return false;
128
+ }
129
+ return mutationAnnotations.some((annotation) => annotationNameFilter.has(annotation.name));
130
+ }
@@ -5,7 +5,11 @@ import z from 'zod';
5
5
  // @ts-expect-error -- uses subpath imports and vite worker import
6
6
  import MutationOverTimeWorker from '#mutationOverTime?worker&inline';
7
7
  import { BaseMutationOverTimeDataMap, type MutationOverTimeDataMap } from './MutationOverTimeData';
8
- import { displayMutationsSchema, getFilteredMutationOverTimeData } from './getFilteredMutationsOverTimeData';
8
+ import {
9
+ displayMutationsSchema,
10
+ getFilteredMutationOverTimeData,
11
+ type MutationFilter,
12
+ } from './getFilteredMutationsOverTimeData';
9
13
  import { type MutationOverTimeWorkerResponse } from './mutationOverTimeWorker';
10
14
  import MutationsOverTimeGrid from './mutations-over-time-grid';
11
15
  import { type MutationOverTimeQuery } from '../../query/queryMutationsOverTime';
@@ -29,7 +33,7 @@ import { Fullscreen } from '../components/fullscreen';
29
33
  import Info, { InfoComponentCode, InfoHeadline1, InfoParagraph } from '../components/info';
30
34
  import { LoadingDisplay } from '../components/loading-display';
31
35
  import { type DisplayedMutationType, MutationTypeSelector } from '../components/mutation-type-selector';
32
- import { MutationsOverTimeTextFilter } from '../components/mutations-over-time-text-filter';
36
+ import { MutationsOverTimeMutationsFilter } from '../components/mutations-over-time-mutations-filter';
33
37
  import { NoDataDisplay } from '../components/no-data-display';
34
38
  import type { ProportionInterval } from '../components/proportion-selector';
35
39
  import { ProportionSelectorDropdown } from '../components/proportion-selector-dropdown';
@@ -129,7 +133,10 @@ const MutationsOverTimeTabs: FunctionComponent<MutationOverTimeTabsProps> = ({
129
133
  }) => {
130
134
  const tabsRef = useDispatchFinishedLoadingEvent();
131
135
 
132
- const [mutationFilterValue, setMutationFilterValue] = useState('');
136
+ const [mutationFilterValue, setMutationFilterValue] = useState<MutationFilter>({
137
+ textFilter: '',
138
+ annotationNameFilter: new Set(),
139
+ });
133
140
  const annotationProvider = useMutationAnnotationsProvider();
134
141
 
135
142
  const [proportionInterval, setProportionInterval] = useState(originalComponentProps.initialMeanProportionInterval);
@@ -227,8 +234,8 @@ type ToolbarProps = {
227
234
  colorScale: ColorScale;
228
235
  setColorScale: Dispatch<StateUpdater<ColorScale>>;
229
236
  originalComponentProps: MutationsOverTimeProps;
230
- mutationFilterValue: string;
231
- setFilterValue: (filterValue: string) => void;
237
+ mutationFilterValue: MutationFilter;
238
+ setFilterValue: Dispatch<StateUpdater<MutationFilter>>;
232
239
  };
233
240
 
234
241
  const Toolbar: FunctionComponent<ToolbarProps> = ({
@@ -248,7 +255,7 @@ const Toolbar: FunctionComponent<ToolbarProps> = ({
248
255
  }) => {
249
256
  return (
250
257
  <>
251
- <MutationsOverTimeTextFilter setFilterValue={setFilterValue} value={mutationFilterValue} />
258
+ <MutationsOverTimeMutationsFilter setFilterValue={setFilterValue} value={mutationFilterValue} />
252
259
  {activeTab === 'Grid' && (
253
260
  <ColorScaleSelectorDropdown colorScale={colorScale} setColorScale={setColorScale} />
254
261
  )}
@@ -14,13 +14,16 @@ import { ErrorBoundary } from '../../components/error-boundary';
14
14
  import { Fullscreen } from '../../components/fullscreen';
15
15
  import Info, { InfoComponentCode, InfoHeadline1, InfoParagraph } from '../../components/info';
16
16
  import { LoadingDisplay } from '../../components/loading-display';
17
- import { MutationsOverTimeTextFilter } from '../../components/mutations-over-time-text-filter';
17
+ import { MutationsOverTimeMutationsFilter } from '../../components/mutations-over-time-mutations-filter';
18
18
  import { NoDataDisplay } from '../../components/no-data-display';
19
19
  import { ResizeContainer } from '../../components/resize-container';
20
20
  import { type DisplayedSegment, SegmentSelector } from '../../components/segment-selector';
21
21
  import Tabs from '../../components/tabs';
22
22
  import { type MutationOverTimeDataMap } from '../../mutationsOverTime/MutationOverTimeData';
23
- import { mutationOrAnnotationDoNotMatchFilter } from '../../mutationsOverTime/getFilteredMutationsOverTimeData';
23
+ import {
24
+ type MutationFilter,
25
+ mutationOrAnnotationDoNotMatchFilter,
26
+ } from '../../mutationsOverTime/getFilteredMutationsOverTimeData';
24
27
  import MutationsOverTimeGrid from '../../mutationsOverTime/mutations-over-time-grid';
25
28
  import { pageSizesSchema } from '../../shared/tanstackTable/pagination';
26
29
  import { PageSizeContextProvider } from '../../shared/tanstackTable/pagination-context';
@@ -121,7 +124,7 @@ function getFilteredMutationOverTimeData({
121
124
  }: {
122
125
  data: MutationOverTimeDataMap;
123
126
  displayedSegments: DisplayedSegment[];
124
- mutationFilterValue: string;
127
+ mutationFilterValue: MutationFilter;
125
128
  sequenceType: SequenceType;
126
129
  annotationProvider: ReturnType<typeof useMutationAnnotationsProvider>;
127
130
  }): MutationOverTimeDataMap {
@@ -148,7 +151,10 @@ const MutationsOverTimeTabs: FunctionComponent<MutationOverTimeTabsProps> = ({
148
151
  }) => {
149
152
  const tabsRef = useDispatchFinishedLoadingEvent();
150
153
 
151
- const [mutationFilterValue, setMutationFilterValue] = useState('');
154
+ const [mutationFilterValue, setMutationFilterValue] = useState<MutationFilter>({
155
+ textFilter: '',
156
+ annotationNameFilter: new Set(),
157
+ });
152
158
  const annotationProvider = useMutationAnnotationsProvider();
153
159
 
154
160
  const [colorScale, setColorScale] = useState<ColorScale>({ min: 0, max: 1, color: 'indigo' });
@@ -211,8 +217,8 @@ type ToolbarProps = {
211
217
  data: MutationOverTimeDataPerLocation;
212
218
  displayedSegments: DisplayedSegment[];
213
219
  setDisplayedSegments: (segments: DisplayedSegment[]) => void;
214
- mutationFilterValue: string;
215
- setFilterValue: (filterValue: string) => void;
220
+ mutationFilterValue: MutationFilter;
221
+ setFilterValue: Dispatch<StateUpdater<MutationFilter>>;
216
222
  };
217
223
 
218
224
  const Toolbar: FunctionComponent<ToolbarProps> = ({
@@ -226,7 +232,7 @@ const Toolbar: FunctionComponent<ToolbarProps> = ({
226
232
  }) => {
227
233
  return (
228
234
  <>
229
- <MutationsOverTimeTextFilter setFilterValue={setFilterValue} value={mutationFilterValue} />
235
+ <MutationsOverTimeMutationsFilter setFilterValue={setFilterValue} value={mutationFilterValue} />
230
236
  <ColorScaleSelectorDropdown colorScale={colorScale} setColorScale={setColorScale} />
231
237
  <SegmentSelector
232
238
  displayedSegments={displayedSegments}