@genspectrum/dashboard-components 0.1.5 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/custom-elements.json +1161 -928
- package/dist/dashboard-components.js +663 -237
- package/dist/dashboard-components.js.map +1 -1
- package/dist/genspectrum-components.d.ts +177 -140
- package/dist/style.css +247 -50
- package/package.json +2 -3
- package/src/constants.ts +1 -1
- package/src/lapisApi/lapisApi.ts +46 -2
- package/src/lapisApi/lapisTypes.ts +14 -0
- package/src/preact/aggregatedData/aggregate.stories.tsx +4 -2
- package/src/preact/aggregatedData/aggregate.tsx +31 -29
- package/src/preact/components/error-boundary.stories.tsx +54 -0
- package/src/preact/components/error-boundary.tsx +22 -0
- package/src/preact/components/error-display.stories.tsx +32 -4
- package/src/preact/components/error-display.tsx +48 -1
- package/src/preact/components/loading-display.stories.tsx +6 -6
- package/src/preact/components/loading-display.tsx +1 -1
- package/src/preact/components/no-data-display.tsx +5 -1
- package/src/preact/components/resize-container.tsx +5 -14
- package/src/preact/dateRangeSelector/date-range-selector.stories.tsx +19 -0
- package/src/preact/dateRangeSelector/date-range-selector.tsx +38 -7
- package/src/preact/locationFilter/fetchAutocompletionList.ts +15 -1
- package/src/preact/locationFilter/location-filter.stories.tsx +23 -6
- package/src/preact/locationFilter/location-filter.tsx +28 -18
- package/src/preact/mutationComparison/mutation-comparison.stories.tsx +6 -3
- package/src/preact/mutationComparison/mutation-comparison.tsx +33 -32
- package/src/preact/mutationComparison/queryMutationData.ts +2 -3
- package/src/preact/mutationFilter/mutation-filter.stories.tsx +18 -3
- package/src/preact/mutationFilter/mutation-filter.tsx +26 -7
- package/src/preact/mutations/mutations.stories.tsx +6 -3
- package/src/preact/mutations/mutations.tsx +28 -26
- package/src/preact/prevalenceOverTime/prevalence-over-time.stories.tsx +14 -7
- package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +50 -32
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.stories.tsx +6 -3
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.tsx +46 -32
- package/src/preact/textInput/text-input.stories.tsx +26 -0
- package/src/preact/textInput/text-input.tsx +25 -3
- package/src/query/queryPrevalenceOverTime.ts +4 -10
- package/src/types.ts +4 -1
- package/src/web-components/ResizeContainer.mdx +13 -0
- package/src/web-components/app.stories.ts +1 -2
- package/src/web-components/app.ts +7 -3
- package/src/web-components/index.ts +1 -1
- package/src/web-components/input/{date-range-selector-component.stories.ts → gs-date-range-selector.stories.ts} +29 -4
- package/src/web-components/input/{date-range-selector-component.tsx → gs-date-range-selector.tsx} +32 -10
- package/src/web-components/input/{location-filter-component.stories.ts → gs-location-filter.stories.ts} +32 -5
- package/src/web-components/input/{location-filter-component.tsx → gs-location-filter.tsx} +11 -1
- package/src/web-components/input/{mutation-filter-component.stories.ts → gs-mutation-filter.stories.ts} +23 -4
- package/src/web-components/input/gs-mutation-filter.tsx +126 -0
- package/src/web-components/input/{text-input-component.stories.ts → gs-text-input.stories.ts} +34 -6
- package/src/web-components/input/{text-input-component.tsx → gs-text-input.tsx} +16 -4
- package/src/web-components/input/index.ts +4 -4
- package/src/web-components/input/introduction.mdx +11 -0
- package/src/web-components/introduction.mdx +15 -0
- package/src/web-components/visualization/data_visualization_statistical_analysis.mdx +26 -0
- package/src/web-components/{display/aggregate-component.stories.ts → visualization/gs-aggregate.stories.ts} +23 -11
- package/src/web-components/visualization/gs-aggregate.tsx +88 -0
- package/src/web-components/{display/mutation-comparison-component.stories.ts → visualization/gs-mutation-comparison.stories.ts} +21 -16
- package/src/web-components/{display/mutation-comparison-component.tsx → visualization/gs-mutation-comparison.tsx} +27 -18
- package/src/web-components/{display/mutations-component.stories.ts → visualization/gs-mutations.stories.ts} +20 -15
- package/src/web-components/{display/mutations-component.tsx → visualization/gs-mutations.tsx} +20 -10
- package/src/web-components/{display/prevalence-over-time-component.stories.ts → visualization/gs-prevalence-over-time.stories.ts} +29 -20
- package/src/web-components/{display/prevalence-over-time-component.tsx → visualization/gs-prevalence-over-time.tsx} +47 -22
- package/src/web-components/{display/relative-growth-advantage-component.stories.ts → visualization/gs-relative-growth-advantage.stories.ts} +12 -7
- package/src/web-components/{display/relative-growth-advantage-component.tsx → visualization/gs-relative-growth-advantage.tsx} +21 -9
- package/src/web-components/visualization/index.ts +5 -0
- package/src/web-components/display/aggregate-component.tsx +0 -72
- package/src/web-components/display/index.ts +0 -5
- package/src/web-components/input/mutation-filter-component.tsx +0 -83
|
@@ -8,12 +8,13 @@ import {
|
|
|
8
8
|
} from '../../query/queryRelativeGrowthAdvantage';
|
|
9
9
|
import { type LapisFilter } from '../../types';
|
|
10
10
|
import { LapisUrlContext } from '../LapisUrlContext';
|
|
11
|
+
import { ErrorBoundary } from '../components/error-boundary';
|
|
11
12
|
import { ErrorDisplay } from '../components/error-display';
|
|
12
13
|
import Headline from '../components/headline';
|
|
13
14
|
import Info, { InfoHeadline1, InfoHeadline2, InfoLink, InfoParagraph } from '../components/info';
|
|
14
15
|
import { LoadingDisplay } from '../components/loading-display';
|
|
15
16
|
import { NoDataDisplay } from '../components/no-data-display';
|
|
16
|
-
import { ResizeContainer
|
|
17
|
+
import { ResizeContainer } from '../components/resize-container';
|
|
17
18
|
import { ScalingSelector } from '../components/scaling-selector';
|
|
18
19
|
import Tabs from '../components/tabs';
|
|
19
20
|
import { type ScaleType } from '../shared/charts/getYAxisScale';
|
|
@@ -21,22 +22,51 @@ import { useQuery } from '../useQuery';
|
|
|
21
22
|
|
|
22
23
|
export type View = 'line';
|
|
23
24
|
|
|
24
|
-
export interface RelativeGrowthAdvantageProps {
|
|
25
|
+
export interface RelativeGrowthAdvantageProps extends RelativeGrowthAdvantagePropsInner {
|
|
26
|
+
width: string;
|
|
27
|
+
height: string;
|
|
28
|
+
headline?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface RelativeGrowthAdvantagePropsInner {
|
|
25
32
|
numerator: LapisFilter;
|
|
26
33
|
denominator: LapisFilter;
|
|
27
34
|
generationTime: number;
|
|
28
35
|
views: View[];
|
|
29
|
-
size?: Size;
|
|
30
|
-
headline?: string;
|
|
31
36
|
}
|
|
32
37
|
|
|
33
38
|
export const RelativeGrowthAdvantage: FunctionComponent<RelativeGrowthAdvantageProps> = ({
|
|
39
|
+
views,
|
|
40
|
+
width,
|
|
41
|
+
height,
|
|
34
42
|
numerator,
|
|
35
43
|
denominator,
|
|
36
44
|
generationTime,
|
|
37
|
-
views,
|
|
38
|
-
size,
|
|
39
45
|
headline = 'Relative growth advantage',
|
|
46
|
+
}) => {
|
|
47
|
+
const size = { height, width };
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<ErrorBoundary size={size} headline={headline}>
|
|
51
|
+
<ResizeContainer size={size}>
|
|
52
|
+
<Headline heading={headline}>
|
|
53
|
+
<RelativeGrowthAdvantageInner
|
|
54
|
+
views={views}
|
|
55
|
+
numerator={numerator}
|
|
56
|
+
denominator={denominator}
|
|
57
|
+
generationTime={generationTime}
|
|
58
|
+
/>
|
|
59
|
+
</Headline>
|
|
60
|
+
</ResizeContainer>
|
|
61
|
+
</ErrorBoundary>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const RelativeGrowthAdvantageInner: FunctionComponent<RelativeGrowthAdvantagePropsInner> = ({
|
|
66
|
+
numerator,
|
|
67
|
+
denominator,
|
|
68
|
+
generationTime,
|
|
69
|
+
views,
|
|
40
70
|
}) => {
|
|
41
71
|
const lapis = useContext(LapisUrlContext);
|
|
42
72
|
const [yAxisScaleType, setYAxisScaleType] = useState<ScaleType>('linear');
|
|
@@ -47,41 +77,25 @@ export const RelativeGrowthAdvantage: FunctionComponent<RelativeGrowthAdvantageP
|
|
|
47
77
|
);
|
|
48
78
|
|
|
49
79
|
if (isLoading) {
|
|
50
|
-
return
|
|
51
|
-
<Headline heading={headline}>
|
|
52
|
-
<LoadingDisplay />
|
|
53
|
-
</Headline>
|
|
54
|
-
);
|
|
80
|
+
return <LoadingDisplay />;
|
|
55
81
|
}
|
|
56
82
|
|
|
57
83
|
if (error !== null) {
|
|
58
|
-
return
|
|
59
|
-
<Headline heading={headline}>
|
|
60
|
-
<ErrorDisplay error={error} />
|
|
61
|
-
</Headline>
|
|
62
|
-
);
|
|
84
|
+
return <ErrorDisplay error={error} />;
|
|
63
85
|
}
|
|
64
86
|
|
|
65
87
|
if (data === null) {
|
|
66
|
-
return
|
|
67
|
-
<Headline heading={headline}>
|
|
68
|
-
<NoDataDisplay />
|
|
69
|
-
</Headline>
|
|
70
|
-
);
|
|
88
|
+
return <NoDataDisplay />;
|
|
71
89
|
}
|
|
72
90
|
|
|
73
91
|
return (
|
|
74
|
-
<
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
generationTime={generationTime}
|
|
82
|
-
/>
|
|
83
|
-
</Headline>
|
|
84
|
-
</ResizeContainer>
|
|
92
|
+
<RelativeGrowthAdvantageTabs
|
|
93
|
+
data={data}
|
|
94
|
+
yAxisScaleType={yAxisScaleType}
|
|
95
|
+
setYAxisScaleType={setYAxisScaleType}
|
|
96
|
+
views={views}
|
|
97
|
+
generationTime={generationTime}
|
|
98
|
+
/>
|
|
85
99
|
);
|
|
86
100
|
};
|
|
87
101
|
|
|
@@ -33,6 +33,29 @@ const meta: Meta<TextInputProps> = {
|
|
|
33
33
|
},
|
|
34
34
|
},
|
|
35
35
|
decorators: [withActions],
|
|
36
|
+
argTypes: {
|
|
37
|
+
lapisField: {
|
|
38
|
+
control: {
|
|
39
|
+
type: 'select',
|
|
40
|
+
},
|
|
41
|
+
options: ['host'],
|
|
42
|
+
},
|
|
43
|
+
placeholderText: {
|
|
44
|
+
control: {
|
|
45
|
+
type: 'text',
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
initialValue: {
|
|
49
|
+
control: {
|
|
50
|
+
type: 'text',
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
width: {
|
|
54
|
+
control: {
|
|
55
|
+
type: 'text',
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
},
|
|
36
59
|
};
|
|
37
60
|
|
|
38
61
|
export default meta;
|
|
@@ -44,12 +67,15 @@ export const Default: StoryObj<TextInputProps> = {
|
|
|
44
67
|
lapisField={args.lapisField}
|
|
45
68
|
placeholderText={args.placeholderText}
|
|
46
69
|
initialValue={args.initialValue}
|
|
70
|
+
width={args.width}
|
|
47
71
|
/>
|
|
48
72
|
</LapisUrlContext.Provider>
|
|
49
73
|
),
|
|
50
74
|
args: {
|
|
51
75
|
lapisField: 'host',
|
|
52
76
|
placeholderText: 'Enter a host name',
|
|
77
|
+
initialValue: '',
|
|
78
|
+
width: '100%',
|
|
53
79
|
},
|
|
54
80
|
};
|
|
55
81
|
|
|
@@ -3,18 +3,40 @@ import { useContext, useRef } from 'preact/hooks';
|
|
|
3
3
|
|
|
4
4
|
import { fetchAutocompleteList } from './fetchAutocompleteList';
|
|
5
5
|
import { LapisUrlContext } from '../LapisUrlContext';
|
|
6
|
+
import { ErrorBoundary } from '../components/error-boundary';
|
|
6
7
|
import { ErrorDisplay } from '../components/error-display';
|
|
7
8
|
import { LoadingDisplay } from '../components/loading-display';
|
|
8
9
|
import { NoDataDisplay } from '../components/no-data-display';
|
|
10
|
+
import { ResizeContainer } from '../components/resize-container';
|
|
9
11
|
import { useQuery } from '../useQuery';
|
|
10
12
|
|
|
11
|
-
export interface
|
|
13
|
+
export interface TextInputInnerProps {
|
|
12
14
|
lapisField: string;
|
|
13
15
|
placeholderText?: string;
|
|
14
16
|
initialValue?: string;
|
|
15
17
|
}
|
|
16
18
|
|
|
17
|
-
export
|
|
19
|
+
export interface TextInputProps extends TextInputInnerProps {
|
|
20
|
+
width: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const TextInput: FunctionComponent<TextInputProps> = ({ width, lapisField, placeholderText, initialValue }) => {
|
|
24
|
+
const size = { width, height: '3rem' };
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<ErrorBoundary size={size}>
|
|
28
|
+
<ResizeContainer size={size}>
|
|
29
|
+
<TextInputInner lapisField={lapisField} placeholderText={placeholderText} initialValue={initialValue} />
|
|
30
|
+
</ResizeContainer>
|
|
31
|
+
</ErrorBoundary>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const TextInputInner: FunctionComponent<TextInputInnerProps> = ({
|
|
36
|
+
lapisField,
|
|
37
|
+
placeholderText,
|
|
38
|
+
initialValue,
|
|
39
|
+
}) => {
|
|
18
40
|
const lapis = useContext(LapisUrlContext);
|
|
19
41
|
|
|
20
42
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
@@ -58,7 +80,7 @@ export const TextInput: FunctionComponent<TextInputProps> = ({ lapisField, place
|
|
|
58
80
|
<>
|
|
59
81
|
<input
|
|
60
82
|
type='text'
|
|
61
|
-
class='input input-bordered'
|
|
83
|
+
class='input input-bordered w-full'
|
|
62
84
|
placeholder={placeholderText !== undefined ? placeholderText : lapisField}
|
|
63
85
|
onInput={onInput}
|
|
64
86
|
ref={inputRef}
|
|
@@ -23,7 +23,7 @@ export type PrevalenceOverTimeVariantData = {
|
|
|
23
23
|
|
|
24
24
|
export function queryPrevalenceOverTime(
|
|
25
25
|
numeratorFilter: NamedLapisFilter | NamedLapisFilter[],
|
|
26
|
-
denominatorFilter:
|
|
26
|
+
denominatorFilter: LapisFilter,
|
|
27
27
|
granularity: TemporalGranularity,
|
|
28
28
|
smoothingWindow: number,
|
|
29
29
|
lapis: string,
|
|
@@ -31,10 +31,10 @@ export function queryPrevalenceOverTime(
|
|
|
31
31
|
): Promise<PrevalenceOverTimeData> {
|
|
32
32
|
const numeratorFilters = makeArray(numeratorFilter);
|
|
33
33
|
|
|
34
|
-
const denominatorData = fetchAndPrepare(
|
|
34
|
+
const denominatorData = fetchAndPrepare(denominatorFilter, granularity, smoothingWindow);
|
|
35
35
|
const subQueries = numeratorFilters.map(async (namedLapisFilter) => {
|
|
36
|
-
const { displayName,
|
|
37
|
-
const numeratorData = fetchAndPrepare(
|
|
36
|
+
const { displayName, lapisFilter } = namedLapisFilter;
|
|
37
|
+
const numeratorData = fetchAndPrepare(lapisFilter, granularity, smoothingWindow);
|
|
38
38
|
const divide = new DivisionOperator(
|
|
39
39
|
numeratorData,
|
|
40
40
|
denominatorData,
|
|
@@ -53,12 +53,6 @@ export function queryPrevalenceOverTime(
|
|
|
53
53
|
return Promise.all(subQueries);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
function getFilter(filter: NamedLapisFilter) {
|
|
57
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
58
|
-
const { displayName, ...filterWithoutDisplayName } = filter;
|
|
59
|
-
return filterWithoutDisplayName;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
56
|
function makeArray<T>(arrayOrSingleItem: T | T[]) {
|
|
63
57
|
if (Array.isArray(arrayOrSingleItem)) {
|
|
64
58
|
return arrayOrSingleItem;
|
package/src/types.ts
CHANGED
|
@@ -2,7 +2,10 @@ import { type Deletion, type Insertion, type Substitution } from './utils/mutati
|
|
|
2
2
|
|
|
3
3
|
export type LapisFilter = Record<string, string | number | null | boolean>;
|
|
4
4
|
|
|
5
|
-
export type NamedLapisFilter =
|
|
5
|
+
export type NamedLapisFilter = {
|
|
6
|
+
lapisFilter: LapisFilter;
|
|
7
|
+
displayName: string;
|
|
8
|
+
};
|
|
6
9
|
|
|
7
10
|
export type TemporalGranularity = 'day' | 'week' | 'month' | 'year';
|
|
8
11
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Meta } from '@storybook/blocks';
|
|
2
|
+
|
|
3
|
+
<Meta title='Components/Size of components' />
|
|
4
|
+
|
|
5
|
+
# Size of components
|
|
6
|
+
|
|
7
|
+
All visualization and input components can be provided with a width prop (or height, when applicable) to control the size of the component.
|
|
8
|
+
If not provided, the default width or height is used. In most cases, the default is a width of '100%' of its parent container and a predefined height.
|
|
9
|
+
|
|
10
|
+
Both width and height can be set using the css units (e.g. 'px', 'em', 'rem', '%', 'vh', 'vw', etc.).
|
|
11
|
+
By using '%', the size of the component can be controlled by the parent component.
|
|
12
|
+
|
|
13
|
+
Caution: When using '%' as a unit, the parent component should have a defined width or height.
|
|
@@ -25,7 +25,6 @@ const meta: Meta = {
|
|
|
25
25
|
parameters: withComponentDocs({
|
|
26
26
|
fetchMock: {},
|
|
27
27
|
componentDocs: {
|
|
28
|
-
tag: 'gs-app',
|
|
29
28
|
opensShadowDom: false,
|
|
30
29
|
expectsChildren: true,
|
|
31
30
|
codeExample,
|
|
@@ -92,7 +91,7 @@ export const FailsToFetchReferenceGenome: StoryObj<{ lapis: string }> = {
|
|
|
92
91
|
const canvas = within(canvasElement);
|
|
93
92
|
|
|
94
93
|
await waitFor(() => {
|
|
95
|
-
expect(canvas.getByText('Error')).toBeVisible();
|
|
94
|
+
expect(canvas.getByText('Error', { exact: false })).toBeVisible();
|
|
96
95
|
});
|
|
97
96
|
},
|
|
98
97
|
};
|
|
@@ -27,6 +27,8 @@ import { fetchReferenceGenome } from '../lapisApi/lapisApi';
|
|
|
27
27
|
@customElement('gs-app')
|
|
28
28
|
export class App extends LitElement {
|
|
29
29
|
/**
|
|
30
|
+
* Required.
|
|
31
|
+
*
|
|
30
32
|
* The URL of the LAPIS instance that all children of this component will use.
|
|
31
33
|
*/
|
|
32
34
|
@provide({ context: lapisContext })
|
|
@@ -54,9 +56,11 @@ export class App extends LitElement {
|
|
|
54
56
|
|
|
55
57
|
override render() {
|
|
56
58
|
return this.updateReferenceGenome.render({
|
|
57
|
-
complete: () => html
|
|
58
|
-
error: () =>
|
|
59
|
-
|
|
59
|
+
complete: () => html``, // Children will be rendered in the light DOM anyway. We can't use slots without a shadow DOM.
|
|
60
|
+
error: () =>
|
|
61
|
+
html` <div class="m-2 w-full alert alert-error">
|
|
62
|
+
Error: Cannot fetch reference genome. Is LAPIS available?
|
|
63
|
+
</div>`,
|
|
60
64
|
});
|
|
61
65
|
}
|
|
62
66
|
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
PRESET_VALUE_LAST_6_MONTHS,
|
|
16
16
|
PRESET_VALUE_LAST_MONTH,
|
|
17
17
|
} from '../../preact/dateRangeSelector/date-range-selector';
|
|
18
|
-
import './date-range-selector
|
|
18
|
+
import './gs-date-range-selector';
|
|
19
19
|
import '../app';
|
|
20
20
|
import { toYYYYMMDD } from '../../preact/dateRangeSelector/dateConversion';
|
|
21
21
|
import { withinShadowRoot } from '../withinShadowRoot.story';
|
|
@@ -25,9 +25,11 @@ const codeExample = String.raw`
|
|
|
25
25
|
customSelectOptions='[{ "label": "Year 2021", "dateFrom": "2021-01-01", "dateTo": "2021-12-31" }]'
|
|
26
26
|
earliestDate="1970-01-01"
|
|
27
27
|
initialValue="${PRESET_VALUE_LAST_6_MONTHS}"
|
|
28
|
+
width="100%"
|
|
29
|
+
dateColumn="myDateColumn"
|
|
28
30
|
></gs-date-range-selector>`;
|
|
29
31
|
|
|
30
|
-
const meta: Meta<DateRangeSelectorProps<'CustomDateRange'
|
|
32
|
+
const meta: Meta<Required<DateRangeSelectorProps<'CustomDateRange'>>> = {
|
|
31
33
|
title: 'Input/DateRangeSelector',
|
|
32
34
|
component: 'gs-date-range-selector',
|
|
33
35
|
parameters: withComponentDocs({
|
|
@@ -36,7 +38,6 @@ const meta: Meta<DateRangeSelectorProps<'CustomDateRange'>> = {
|
|
|
36
38
|
},
|
|
37
39
|
fetchMock: {},
|
|
38
40
|
componentDocs: {
|
|
39
|
-
tag: 'gs-date-range-selector',
|
|
40
41
|
opensShadowDom: true,
|
|
41
42
|
expectsChildren: false,
|
|
42
43
|
codeExample,
|
|
@@ -58,11 +59,29 @@ const meta: Meta<DateRangeSelectorProps<'CustomDateRange'>> = {
|
|
|
58
59
|
'CustomDateRange',
|
|
59
60
|
],
|
|
60
61
|
},
|
|
62
|
+
dateColumn: { control: { type: 'text' } },
|
|
63
|
+
customSelectOptions: {
|
|
64
|
+
control: {
|
|
65
|
+
type: 'object',
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
earliestDate: {
|
|
69
|
+
control: {
|
|
70
|
+
type: 'text',
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
width: {
|
|
74
|
+
control: {
|
|
75
|
+
type: 'text',
|
|
76
|
+
},
|
|
77
|
+
},
|
|
61
78
|
},
|
|
62
79
|
args: {
|
|
63
80
|
customSelectOptions: [{ label: 'CustomDateRange', dateFrom: '2021-01-01', dateTo: '2021-12-31' }],
|
|
64
81
|
earliestDate: '1970-01-01',
|
|
65
82
|
initialValue: PRESET_VALUE_LAST_6_MONTHS,
|
|
83
|
+
dateColumn: 'aDateColumn',
|
|
84
|
+
width: '100%',
|
|
66
85
|
},
|
|
67
86
|
decorators: [withActions],
|
|
68
87
|
tags: ['autodocs'],
|
|
@@ -70,7 +89,7 @@ const meta: Meta<DateRangeSelectorProps<'CustomDateRange'>> = {
|
|
|
70
89
|
|
|
71
90
|
export default meta;
|
|
72
91
|
|
|
73
|
-
export const DateRangeSelectorStory: StoryObj<DateRangeSelectorProps<'CustomDateRange'
|
|
92
|
+
export const DateRangeSelectorStory: StoryObj<Required<DateRangeSelectorProps<'CustomDateRange'>>> = {
|
|
74
93
|
render: (args) =>
|
|
75
94
|
html` <gs-app lapis="${LAPIS_URL}">
|
|
76
95
|
<div class="max-w-screen-lg">
|
|
@@ -78,6 +97,8 @@ export const DateRangeSelectorStory: StoryObj<DateRangeSelectorProps<'CustomDate
|
|
|
78
97
|
.customSelectOptions=${args.customSelectOptions}
|
|
79
98
|
.earliestDate=${args.earliestDate}
|
|
80
99
|
.initialValue=${args.initialValue}
|
|
100
|
+
.width=${args.width}
|
|
101
|
+
.dateColumn=${args.dateColumn}
|
|
81
102
|
></gs-date-range-selector>
|
|
82
103
|
</div>
|
|
83
104
|
</gs-app>`,
|
|
@@ -91,5 +112,9 @@ export const DateRangeSelectorStory: StoryObj<DateRangeSelectorProps<'CustomDate
|
|
|
91
112
|
expect(dateTo()).toHaveValue(toYYYYMMDD(new Date()));
|
|
92
113
|
});
|
|
93
114
|
});
|
|
115
|
+
|
|
116
|
+
// Due to the limitations of storybook testing which does not fire an event,
|
|
117
|
+
// when selecting a value from the dropdown we can't test the fired event here.
|
|
118
|
+
// An e2e test (using playwright) for that can be found in tests/dateRangeSelector.spec.ts
|
|
94
119
|
},
|
|
95
120
|
};
|
package/src/web-components/input/{date-range-selector-component.tsx → gs-date-range-selector.tsx}
RENAMED
|
@@ -10,7 +10,8 @@ import { PreactLitAdapter } from '../PreactLitAdapter';
|
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* ## Context
|
|
13
|
-
* This component is a group of input fields designed to specify
|
|
13
|
+
* This component is a group of input fields designed to specify date range filters
|
|
14
|
+
* for a given date column of this Lapis instance. It consists of 3 fields:
|
|
14
15
|
*
|
|
15
16
|
* - a select field to choose a predefined date range,
|
|
16
17
|
* - an input field with an attached date picker for the start date,
|
|
@@ -20,12 +21,21 @@ import { PreactLitAdapter } from '../PreactLitAdapter';
|
|
|
20
21
|
* Setting a value in either of the date pickers will set the select field to "custom",
|
|
21
22
|
* which represents an arbitrary date range.
|
|
22
23
|
*
|
|
23
|
-
* @fires {CustomEvent<{
|
|
24
|
+
* @fires {CustomEvent<{ `${dateColumn}From`: string; `${dateColumn}To`: string; }>} gs-date-range-changed
|
|
24
25
|
* Fired when:
|
|
25
26
|
* - The select field is changed,
|
|
26
27
|
* - A date is selected in either of the date pickers,
|
|
27
28
|
* - A date was typed into either of the date input fields, and the input field loses focus ("on blur").
|
|
28
29
|
* Contains the dates in the format `YYYY-MM-DD`.
|
|
30
|
+
*
|
|
31
|
+
* Example: For `dateColumn = yourDate`, an event with `detail` containing
|
|
32
|
+
* ```
|
|
33
|
+
* {
|
|
34
|
+
* yourDataFrom: "2021-01-01",
|
|
35
|
+
* yourDataTo: "2021-12-31"
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
* will be fired.
|
|
29
39
|
*/
|
|
30
40
|
@customElement('gs-date-range-selector')
|
|
31
41
|
export class DateRangeSelectorComponent extends PreactLitAdapter {
|
|
@@ -42,7 +52,7 @@ export class DateRangeSelectorComponent extends PreactLitAdapter {
|
|
|
42
52
|
* The `dateFrom` value to use in the `allTimes` preset in the format `YYYY-MM-DD`.
|
|
43
53
|
*/
|
|
44
54
|
@property({ type: String })
|
|
45
|
-
earliestDate: string
|
|
55
|
+
earliestDate: string = '1900-01-01';
|
|
46
56
|
|
|
47
57
|
// prettier-ignore
|
|
48
58
|
// The multiline union type must not start with `| 'custom'` - Storybook will list "" as the first type which is wrong
|
|
@@ -61,8 +71,21 @@ export class DateRangeSelectorComponent extends PreactLitAdapter {
|
|
|
61
71
|
| 'last2Months'
|
|
62
72
|
| 'last3Months'
|
|
63
73
|
| 'last6Months'
|
|
64
|
-
| string
|
|
65
|
-
|
|
74
|
+
| string = 'last6Months';
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The width of the component.
|
|
78
|
+
*
|
|
79
|
+
* Visit https://genspectrum.github.io/dashboards/?path=/docs/components-size-of-components--docs for more information.
|
|
80
|
+
*/
|
|
81
|
+
@property({ type: String })
|
|
82
|
+
width: string = '100%';
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* The name of the column in LAPIS that contains the date information.
|
|
86
|
+
*/
|
|
87
|
+
@property({ type: String })
|
|
88
|
+
dateColumn: string = 'date';
|
|
66
89
|
|
|
67
90
|
override render() {
|
|
68
91
|
return (
|
|
@@ -70,6 +93,8 @@ export class DateRangeSelectorComponent extends PreactLitAdapter {
|
|
|
70
93
|
customSelectOptions={this.customSelectOptions}
|
|
71
94
|
earliestDate={this.earliestDate}
|
|
72
95
|
initialValue={this.initialValue}
|
|
96
|
+
dateColumn={this.dateColumn}
|
|
97
|
+
width={this.width}
|
|
73
98
|
/>
|
|
74
99
|
);
|
|
75
100
|
}
|
|
@@ -81,10 +106,7 @@ declare global {
|
|
|
81
106
|
}
|
|
82
107
|
|
|
83
108
|
interface HTMLElementEventMap {
|
|
84
|
-
'gs-date-range-changed': CustomEvent<
|
|
85
|
-
dateFrom: string;
|
|
86
|
-
dateTo: string;
|
|
87
|
-
}>;
|
|
109
|
+
'gs-date-range-changed': CustomEvent<Record<string, string>>;
|
|
88
110
|
}
|
|
89
111
|
}
|
|
90
112
|
|
|
@@ -93,6 +115,6 @@ type CustomSelectOptionsMatches = Expect<
|
|
|
93
115
|
Equals<typeof DateRangeSelectorComponent.prototype.customSelectOptions, CustomSelectOption<string>[]>
|
|
94
116
|
>;
|
|
95
117
|
type InitialValueMatches = Expect<
|
|
96
|
-
Equals<typeof DateRangeSelectorComponent.prototype.initialValue, PresetOptionValues | string
|
|
118
|
+
Equals<typeof DateRangeSelectorComponent.prototype.initialValue, PresetOptionValues | string>
|
|
97
119
|
>;
|
|
98
120
|
/* eslint-enable @typescript-eslint/no-unused-vars, no-unused-vars */
|
|
@@ -7,11 +7,18 @@ import { ifDefined } from 'lit/directives/if-defined.js';
|
|
|
7
7
|
import { withComponentDocs } from '../../../.storybook/ComponentDocsBlock';
|
|
8
8
|
import { AGGREGATED_ENDPOINT, LAPIS_URL } from '../../constants';
|
|
9
9
|
import '../app';
|
|
10
|
-
import './location-filter
|
|
10
|
+
import './gs-location-filter';
|
|
11
11
|
import data from '../../preact/locationFilter/__mockData__/aggregated.json';
|
|
12
12
|
import { type LocationFilterProps } from '../../preact/locationFilter/location-filter';
|
|
13
13
|
import { withinShadowRoot } from '../withinShadowRoot.story';
|
|
14
14
|
|
|
15
|
+
const codeExample = String.raw`
|
|
16
|
+
<gs-location-filter
|
|
17
|
+
fields='["region", "country"]'
|
|
18
|
+
initialValue='Europe / Switzerland'
|
|
19
|
+
width="100%"
|
|
20
|
+
></gs-location-filter>`;
|
|
21
|
+
|
|
15
22
|
const meta: Meta = {
|
|
16
23
|
title: 'Input/Location filter',
|
|
17
24
|
component: 'gs-location-filter',
|
|
@@ -20,12 +27,28 @@ const meta: Meta = {
|
|
|
20
27
|
handles: ['gs-location-changed'],
|
|
21
28
|
},
|
|
22
29
|
componentDocs: {
|
|
23
|
-
tag: 'gs-location-filter',
|
|
24
30
|
opensShadowDom: true,
|
|
25
31
|
expectsChildren: false,
|
|
26
|
-
codeExample
|
|
32
|
+
codeExample,
|
|
27
33
|
},
|
|
28
34
|
}),
|
|
35
|
+
argTypes: {
|
|
36
|
+
fields: {
|
|
37
|
+
control: {
|
|
38
|
+
type: 'object',
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
initialValue: {
|
|
42
|
+
control: {
|
|
43
|
+
type: 'text',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
width: {
|
|
47
|
+
control: {
|
|
48
|
+
type: 'text',
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
29
52
|
decorators: [withActions],
|
|
30
53
|
tags: ['autodocs'],
|
|
31
54
|
};
|
|
@@ -39,6 +62,7 @@ const Template: StoryObj<LocationFilterProps> = {
|
|
|
39
62
|
<gs-location-filter
|
|
40
63
|
.fields=${args.fields}
|
|
41
64
|
initialValue=${ifDefined(args.initialValue)}
|
|
65
|
+
.width=${args.width}
|
|
42
66
|
></gs-location-filter>
|
|
43
67
|
</div>
|
|
44
68
|
</gs-app>`;
|
|
@@ -46,6 +70,7 @@ const Template: StoryObj<LocationFilterProps> = {
|
|
|
46
70
|
args: {
|
|
47
71
|
fields: ['region', 'country', 'division', 'location'],
|
|
48
72
|
initialValue: '',
|
|
73
|
+
width: '100%',
|
|
49
74
|
},
|
|
50
75
|
};
|
|
51
76
|
|
|
@@ -109,7 +134,9 @@ export const FetchingLocationsFails: StoryObj<LocationFilterProps> = {
|
|
|
109
134
|
matcher: aggregatedEndpointMatcher,
|
|
110
135
|
response: {
|
|
111
136
|
status: 400,
|
|
112
|
-
body: {
|
|
137
|
+
body: {
|
|
138
|
+
error: { status: 400, detail: 'Dummy error message from mock LAPIS', type: 'about:blank' },
|
|
139
|
+
},
|
|
113
140
|
},
|
|
114
141
|
},
|
|
115
142
|
],
|
|
@@ -119,7 +146,7 @@ export const FetchingLocationsFails: StoryObj<LocationFilterProps> = {
|
|
|
119
146
|
const canvas = await withinShadowRoot(canvasElement, 'gs-location-filter');
|
|
120
147
|
|
|
121
148
|
await waitFor(() =>
|
|
122
|
-
expect(canvas.getByText('
|
|
149
|
+
expect(canvas.getByText('Oops! Something went wrong.', { exact: false })).toBeInTheDocument(),
|
|
123
150
|
);
|
|
124
151
|
},
|
|
125
152
|
};
|
|
@@ -40,6 +40,8 @@ export class LocationFilterComponent extends PreactLitAdapter {
|
|
|
40
40
|
initialValue = '';
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
|
+
* Required.
|
|
44
|
+
*
|
|
43
45
|
* The fields to display in the location filter, in hierarchical order.
|
|
44
46
|
* The top-level field should be the first entry in the array.
|
|
45
47
|
* This component assumes that the values for each field form a strict hierarchy
|
|
@@ -48,8 +50,16 @@ export class LocationFilterComponent extends PreactLitAdapter {
|
|
|
48
50
|
@property({ type: Array })
|
|
49
51
|
fields: string[] = [];
|
|
50
52
|
|
|
53
|
+
/**
|
|
54
|
+
* The width of the component.
|
|
55
|
+
*
|
|
56
|
+
* Visit https://genspectrum.github.io/dashboards/?path=/docs/components-size-of-components--docs for more information.
|
|
57
|
+
*/
|
|
58
|
+
@property({ type: String })
|
|
59
|
+
width: string = '100%';
|
|
60
|
+
|
|
51
61
|
override render() {
|
|
52
|
-
return <LocationFilter initialValue={this.initialValue} fields={this.fields} />;
|
|
62
|
+
return <LocationFilter initialValue={this.initialValue} fields={this.fields} width={this.width} />;
|
|
53
63
|
}
|
|
54
64
|
}
|
|
55
65
|
|