@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.
- package/README.md +22 -2
- package/custom-elements.json +81 -0
- package/dist/assets/{mutationOverTimeWorker-DPS3tmOd.js.map → mutationOverTimeWorker-BRPqAM5Z.js.map} +1 -1
- package/dist/components.d.ts +49 -22
- package/dist/components.js +130 -37
- package/dist/components.js.map +1 -1
- package/dist/util.d.ts +22 -22
- package/package.json +1 -1
- package/src/preact/MutationLinkTemplateContext.tsx +56 -0
- package/src/preact/components/annotated-mutation.stories.tsx +69 -17
- package/src/preact/components/annotated-mutation.tsx +45 -19
- package/src/preact/mutationComparison/mutation-comparison-table.tsx +3 -0
- package/src/preact/mutations/mutations-table.tsx +3 -0
- package/src/preact/shared/stories/expectMutationAnnotation.ts +3 -1
- package/src/preact/wastewater/mutationsOverTime/__mockData__/detailsAminAcidNonSegmented.json +88 -0
- package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.stories.tsx +34 -0
- package/src/query/queryWastewaterMutationsOverTime.ts +1 -1
- package/src/utils/mutations.ts +18 -10
- package/src/web-components/gs-app.spec-d.ts +7 -0
- package/src/web-components/gs-app.stories.ts +32 -2
- package/src/web-components/gs-app.ts +17 -0
- package/src/web-components/mutation-link-template-context.ts +13 -0
- package/src/web-components/mutationLinks.mdx +27 -0
- package/src/web-components/visualization/gs-mutation-comparison.tsx +18 -8
- package/src/web-components/visualization/gs-mutations-over-time.stories.ts +12 -1
- package/src/web-components/visualization/gs-mutations-over-time.tsx +24 -14
- package/src/web-components/visualization/gs-mutations.tsx +19 -9
- package/src/web-components/wastewaterVisualization/gs-wastewater-mutations-over-time.tsx +17 -7
- package/standalone-bundle/assets/{mutationOverTimeWorker-Dp-A14AP.js.map → mutationOverTimeWorker-DtFX4Ihx.js.map} +1 -1
- package/standalone-bundle/dashboard-components.js +6360 -6298
- 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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
@@ -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 {
|
|
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
|
-
|
|
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<
|
|
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
|
-
<
|
|
30
|
-
<
|
|
31
|
-
|
|
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<
|
|
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('
|
|
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('
|
|
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('
|
|
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
|
|
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
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
<
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
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
|
}
|