@genspectrum/dashboard-components 1.7.0 → 1.8.1

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.
Files changed (31) hide show
  1. package/README.md +22 -2
  2. package/custom-elements.json +81 -0
  3. package/dist/assets/{mutationOverTimeWorker-DPS3tmOd.js.map → mutationOverTimeWorker-BRPqAM5Z.js.map} +1 -1
  4. package/dist/components.d.ts +49 -22
  5. package/dist/components.js +130 -37
  6. package/dist/components.js.map +1 -1
  7. package/dist/util.d.ts +22 -22
  8. package/package.json +1 -1
  9. package/src/preact/MutationLinkTemplateContext.tsx +56 -0
  10. package/src/preact/components/annotated-mutation.stories.tsx +69 -17
  11. package/src/preact/components/annotated-mutation.tsx +45 -19
  12. package/src/preact/mutationComparison/mutation-comparison-table.tsx +3 -0
  13. package/src/preact/mutations/mutations-table.tsx +3 -0
  14. package/src/preact/shared/stories/expectMutationAnnotation.ts +3 -1
  15. package/src/preact/wastewater/mutationsOverTime/__mockData__/detailsAminAcidNonSegmented.json +88 -0
  16. package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.stories.tsx +34 -0
  17. package/src/query/queryWastewaterMutationsOverTime.ts +1 -1
  18. package/src/utils/mutations.ts +18 -10
  19. package/src/web-components/gs-app.spec-d.ts +7 -0
  20. package/src/web-components/gs-app.stories.ts +32 -2
  21. package/src/web-components/gs-app.ts +17 -0
  22. package/src/web-components/mutation-link-template-context.ts +13 -0
  23. package/src/web-components/mutationLinks.mdx +27 -0
  24. package/src/web-components/visualization/gs-mutation-comparison.tsx +18 -8
  25. package/src/web-components/visualization/gs-mutations-over-time.stories.ts +12 -1
  26. package/src/web-components/visualization/gs-mutations-over-time.tsx +24 -14
  27. package/src/web-components/visualization/gs-mutations.tsx +19 -9
  28. package/src/web-components/wastewaterVisualization/gs-wastewater-mutations-over-time.tsx +17 -7
  29. package/standalone-bundle/assets/{mutationOverTimeWorker-Dp-A14AP.js.map → mutationOverTimeWorker-DtFX4Ihx.js.map} +1 -1
  30. package/standalone-bundle/dashboard-components.js +6360 -6298
  31. package/standalone-bundle/dashboard-components.js.map +1 -1
package/dist/util.d.ts CHANGED
@@ -989,7 +989,7 @@ declare global {
989
989
 
990
990
  declare global {
991
991
  interface HTMLElementTagNameMap {
992
- 'gs-relative-growth-advantage': RelativeGrowthAdvantageComponent;
992
+ 'gs-prevalence-over-time': PrevalenceOverTimeComponent;
993
993
  }
994
994
  }
995
995
 
@@ -997,7 +997,7 @@ declare global {
997
997
  declare global {
998
998
  namespace JSX {
999
999
  interface IntrinsicElements {
1000
- 'gs-relative-growth-advantage': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1000
+ 'gs-prevalence-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1001
1001
  }
1002
1002
  }
1003
1003
  }
@@ -1005,7 +1005,7 @@ declare global {
1005
1005
 
1006
1006
  declare global {
1007
1007
  interface HTMLElementTagNameMap {
1008
- 'gs-prevalence-over-time': PrevalenceOverTimeComponent;
1008
+ 'gs-relative-growth-advantage': RelativeGrowthAdvantageComponent;
1009
1009
  }
1010
1010
  }
1011
1011
 
@@ -1013,7 +1013,7 @@ declare global {
1013
1013
  declare global {
1014
1014
  namespace JSX {
1015
1015
  interface IntrinsicElements {
1016
- 'gs-prevalence-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1016
+ 'gs-relative-growth-advantage': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1017
1017
  }
1018
1018
  }
1019
1019
  }
@@ -1067,6 +1067,22 @@ declare global {
1067
1067
  }
1068
1068
 
1069
1069
 
1070
+ declare global {
1071
+ interface HTMLElementTagNameMap {
1072
+ 'gs-statistics': StatisticsComponent;
1073
+ }
1074
+ }
1075
+
1076
+
1077
+ declare global {
1078
+ namespace JSX {
1079
+ interface IntrinsicElements {
1080
+ 'gs-statistics': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1081
+ }
1082
+ }
1083
+ }
1084
+
1085
+
1070
1086
  declare global {
1071
1087
  interface HTMLElementTagNameMap {
1072
1088
  'gs-sequences-by-location': SequencesByLocationComponent;
@@ -1085,7 +1101,7 @@ declare global {
1085
1101
 
1086
1102
  declare global {
1087
1103
  interface HTMLElementTagNameMap {
1088
- 'gs-statistics': StatisticsComponent;
1104
+ 'gs-wastewater-mutations-over-time': WastewaterMutationsOverTimeComponent;
1089
1105
  }
1090
1106
  }
1091
1107
 
@@ -1093,7 +1109,7 @@ declare global {
1093
1109
  declare global {
1094
1110
  namespace JSX {
1095
1111
  interface IntrinsicElements {
1096
- 'gs-statistics': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1112
+ 'gs-wastewater-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1097
1113
  }
1098
1114
  }
1099
1115
  }
@@ -1215,22 +1231,6 @@ declare global {
1215
1231
  }
1216
1232
 
1217
1233
 
1218
- declare global {
1219
- interface HTMLElementTagNameMap {
1220
- 'gs-wastewater-mutations-over-time': WastewaterMutationsOverTimeComponent;
1221
- }
1222
- }
1223
-
1224
-
1225
- declare global {
1226
- namespace JSX {
1227
- interface IntrinsicElements {
1228
- 'gs-wastewater-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1229
- }
1230
- }
1231
- }
1232
-
1233
-
1234
1234
  declare module 'chart.js' {
1235
1235
  interface CartesianScaleTypeRegistry {
1236
1236
  logit: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genspectrum/dashboard-components",
3
- "version": "1.7.0",
3
+ "version": "1.8.1",
4
4
  "description": "GenSpectrum web components for building dashboards",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0-only",
@@ -0,0 +1,56 @@
1
+ import { createContext, type Provider } from 'preact';
2
+ import { useContext, useMemo } from 'preact/hooks';
3
+
4
+ import type { SequenceType } from '../types';
5
+ import type { Deletion, Substitution } from '../utils/mutations';
6
+ import { mutationLinkTemplateSchema } from '../web-components/mutation-link-template-context';
7
+ import { ErrorDisplay } from './components/error-display';
8
+ import { ResizeContainer } from './components/resize-container';
9
+
10
+ type MutationLinkTemplate = {
11
+ nucleotideMutation?: string;
12
+ aminoAcidMutation?: string;
13
+ };
14
+
15
+ const MutationLinkTemplateContext = createContext<MutationLinkTemplate>({
16
+ nucleotideMutation: undefined,
17
+ aminoAcidMutation: undefined,
18
+ });
19
+
20
+ export const MutationLinkTemplateContextProvider: Provider<MutationLinkTemplate> = ({ value, children }) => {
21
+ const parseResult = useMemo(() => mutationLinkTemplateSchema.safeParse(value), [value]);
22
+
23
+ if (!parseResult.success) {
24
+ return (
25
+ <ResizeContainer size={{ width: '100%' }}>
26
+ <ErrorDisplay error={parseResult.error} layout='vertical' />
27
+ </ResizeContainer>
28
+ );
29
+ }
30
+
31
+ return (
32
+ <MutationLinkTemplateContext.Provider value={parseResult.data}>{children}</MutationLinkTemplateContext.Provider>
33
+ );
34
+ };
35
+
36
+ export function useMutationLinkProvider() {
37
+ const linkTemplate = useContext(MutationLinkTemplateContext);
38
+
39
+ return (mutation: Substitution | Deletion, sequenceType: SequenceType) => {
40
+ switch (sequenceType) {
41
+ case 'nucleotide': {
42
+ if (linkTemplate.nucleotideMutation !== undefined) {
43
+ return linkTemplate.nucleotideMutation.replace('{{mutation}}', encodeURIComponent(mutation.code));
44
+ }
45
+ return undefined;
46
+ }
47
+
48
+ case 'amino acid': {
49
+ if (linkTemplate.aminoAcidMutation !== undefined) {
50
+ return linkTemplate.aminoAcidMutation.replace('{{mutation}}', encodeURIComponent(mutation.code));
51
+ }
52
+ return undefined;
53
+ }
54
+ }
55
+ };
56
+ }
@@ -2,15 +2,25 @@ import { type Meta, type StoryObj } from '@storybook/preact';
2
2
  import { expect, userEvent, waitFor, within } from '@storybook/test';
3
3
 
4
4
  import { AnnotatedMutation, type AnnotatedMutationProps } from './annotated-mutation';
5
- import { type MutationAnnotations } from '../../web-components/mutation-annotations-context';
5
+ import type { MutationAnnotations } from '../../web-components/mutation-annotations-context';
6
+ import type { MutationLinkTemplate } from '../../web-components/mutation-link-template-context';
6
7
  import { MutationAnnotationsContextProvider } from '../MutationAnnotationsContext';
8
+ import { MutationLinkTemplateContextProvider } from '../MutationLinkTemplateContext';
7
9
 
8
- const meta: Meta<AnnotatedMutationProps & { annotations: MutationAnnotations }> = {
10
+ type ContextProps = {
11
+ annotations: MutationAnnotations;
12
+ linkTemplate: MutationLinkTemplate;
13
+ };
14
+
15
+ type StoryProps = AnnotatedMutationProps & ContextProps;
16
+
17
+ const meta: Meta<StoryProps> = {
9
18
  title: 'Component/Annotated Mutation',
10
19
  component: AnnotatedMutation,
11
20
  parameters: { fetchMock: {} },
12
21
  argTypes: {
13
22
  annotations: { control: { type: 'object' } },
23
+ linkTemplate: { control: { type: 'object' } },
14
24
  mutation: { control: { type: 'object' } },
15
25
  sequenceType: {
16
26
  options: ['nucleotide', 'amino acid'],
@@ -21,14 +31,16 @@ const meta: Meta<AnnotatedMutationProps & { annotations: MutationAnnotations }>
21
31
 
22
32
  export default meta;
23
33
 
24
- export const MutationWithoutAnnotationEntry: StoryObj<AnnotatedMutationProps & { annotations: MutationAnnotations }> = {
34
+ export const MutationWithoutAnnotationEntry: StoryObj<StoryProps> = {
25
35
  render: (args) => {
26
- const { annotations, ...annotatedMutationsArgs } = args;
36
+ const { annotations, linkTemplate, ...annotatedMutationsArgs } = args;
27
37
 
28
38
  return (
29
- <MutationAnnotationsContextProvider value={annotations}>
30
- <AnnotatedMutation {...annotatedMutationsArgs} />
31
- </MutationAnnotationsContextProvider>
39
+ <MutationLinkTemplateContextProvider value={linkTemplate}>
40
+ <MutationAnnotationsContextProvider value={annotations}>
41
+ <AnnotatedMutation {...annotatedMutationsArgs} />
42
+ </MutationAnnotationsContextProvider>
43
+ </MutationLinkTemplateContextProvider>
32
44
  );
33
45
  },
34
46
  args: {
@@ -48,6 +60,7 @@ export const MutationWithoutAnnotationEntry: StoryObj<AnnotatedMutationProps & {
48
60
  nucleotideMutations: ['123T'],
49
61
  },
50
62
  ],
63
+ linkTemplate: {},
51
64
  },
52
65
  play: async ({ canvasElement }) => {
53
66
  const canvas = within(canvasElement);
@@ -58,7 +71,7 @@ export const MutationWithoutAnnotationEntry: StoryObj<AnnotatedMutationProps & {
58
71
  },
59
72
  };
60
73
 
61
- export const MutationWithAnnotationEntry: StoryObj<AnnotatedMutationProps & { annotations: MutationAnnotations }> = {
74
+ export const MutationWithAnnotationEntry: StoryObj<StoryProps> = {
62
75
  ...MutationWithoutAnnotationEntry,
63
76
  args: {
64
77
  ...MutationWithoutAnnotationEntry.args,
@@ -77,15 +90,13 @@ export const MutationWithAnnotationEntry: StoryObj<AnnotatedMutationProps & { an
77
90
  await waitFor(() => expect(canvas.getByText('A23403G')).toBeVisible());
78
91
  await expect(getAnnotationIndicator(canvas)).toBeVisible();
79
92
 
80
- await userEvent.click(canvas.getByText('A23403G'));
93
+ await userEvent.click(canvas.getByText('*'));
81
94
  await waitFor(() => expect(getAnnotationName(canvas)).toBeVisible());
82
95
  await expect(canvas.getByRole('link', { name: 'with a link.' })).toBeVisible();
83
96
  },
84
97
  };
85
98
 
86
- export const MutationWithMultipleAnnotationEntries: StoryObj<
87
- AnnotatedMutationProps & { annotations: MutationAnnotations }
88
- > = {
99
+ export const MutationWithMultipleAnnotationEntries: StoryObj<StoryProps> = {
89
100
  ...MutationWithoutAnnotationEntry,
90
101
  args: {
91
102
  ...MutationWithoutAnnotationEntry.args,
@@ -111,17 +122,16 @@ export const MutationWithMultipleAnnotationEntries: StoryObj<
111
122
  await expect(getAnnotationIndicator(canvas)).toBeVisible();
112
123
  await expect(canvas.queryByText('+')).toBeVisible();
113
124
 
114
- await userEvent.click(canvas.getByText('A23403G'));
125
+ await userEvent.click(canvas.getByText('*'));
115
126
  await waitFor(() => expect(getAnnotationName(canvas)).toBeVisible());
116
127
  await expect(canvas.queryByText('Another test annotation')).toBeVisible();
117
128
  },
118
129
  };
119
130
 
120
- export const AminoAcidMutationWithAnnotationEntry: StoryObj<
121
- AnnotatedMutationProps & { annotations: MutationAnnotations }
122
- > = {
131
+ export const AminoAcidMutationWithAnnotationEntry: StoryObj<StoryProps> = {
123
132
  ...MutationWithoutAnnotationEntry,
124
133
  args: {
134
+ ...MutationWithoutAnnotationEntry.args,
125
135
  mutation: {
126
136
  type: 'substitution',
127
137
  code: 'S:A501G',
@@ -145,7 +155,7 @@ export const AminoAcidMutationWithAnnotationEntry: StoryObj<
145
155
  await waitFor(() => expect(canvas.getByText('S:A501G')).toBeVisible());
146
156
  await expect(getAnnotationIndicator(canvas)).toBeVisible();
147
157
 
148
- await userEvent.click(canvas.getByText('S:A501G'));
158
+ await userEvent.click(canvas.getByText('*'));
149
159
  await waitFor(() => expect(getAnnotationName(canvas)).toBeVisible());
150
160
  },
151
161
  };
@@ -157,3 +167,45 @@ function getAnnotationIndicator(canvas: ReturnType<typeof within>) {
157
167
  function getAnnotationName(canvas: ReturnType<typeof within>) {
158
168
  return canvas.queryByText('Test annotation');
159
169
  }
170
+
171
+ export const NucleotideMutationWithLink: StoryObj<StoryProps> = {
172
+ ...MutationWithoutAnnotationEntry,
173
+ args: {
174
+ ...MutationWithoutAnnotationEntry.args,
175
+ linkTemplate: {
176
+ nucleotideMutation: 'http://foo.com/query?nucMut={{mutation}}',
177
+ },
178
+ },
179
+ play: async ({ canvasElement }) => {
180
+ const canvas = within(canvasElement);
181
+
182
+ await waitFor(() => expect(canvas.getByText('A23403G')).toBeVisible());
183
+ const link = canvas.getByText('A23403G').closest('a');
184
+ void expect(link).toHaveAttribute('href', 'http://foo.com/query?nucMut=A23403G');
185
+ },
186
+ };
187
+
188
+ export const AminoAcidMutationWithLink: StoryObj<StoryProps> = {
189
+ ...MutationWithoutAnnotationEntry,
190
+ args: {
191
+ ...MutationWithoutAnnotationEntry.args,
192
+ mutation: {
193
+ type: 'substitution',
194
+ code: 'S:A501G',
195
+ position: 501,
196
+ valueAtReference: 'A',
197
+ substitutionValue: 'G',
198
+ },
199
+ sequenceType: 'amino acid',
200
+ linkTemplate: {
201
+ aminoAcidMutation: 'http://foo.com/query?aaMut={{mutation}}',
202
+ },
203
+ },
204
+ play: async ({ canvasElement }) => {
205
+ const canvas = within(canvasElement);
206
+
207
+ await waitFor(() => expect(canvas.getByText('S:A501G')).toBeVisible());
208
+ const link = canvas.getByText('S:A501G').closest('a');
209
+ void expect(link).toHaveAttribute('href', 'http://foo.com/query?aaMut=S%3AA501G');
210
+ },
211
+ };
@@ -7,6 +7,7 @@ import type { Deletion, Substitution } from '../../utils/mutations';
7
7
  import { useMutationAnnotationsProvider } from '../MutationAnnotationsContext';
8
8
  import { InfoHeadline1, InfoHeadline2, InfoParagraph } from './info';
9
9
  import { ButtonWithModalDialog, useModalRef } from './modal';
10
+ import { useMutationLinkProvider } from '../MutationLinkTemplateContext';
10
11
 
11
12
  export type AnnotatedMutationProps = {
12
13
  mutation: Substitution | Deletion;
@@ -15,13 +16,22 @@ export type AnnotatedMutationProps = {
15
16
 
16
17
  export const AnnotatedMutation: FunctionComponent<AnnotatedMutationProps> = (props) => {
17
18
  const annotationsProvider = useMutationAnnotationsProvider();
19
+ const linkProvider = useMutationLinkProvider();
18
20
  const modalRef = useModalRef();
19
21
 
20
- return <AnnotatedMutationWithoutContext {...props} annotationsProvider={annotationsProvider} modalRef={modalRef} />;
22
+ return (
23
+ <AnnotatedMutationWithoutContext
24
+ {...props}
25
+ annotationsProvider={annotationsProvider}
26
+ linkProvider={linkProvider}
27
+ modalRef={modalRef}
28
+ />
29
+ );
21
30
  };
22
31
 
23
32
  type GridJsAnnotatedMutationProps = AnnotatedMutationProps & {
24
33
  annotationsProvider: ReturnType<typeof useMutationAnnotationsProvider>;
34
+ linkProvider: ReturnType<typeof useMutationLinkProvider>;
25
35
  };
26
36
 
27
37
  /**
@@ -43,12 +53,26 @@ const AnnotatedMutationWithoutContext: FunctionComponent<AnnotatedMutationWithou
43
53
  mutation,
44
54
  sequenceType,
45
55
  annotationsProvider,
56
+ linkProvider,
46
57
  modalRef,
47
58
  }) => {
59
+ const link = linkProvider(mutation, sequenceType);
60
+ let innerLabel = <>{mutation.code}</>;
61
+ if (link !== undefined) {
62
+ innerLabel = (
63
+ <a
64
+ className='hover:text-blue-800 focus:outline-none focus:ring-2 focus:ring-blue-300 underline'
65
+ href={link}
66
+ >
67
+ {mutation.code}
68
+ </a>
69
+ );
70
+ }
71
+
48
72
  const mutationAnnotations = annotationsProvider(mutation, sequenceType);
49
73
 
50
74
  if (mutationAnnotations === undefined || mutationAnnotations.length === 0) {
51
- return mutation.code;
75
+ return innerLabel;
52
76
  }
53
77
 
54
78
  const modalContent = (
@@ -66,22 +90,24 @@ const AnnotatedMutationWithoutContext: FunctionComponent<AnnotatedMutationWithou
66
90
  );
67
91
 
68
92
  return (
69
- <ButtonWithModalDialog
70
- buttonClassName={'select-text cursor-pointer'}
71
- modalContent={modalContent}
72
- modalRef={modalRef}
73
- >
74
- {mutation.code}
75
- <sup>
76
- {mutationAnnotations
77
- .map((annotation) => annotation.symbol)
78
- .map((symbol, index) => (
79
- <Fragment key={symbol}>
80
- <span className='text-red-600'>{symbol}</span>
81
- {index !== mutationAnnotations.length - 1 && ','}
82
- </Fragment>
83
- ))}
84
- </sup>
85
- </ButtonWithModalDialog>
93
+ <>
94
+ {innerLabel}
95
+ <ButtonWithModalDialog
96
+ buttonClassName={'select-text cursor-pointer'}
97
+ modalContent={modalContent}
98
+ modalRef={modalRef}
99
+ >
100
+ <sup className='hover:underline focus-visible:underline decoration-red-600'>
101
+ {mutationAnnotations
102
+ .map((annotation) => annotation.symbol)
103
+ .map((symbol, index) => (
104
+ <Fragment key={symbol}>
105
+ <span className='text-red-600'>{symbol}</span>
106
+ {index !== mutationAnnotations.length - 1 && ','}
107
+ </Fragment>
108
+ ))}
109
+ </sup>
110
+ </ButtonWithModalDialog>
111
+ </>
86
112
  );
87
113
  };
@@ -6,6 +6,7 @@ import { type Dataset } from '../../operator/Dataset';
6
6
  import type { SequenceType } from '../../types';
7
7
  import { type DeletionClass, type SubstitutionClass } from '../../utils/mutations';
8
8
  import { useMutationAnnotationsProvider } from '../MutationAnnotationsContext';
9
+ import { useMutationLinkProvider } from '../MutationLinkTemplateContext';
9
10
  import { GridJsAnnotatedMutation } from '../components/annotated-mutation';
10
11
  import { type ProportionInterval } from '../components/proportion-selector';
11
12
  import { Table } from '../components/table';
@@ -26,6 +27,7 @@ export const MutationComparisonTable: FunctionComponent<MutationsTableProps> = (
26
27
  sequenceType,
27
28
  }) => {
28
29
  const annotationsProvider = useMutationAnnotationsProvider();
30
+ const linkProvider = useMutationLinkProvider();
29
31
 
30
32
  const headers = [
31
33
  {
@@ -38,6 +40,7 @@ export const MutationComparisonTable: FunctionComponent<MutationsTableProps> = (
38
40
  mutation={cell}
39
41
  sequenceType={sequenceType}
40
42
  annotationsProvider={annotationsProvider}
43
+ linkProvider={linkProvider}
41
44
  />
42
45
  ),
43
46
  },
@@ -5,6 +5,7 @@ import { getMutationsTableData } from './getMutationsTableData';
5
5
  import { type SequenceType, type SubstitutionOrDeletionEntry } from '../../types';
6
6
  import { type DeletionClass, type SubstitutionClass } from '../../utils/mutations';
7
7
  import { useMutationAnnotationsProvider } from '../MutationAnnotationsContext';
8
+ import { useMutationLinkProvider } from '../MutationLinkTemplateContext';
8
9
  import { GridJsAnnotatedMutation } from '../components/annotated-mutation';
9
10
  import type { ProportionInterval } from '../components/proportion-selector';
10
11
  import { Table } from '../components/table';
@@ -29,6 +30,7 @@ const MutationsTable: FunctionComponent<MutationsTableProps> = ({
29
30
  sequenceType,
30
31
  }) => {
31
32
  const annotationsProvider = useMutationAnnotationsProvider();
33
+ const linkProvider = useMutationLinkProvider();
32
34
 
33
35
  const headers = [
34
36
  {
@@ -43,6 +45,7 @@ const MutationsTable: FunctionComponent<MutationsTableProps> = ({
43
45
  mutation={cell}
44
46
  sequenceType={sequenceType}
45
47
  annotationsProvider={annotationsProvider}
48
+ linkProvider={linkProvider}
46
49
  />
47
50
  ),
48
51
  },
@@ -6,7 +6,9 @@ export async function expectMutationAnnotation(canvasElement: HTMLElement, mutat
6
6
  await waitFor(async () => {
7
7
  const annotatedMutation = canvas.getAllByText(mutation)[0];
8
8
  await expect(annotatedMutation).toBeVisible();
9
- await userEvent.click(annotatedMutation);
9
+ const button = within(annotatedMutation).getByRole('button');
10
+ await expect(button).toBeVisible();
11
+ await userEvent.click(button);
10
12
  });
11
13
 
12
14
  await waitFor(() => expect(canvas.getByText(`Annotations for ${mutation}`)).toBeVisible());
@@ -0,0 +1,88 @@
1
+ {
2
+ "data": [
3
+ {
4
+ "aminoAcidMutationFrequency": null,
5
+ "date": "2024-11-13",
6
+ "location": "Lugano",
7
+ "nucleotideMutationFrequency": "{\"A12183T\": null, \"C2554T\": null, \"C3422A\": 0.9598659873008728, \"A966C\": null, \"G6661A\": 0.5527499914169312, \"G7731A\": 0.9832900166511536, \"T4026G\": 0.9991809725761414, \"T5260C\": null, \"T5287C\": null}",
8
+ "reference": "RSV-B"
9
+ },
10
+ {
11
+ "aminoAcidMutationFrequency": null,
12
+ "date": "2024-11-17",
13
+ "location": "Genève",
14
+ "nucleotideMutationFrequency": "{\"A12183T\": 1.0, \"C2554T\": null, \"C3422A\": 0.0, \"A966C\": null, \"G6661A\": 0.8785049915313721, \"G7731A\": 0.9855599999427795, \"T4026G\": null, \"T5260C\": 1.0, \"T5287C\": 0.9932659864425659}",
15
+ "reference": "RSV-B"
16
+ },
17
+ {
18
+ "aminoAcidMutationFrequency": "{\"S4286C\": null, \"R346T\": 0.25, \"Q493E\": 0.60, \"G204P\": null}",
19
+ "date": "2024-11-17",
20
+ "location": "Lugano",
21
+ "nucleotideMutationFrequency": "{\"A12183T\": 0.998993992805481, \"C2554T\": null, \"C3422A\": null, \"A966C\": null, \"G6661A\": 0.8917459845542908, \"G7731A\": 0.9770470261573792, \"T4026G\": 0.0, \"T5260C\": null, \"T5287C\": null}",
22
+ "reference": "RSV-B"
23
+ },
24
+ {
25
+ "aminoAcidMutationFrequency": "{\"S4286C\": 0.42, \"R346T\": 0.22, \"Q493E\": 0.66, \"G204P\": null}",
26
+ "date": "2024-11-16",
27
+ "location": "Lugano",
28
+ "nucleotideMutationFrequency": "{\"A12183T\": null, \"C2554T\": null, \"C3422A\": null, \"A966C\": null, \"G6661A\": 0.9476320147514343, \"G7731A\": 0.9809200167655945, \"T4026G\": 0.9992259740829468, \"T5260C\": null, \"T5287C\": null}",
29
+ "reference": "RSV-B"
30
+ },
31
+ {
32
+ "aminoAcidMutationFrequency": null,
33
+ "date": "2024-11-18",
34
+ "location": "Genève",
35
+ "nucleotideMutationFrequency": "{\"A12183T\": null, \"C2554T\": 0.9978219866752625, \"C3422A\": null, \"A966C\": 1.0, \"G6661A\": null, \"G7731A\": null, \"T4026G\": null, \"T5260C\": null, \"T5287C\": null}",
36
+ "reference": "RSV-B"
37
+ },
38
+ {
39
+ "aminoAcidMutationFrequency": null,
40
+ "date": "2024-11-14",
41
+ "location": "Genève",
42
+ "nucleotideMutationFrequency": "{\"A12183T\": null, \"C2554T\": 0.9994590282440186, \"C3422A\": 0.05333299934864044, \"A966C\": 1.0, \"G6661A\": 0.9282640218734741, \"G7731A\": 0.9803630113601685, \"T4026G\": 0.0, \"T5260C\": 0.9970409870147705, \"T5287C\": 0.996694028377533}",
43
+ "reference": "RSV-B"
44
+ },
45
+ {
46
+ "aminoAcidMutationFrequency": null,
47
+ "date": "2024-11-13",
48
+ "location": "Laupen",
49
+ "nucleotideMutationFrequency": "{\"A12668G\": null, \"A3564G\": null, \"C10862T\": null, \"C12710T\": null, \"C3624T\": null, \"G11123A\": null, \"G3616A\": null, \"G7379A\": 0.9470750093460083, \"T11034A\": null, \"T3483C\": null}",
50
+ "reference": "RSV-A"
51
+ },
52
+ {
53
+ "aminoAcidMutationFrequency": null,
54
+ "date": "2024-11-13",
55
+ "location": "Zürich",
56
+ "nucleotideMutationFrequency": "{\"A12668G\": 0.9988809823989868, \"A3564G\": null, \"C10862T\": 1.0, \"C12710T\": 0.9991809725761414, \"C3624T\": null, \"G11123A\": 1.0, \"G3616A\": null, \"G7379A\": 0.9414680004119873, \"T11034A\": 0.9988250136375427, \"T3483C\": null}",
57
+ "reference": "RSV-A"
58
+ },
59
+ {
60
+ "aminoAcidMutationFrequency": null,
61
+ "date": "2024-11-16",
62
+ "location": "Zürich",
63
+ "nucleotideMutationFrequency": "{\"A12668G\": 0.999222993850708, \"A3564G\": 0.9998199939727783, \"C10862T\": 1.0, \"C12710T\": 0.9996299743652344, \"C3624T\": 0.9999099969863892, \"G11123A\": 1.0, \"G3616A\": 0.08799900114536285, \"G7379A\": null, \"T11034A\": 0.9970099925994873, \"T3483C\": 0.9993579983711243}",
64
+ "reference": "RSV-A"
65
+ },
66
+ {
67
+ "aminoAcidMutationFrequency": null,
68
+ "date": "2024-11-16",
69
+ "location": "Chur",
70
+ "nucleotideMutationFrequency": "{\"A12668G\": null, \"A3564G\": null, \"C10862T\": null, \"C12710T\": null, \"C3624T\": null, \"G11123A\": null, \"G3616A\": null, \"G7379A\": 0.6464089751243591, \"T11034A\": null, \"T3483C\": null}",
71
+ "reference": "RSV-A"
72
+ },
73
+ {
74
+ "aminoAcidMutationFrequency": null,
75
+ "date": "2024-11-18",
76
+ "location": "Basel",
77
+ "nucleotideMutationFrequency": "{\"A12668G\": null, \"A3564G\": 1.0, \"C10862T\": null, \"C12710T\": null, \"C3624T\": 1.0, \"G11123A\": null, \"G3616A\": 0.08222199976444244, \"G7379A\": null, \"T11034A\": null, \"T3483C\": 0.9998080134391785}",
78
+ "reference": "RSV-A"
79
+ }
80
+ ],
81
+ "info": {
82
+ "dataVersion": "1737327031",
83
+ "requestId": "5591b455-3d84-438c-8434-2e57ee2ad569",
84
+ "requestInfo": "RSV on api.wise-loculus.genspectrum.org at 2025-01-20T11:58:03.498206810",
85
+ "reportTo": "Please report to https://github.com/GenSpectrum/LAPIS/issues in case you encounter any unexpected issues. Please include the request ID and the requestInfo in your report.",
86
+ "lapisVersion": "0.3.10"
87
+ }
88
+ }
@@ -8,6 +8,7 @@ import referenceGenome from '../../../lapisApi/__mockData__/referenceGenome.json
8
8
  import { LapisUrlContextProvider } from '../../LapisUrlContext';
9
9
  import { ReferenceGenomeContext } from '../../ReferenceGenomeContext';
10
10
  import details from './__mockData__/details.json';
11
+ import detailsNonSegmented from './__mockData__/detailsAminAcidNonSegmented.json';
11
12
  import type { MutationsOverTimeProps } from '../../mutationsOverTime/mutations-over-time';
12
13
  import { playThatExpectsFinishedLoadingEvent } from '../../shared/stories/expectFinishedLoadingEvent';
13
14
 
@@ -113,6 +114,39 @@ export const AminoAcids: StoryObj<WastewaterMutationsOverTimeProps> = {
113
114
  },
114
115
  };
115
116
 
117
+ export const AminoAcidsNonSegmented: StoryObj<WastewaterMutationsOverTimeProps> = {
118
+ ...Default,
119
+ args: {
120
+ ...Default.args,
121
+ sequenceType: 'amino acid',
122
+ },
123
+ parameters: {
124
+ fetchMock: {
125
+ mocks: [
126
+ {
127
+ matcher: {
128
+ name: 'details',
129
+ url: WISE_DETAILS_ENDPOINT,
130
+ body: {
131
+ fields: ['date', 'location', 'nucleotideMutationFrequency', 'aminoAcidMutationFrequency'],
132
+ },
133
+ },
134
+ response: {
135
+ status: 200,
136
+ body: detailsNonSegmented,
137
+ },
138
+ },
139
+ ],
140
+ },
141
+ },
142
+ play: async ({ canvas }) => {
143
+ await expectMutationOnPage(canvas, 'G204P');
144
+ await expectMutationOnPage(canvas, 'R346T');
145
+ await expectMutationOnPage(canvas, 'Q493E');
146
+ await expectMutationOnPage(canvas, 'S4286C');
147
+ },
148
+ };
149
+
116
150
  export const UsesMutationFilter: StoryObj<MutationsOverTimeProps> = {
117
151
  ...Default,
118
152
  play: async ({ canvas, step }) => {
@@ -57,7 +57,7 @@ function transformMutations(input: unknown): { mutation: Substitution; proportio
57
57
  }
58
58
 
59
59
  return Object.entries(mutationFrequency.data).map(([key, value]) => {
60
- const mutation = SubstitutionClass.parse(key);
60
+ const mutation = SubstitutionClass.parse(key, true);
61
61
  if (mutation === null) {
62
62
  throw new Error(`Failed to parse mutation: "${key}"`);
63
63
  }