@genspectrum/dashboard-components 0.10.1 → 0.10.2

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 (46) hide show
  1. package/custom-elements.json +23 -23
  2. package/dist/assets/{mutationOverTimeWorker-CvZg52rf.js.map → mutationOverTimeWorker-Di6yP1e6.js.map} +1 -1
  3. package/dist/components.d.ts +50 -48
  4. package/dist/components.js +151 -62
  5. package/dist/components.js.map +1 -1
  6. package/dist/{utilEntrypoint-g4DsyhU7.js → dateRangeOption-du8H7LWu.js} +33 -2
  7. package/dist/dateRangeOption-du8H7LWu.js.map +1 -0
  8. package/dist/util.d.ts +101 -59
  9. package/dist/util.js +3 -2
  10. package/package.json +1 -1
  11. package/src/preact/components/color-scale-selector.tsx +7 -3
  12. package/src/preact/components/error-boundary.tsx +39 -5
  13. package/src/preact/components/error-display.tsx +40 -5
  14. package/src/preact/dateRangeSelector/computeInitialValues.spec.ts +8 -2
  15. package/src/preact/dateRangeSelector/computeInitialValues.ts +6 -0
  16. package/src/preact/dateRangeSelector/date-range-selector.stories.tsx +16 -2
  17. package/src/preact/dateRangeSelector/date-range-selector.tsx +20 -15
  18. package/src/preact/dateRangeSelector/dateRangeOption.ts +10 -5
  19. package/src/preact/lineageFilter/lineage-filter.stories.tsx +18 -4
  20. package/src/preact/lineageFilter/lineage-filter.tsx +15 -10
  21. package/src/preact/locationFilter/location-filter.stories.tsx +14 -0
  22. package/src/preact/locationFilter/location-filter.tsx +15 -10
  23. package/src/preact/mutationComparison/mutation-comparison-venn.tsx +17 -18
  24. package/src/preact/mutationComparison/mutation-comparison.tsx +18 -12
  25. package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutationsByDay.ts +1326 -9341
  26. package/src/preact/mutationsOverTime/__mockData__/byWeek.ts +615 -4920
  27. package/src/preact/mutationsOverTime/__mockData__/defaultMockData.ts +2203 -17624
  28. package/src/preact/mutationsOverTime/mutations-over-time-grid.tsx +16 -8
  29. package/src/preact/mutationsOverTime/mutations-over-time.tsx +1 -3
  30. package/src/preact/shared/stories/expectInvalidAttributesErrorMessage.ts +13 -0
  31. package/src/preact/webWorkers/useWebWorker.ts +8 -4
  32. package/src/query/queryMutationsOverTime.spec.ts +12 -27
  33. package/src/query/queryMutationsOverTime.ts +2 -6
  34. package/src/types.ts +19 -6
  35. package/src/utilEntrypoint.ts +8 -0
  36. package/src/utils/map2d.spec.ts +10 -10
  37. package/src/utils/map2d.ts +10 -10
  38. package/src/web-components/input/gs-date-range-selector.stories.ts +2 -2
  39. package/src/web-components/input/gs-date-range-selector.tsx +3 -3
  40. package/src/web-components/input/gs-lineage-filter.tsx +1 -1
  41. package/src/web-components/input/gs-location-filter.tsx +2 -2
  42. package/src/web-components/visualization/gs-aggregate.tsx +2 -2
  43. package/standalone-bundle/assets/{mutationOverTimeWorker-CypX_PYM.js.map → mutationOverTimeWorker-cIyshfj_.js.map} +1 -1
  44. package/standalone-bundle/dashboard-components.js +6668 -6580
  45. package/standalone-bundle/dashboard-components.js.map +1 -1
  46. package/dist/utilEntrypoint-g4DsyhU7.js.map +0 -1
@@ -1,10 +1,11 @@
1
1
  import flatpickr from 'flatpickr';
2
2
  import 'flatpickr/dist/flatpickr.min.css';
3
3
  import { useEffect, useRef, useState } from 'preact/hooks';
4
+ import z from 'zod';
4
5
 
5
6
  import { computeInitialValues } from './computeInitialValues';
6
7
  import { toYYYYMMDD } from './dateConversion';
7
- import { type DateRangeOption, DateRangeOptionChangedEvent, type DateRangeSelectOption } from './dateRangeOption';
8
+ import { DateRangeOptionChangedEvent, dateRangeOptionSchema, type DateRangeSelectOption } from './dateRangeOption';
8
9
  import { getDatesForSelectorValue, getSelectableOptions } from './selectableOptions';
9
10
  import { ErrorBoundary } from '../components/error-boundary';
10
11
  import { Select } from '../components/select';
@@ -12,24 +13,28 @@ import type { ScaleType } from '../shared/charts/getYAxisScale';
12
13
 
13
14
  const customOption = 'Custom';
14
15
 
15
- export interface DateRangeSelectorProps extends DateRangeSelectorPropsInner {
16
- width: string;
17
- }
16
+ const dateRangeSelectorInnerPropsSchema = z.object({
17
+ dateRangeOptions: z.array(dateRangeOptionSchema),
18
+ earliestDate: z.string().date(),
19
+ initialValue: z.string().optional(),
20
+ initialDateFrom: z.string().date().optional(),
21
+ initialDateTo: z.string().date().optional(),
22
+ dateColumn: z.string().min(1),
23
+ });
18
24
 
19
- export interface DateRangeSelectorPropsInner {
20
- dateRangeOptions: DateRangeOption[];
21
- earliestDate: string;
22
- initialValue: string | undefined;
23
- initialDateFrom: string;
24
- initialDateTo: string;
25
- dateColumn: string;
26
- }
25
+ const dateRangeSelectorPropsSchema = dateRangeSelectorInnerPropsSchema.extend({
26
+ width: z.string(),
27
+ });
27
28
 
28
- export const DateRangeSelector = ({ width, ...innerProps }: DateRangeSelectorProps) => {
29
+ export type DateRangeSelectorProps = z.infer<typeof dateRangeSelectorPropsSchema>;
30
+ export type DateRangeSelectorInnerProps = z.infer<typeof dateRangeSelectorInnerPropsSchema>;
31
+
32
+ export const DateRangeSelector = (props: DateRangeSelectorProps) => {
33
+ const { width, ...innerProps } = props;
29
34
  const size = { width, height: '3rem' };
30
35
 
31
36
  return (
32
- <ErrorBoundary size={size} layout='horizontal'>
37
+ <ErrorBoundary size={size} layout='horizontal' componentProps={props} schema={dateRangeSelectorPropsSchema}>
33
38
  <div style={{ width }}>
34
39
  <DateRangeSelectorInner {...innerProps} />
35
40
  </div>
@@ -44,7 +49,7 @@ export const DateRangeSelectorInner = ({
44
49
  dateColumn,
45
50
  initialDateFrom,
46
51
  initialDateTo,
47
- }: DateRangeSelectorPropsInner) => {
52
+ }: DateRangeSelectorInnerProps) => {
48
53
  const initialValues = computeInitialValues(
49
54
  initialValue,
50
55
  initialDateFrom,
@@ -1,24 +1,29 @@
1
+ import z from 'zod';
2
+
1
3
  import { toYYYYMMDD } from './dateConversion';
2
4
 
3
5
  /**
4
6
  * A date range option that can be used in the `gs-date-range-selector` component.
5
7
  */
6
- export type DateRangeOption = {
8
+ export const dateRangeOptionSchema = z.object({
7
9
  /** The label of the date range option that will be shown to the user */
8
- label: string;
10
+ label: z.string(),
9
11
  /**
10
12
  * The start date of the date range in the format `YYYY-MM-DD`.
11
13
  * If not set, the date range selector will default to the `earliestDate` property.
12
14
  */
13
- dateFrom?: string;
15
+ dateFrom: z.string().date().optional(),
14
16
  /**
15
17
  * The end date of the date range in the format `YYYY-MM-DD`.
16
18
  * If not set, the date range selector will default to the current date.
17
19
  */
18
- dateTo?: string;
19
- };
20
+ dateTo: z.string().date().optional(),
21
+ });
22
+
23
+ export type DateRangeOption = z.infer<typeof dateRangeOptionSchema>;
20
24
 
21
25
  export type DateRangeSelectOption = string | { dateFrom: string; dateTo: string };
26
+
22
27
  export class DateRangeOptionChangedEvent extends CustomEvent<DateRangeSelectOption> {
23
28
  constructor(detail: DateRangeSelectOption) {
24
29
  super('gs-date-range-option-changed', {
@@ -5,6 +5,7 @@ import { previewHandles } from '../../../.storybook/preview';
5
5
  import { AGGREGATED_ENDPOINT, LAPIS_URL } from '../../constants';
6
6
  import aggregatedData from '../../preact/lineageFilter/__mockData__/aggregated.json';
7
7
  import { LapisUrlContext } from '../LapisUrlContext';
8
+ import { expectInvalidAttributesErrorMessage } from '../shared/stories/expectInvalidAttributesErrorMessage';
8
9
 
9
10
  const meta: Meta = {
10
11
  title: 'Input/LineageFilter',
@@ -31,6 +32,12 @@ const meta: Meta = {
31
32
  ],
32
33
  },
33
34
  },
35
+ args: {
36
+ lapisField: 'pangoLineage',
37
+ placeholderText: 'Enter lineage',
38
+ initialValue: '',
39
+ width: '100%',
40
+ },
34
41
  };
35
42
 
36
43
  export default meta;
@@ -46,10 +53,17 @@ export const Default: StoryObj<LineageFilterProps> = {
46
53
  />
47
54
  </LapisUrlContext.Provider>
48
55
  ),
56
+ };
57
+
58
+ export const WithNoLapisField: StoryObj<LineageFilterProps> = {
59
+ ...Default,
49
60
  args: {
50
- lapisField: 'pangoLineage',
51
- placeholderText: 'Enter lineage',
52
- initialValue: '',
53
- width: '100%',
61
+ ...Default.args,
62
+ lapisField: '',
63
+ },
64
+ play: async ({ canvasElement, step }) => {
65
+ step('expect error message', async () => {
66
+ await expectInvalidAttributesErrorMessage(canvasElement, 'String must contain at least 1 character(s)');
67
+ });
54
68
  },
55
69
  };
@@ -1,5 +1,6 @@
1
1
  import { type FunctionComponent } from 'preact';
2
2
  import { useContext, useRef } from 'preact/hooks';
3
+ import z from 'zod';
3
4
 
4
5
  import { fetchLineageAutocompleteList } from './fetchLineageAutocompleteList';
5
6
  import { LapisUrlContext } from '../LapisUrlContext';
@@ -9,21 +10,25 @@ import { NoDataDisplay } from '../components/no-data-display';
9
10
  import { ResizeContainer } from '../components/resize-container';
10
11
  import { useQuery } from '../useQuery';
11
12
 
12
- export interface LineageFilterInnerProps {
13
- lapisField: string;
14
- placeholderText: string;
15
- initialValue: string;
16
- }
13
+ const lineageFilterInnerPropsSchema = z.object({
14
+ lapisField: z.string().min(1),
15
+ placeholderText: z.string().optional(),
16
+ initialValue: z.string(),
17
+ });
17
18
 
18
- export interface LineageFilterProps extends LineageFilterInnerProps {
19
- width: string;
20
- }
19
+ const lineageFilterPropsSchema = lineageFilterInnerPropsSchema.extend({
20
+ width: z.string(),
21
+ });
21
22
 
22
- export const LineageFilter: FunctionComponent<LineageFilterProps> = ({ width, ...innerProps }) => {
23
+ export type LineageFilterInnerProps = z.infer<typeof lineageFilterInnerPropsSchema>;
24
+ export type LineageFilterProps = z.infer<typeof lineageFilterPropsSchema>;
25
+
26
+ export const LineageFilter: FunctionComponent<LineageFilterProps> = (props) => {
27
+ const { width, ...innerProps } = props;
23
28
  const size = { width, height: '3rem' };
24
29
 
25
30
  return (
26
- <ErrorBoundary size={size} layout='horizontal'>
31
+ <ErrorBoundary size={size} layout='horizontal' componentProps={props} schema={lineageFilterPropsSchema}>
27
32
  <ResizeContainer size={size}>
28
33
  <LineageFilterInner {...innerProps} />
29
34
  </ResizeContainer>
@@ -5,6 +5,7 @@ import { LocationFilter, type LocationFilterProps } from './location-filter';
5
5
  import { previewHandles } from '../../../.storybook/preview';
6
6
  import { AGGREGATED_ENDPOINT, LAPIS_URL } from '../../constants';
7
7
  import { LapisUrlContext } from '../LapisUrlContext';
8
+ import { expectInvalidAttributesErrorMessage } from '../shared/stories/expectInvalidAttributesErrorMessage';
8
9
 
9
10
  const meta: Meta<LocationFilterProps> = {
10
11
  title: 'Input/LocationFilter',
@@ -75,3 +76,16 @@ export const Primary: StoryObj<LocationFilterProps> = {
75
76
  </LapisUrlContext.Provider>
76
77
  ),
77
78
  };
79
+
80
+ export const WithNoFields: StoryObj<LocationFilterProps> = {
81
+ ...Primary,
82
+ args: {
83
+ ...Primary.args,
84
+ fields: [],
85
+ },
86
+ play: async ({ canvasElement, step }) => {
87
+ step('expect error message', async () => {
88
+ await expectInvalidAttributesErrorMessage(canvasElement, 'Array must contain at least 1 element(s)');
89
+ });
90
+ },
91
+ };
@@ -1,6 +1,7 @@
1
1
  import { type FunctionComponent } from 'preact';
2
2
  import { useContext, useRef, useState } from 'preact/hooks';
3
3
  import { type JSXInternal } from 'preact/src/jsx';
4
+ import z from 'zod';
4
5
 
5
6
  import { fetchAutocompletionList } from './fetchAutocompletionList';
6
7
  import { LapisUrlContext } from '../LapisUrlContext';
@@ -9,21 +10,25 @@ import { LoadingDisplay } from '../components/loading-display';
9
10
  import { ResizeContainer } from '../components/resize-container';
10
11
  import { useQuery } from '../useQuery';
11
12
 
12
- export interface LocationFilterInnerProps {
13
- initialValue: string;
14
- placeholderText: string;
15
- fields: string[];
16
- }
13
+ const lineageFilterInnerPropsSchema = z.object({
14
+ initialValue: z.string().optional(),
15
+ placeholderText: z.string().optional(),
16
+ fields: z.array(z.string()).min(1),
17
+ });
17
18
 
18
- export interface LocationFilterProps extends LocationFilterInnerProps {
19
- width: string;
20
- }
19
+ const lineageFilterPropsSchema = lineageFilterInnerPropsSchema.extend({
20
+ width: z.string(),
21
+ });
21
22
 
22
- export const LocationFilter: FunctionComponent<LocationFilterProps> = ({ width, ...innerProps }) => {
23
+ export type LocationFilterInnerProps = z.infer<typeof lineageFilterInnerPropsSchema>;
24
+ export type LocationFilterProps = z.infer<typeof lineageFilterPropsSchema>;
25
+
26
+ export const LocationFilter: FunctionComponent<LocationFilterProps> = (props) => {
27
+ const { width, ...innerProps } = props;
23
28
  const size = { width, height: '3rem' };
24
29
 
25
30
  return (
26
- <ErrorBoundary size={size} layout='horizontal'>
31
+ <ErrorBoundary size={size} layout='horizontal' componentProps={props} schema={lineageFilterPropsSchema}>
27
32
  <ResizeContainer size={size}>
28
33
  <LocationFilterInner {...innerProps} />
29
34
  </ResizeContainer>
@@ -1,7 +1,7 @@
1
1
  import { type ActiveElement, Chart, type ChartConfiguration, type ChartEvent, registerables } from 'chart.js';
2
2
  import { ArcSlice, extractSets, VennDiagramController } from 'chartjs-chart-venn';
3
3
  import { type FunctionComponent } from 'preact';
4
- import { useEffect, useMemo, useRef, useState } from 'preact/hooks';
4
+ import { useMemo, useState } from 'preact/hooks';
5
5
 
6
6
  import { type MutationData } from './queryMutationData';
7
7
  import { type Dataset } from '../../operator/Dataset';
@@ -19,8 +19,6 @@ export const MutationComparisonVenn: FunctionComponent<MutationComparisonVennPro
19
19
  data,
20
20
  proportionInterval,
21
21
  }) => {
22
- const divRef = useRef<HTMLDivElement>(null);
23
- const noElementSelectedMessage = 'You have no elements selected. Click in the venn diagram to select.';
24
22
  const [selectedDatasetIndex, setSelectedDatasetIndex] = useState<null | number>(null);
25
23
 
26
24
  const sets = useMemo(
@@ -45,20 +43,6 @@ export const MutationComparisonVenn: FunctionComponent<MutationComparisonVennPro
45
43
  [data, proportionInterval],
46
44
  );
47
45
 
48
- useEffect(() => {
49
- if (divRef.current === null) {
50
- return;
51
- }
52
- if (selectedDatasetIndex === null) {
53
- divRef.current.innerText = noElementSelectedMessage;
54
- return;
55
- }
56
-
57
- const values = sets.datasets[0].data[selectedDatasetIndex].values;
58
- const label = sets.datasets[0].data[selectedDatasetIndex].label;
59
- divRef.current!.innerText = `${label}: ${values.join(', ')}` || '';
60
- }, [divRef, selectedDatasetIndex, sets]);
61
-
62
46
  const config: ChartConfiguration = useMemo(
63
47
  () => ({
64
48
  type: 'venn',
@@ -119,7 +103,22 @@ export const MutationComparisonVenn: FunctionComponent<MutationComparisonVennPro
119
103
  <div className='flex-1'>
120
104
  <GsChart configuration={config} />
121
105
  </div>
122
- <div class='flex flex-wrap break-words m-2' ref={divRef} />
106
+ <p class='flex flex-wrap break-words m-2'>{getSelectedMutationsDescription(selectedDatasetIndex, sets)}</p>
123
107
  </div>
124
108
  );
125
109
  };
110
+
111
+ const noElementSelectedMessage = 'You have no elements selected. Click in the venn diagram to select.';
112
+
113
+ function getSelectedMutationsDescription(
114
+ selectedDatasetIndex: number | null,
115
+ sets: ReturnType<typeof extractSets<string>>,
116
+ ) {
117
+ if (selectedDatasetIndex === null) {
118
+ return noElementSelectedMessage;
119
+ }
120
+
121
+ const values = sets.datasets[0].data[selectedDatasetIndex].values;
122
+ const label = sets.datasets[0].data[selectedDatasetIndex].label;
123
+ return `${label}: ${values.join(', ')}` || '';
124
+ }
@@ -1,11 +1,17 @@
1
1
  import { type FunctionComponent } from 'preact';
2
2
  import { type Dispatch, type StateUpdater, useContext, useMemo, useState } from 'preact/hooks';
3
+ import z from 'zod';
3
4
 
4
5
  import { getMutationComparisonTableData } from './getMutationComparisonTableData';
5
6
  import { MutationComparisonTable } from './mutation-comparison-table';
6
7
  import { MutationComparisonVenn } from './mutation-comparison-venn';
7
8
  import { filterMutationData, type MutationData, queryMutationData } from './queryMutationData';
8
- import { type NamedLapisFilter, type SequenceType } from '../../types';
9
+ import {
10
+ type MutationComparisonView,
11
+ mutationComparisonViewSchema,
12
+ namedLapisFilterSchema,
13
+ sequenceTypeSchema,
14
+ } from '../../types';
9
15
  import { LapisUrlContext } from '../LapisUrlContext';
10
16
  import { CsvDownloadButton } from '../components/csv-download-button';
11
17
  import { ErrorBoundary } from '../components/error-boundary';
@@ -21,23 +27,23 @@ import { type DisplayedSegment, SegmentSelector, useDisplayedSegments } from '..
21
27
  import Tabs from '../components/tabs';
22
28
  import { useQuery } from '../useQuery';
23
29
 
24
- export type View = 'table' | 'venn';
30
+ const mutationComparisonPropsSchema = z.object({
31
+ width: z.string(),
32
+ height: z.string(),
33
+ lapisFilters: z.array(namedLapisFilterSchema).min(1),
34
+ sequenceType: sequenceTypeSchema,
35
+ views: z.array(mutationComparisonViewSchema),
36
+ pageSize: z.union([z.boolean(), z.number()]),
37
+ });
25
38
 
26
- export interface MutationComparisonProps {
27
- width: string;
28
- height: string;
29
- lapisFilters: NamedLapisFilter[];
30
- sequenceType: SequenceType;
31
- views: View[];
32
- pageSize: boolean | number;
33
- }
39
+ export type MutationComparisonProps = z.infer<typeof mutationComparisonPropsSchema>;
34
40
 
35
41
  export const MutationComparison: FunctionComponent<MutationComparisonProps> = (componentProps) => {
36
42
  const { width, height } = componentProps;
37
43
  const size = { height, width };
38
44
 
39
45
  return (
40
- <ErrorBoundary size={size}>
46
+ <ErrorBoundary size={size} componentProps={componentProps} schema={mutationComparisonPropsSchema}>
41
47
  <ResizeContainer size={size}>
42
48
  <MutationComparisonInner {...componentProps} />
43
49
  </ResizeContainer>
@@ -86,7 +92,7 @@ const MutationComparisonTabs: FunctionComponent<MutationComparisonTabsProps> = (
86
92
  [data, displayedSegments, displayedMutationTypes],
87
93
  );
88
94
 
89
- const getTab = (view: View) => {
95
+ const getTab = (view: MutationComparisonView) => {
90
96
  switch (view) {
91
97
  case 'table':
92
98
  return {