@genspectrum/dashboard-components 1.1.0 → 1.2.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.
Files changed (97) hide show
  1. package/custom-elements.json +2 -2
  2. package/dist/{NumberRangeFilterChangedEvent-B64OQZjX.js → NumberRangeFilterChangedEvent-CQ32Qy8D.js} +2 -2
  3. package/dist/NumberRangeFilterChangedEvent-CQ32Qy8D.js.map +1 -0
  4. package/dist/assets/mutationOverTimeWorker-DpW4YOGl.js.map +1 -0
  5. package/dist/components.d.ts +31 -31
  6. package/dist/components.js +74 -120
  7. package/dist/components.js.map +1 -1
  8. package/dist/util.d.ts +31 -31
  9. package/dist/util.js +1 -1
  10. package/package.json +7 -3
  11. package/src/lapisApi/lapisApi.ts +2 -2
  12. package/src/operator/DivisionOperator.ts +4 -2
  13. package/src/operator/FetchDetailsOperator.ts +1 -1
  14. package/src/operator/RenameFieldOperator.ts +3 -3
  15. package/src/preact/aggregatedData/aggregate.tsx +0 -5
  16. package/src/preact/components/annotated-mutation.tsx +0 -1
  17. package/src/preact/components/clearable-select.stories.tsx +1 -1
  18. package/src/preact/components/confidence-interval-selector.tsx +1 -1
  19. package/src/preact/components/error-boundary.tsx +1 -5
  20. package/src/preact/components/error-display.tsx +1 -1
  21. package/src/preact/components/fullscreen.tsx +2 -5
  22. package/src/preact/components/info.stories.tsx +1 -1
  23. package/src/preact/components/min-max-range-slider.tsx +1 -1
  24. package/src/preact/components/proportion-selector.tsx +4 -4
  25. package/src/preact/components/select.tsx +1 -1
  26. package/src/preact/components/table.tsx +1 -1
  27. package/src/preact/components/tabs.tsx +1 -1
  28. package/src/preact/components/tooltip.stories.tsx +1 -1
  29. package/src/preact/components/tooltip.tsx +1 -1
  30. package/src/preact/genomeViewer/CDSPlot.tsx +3 -3
  31. package/src/preact/genomeViewer/loadGff3.ts +5 -8
  32. package/src/preact/lineageFilter/lineage-filter.tsx +1 -1
  33. package/src/preact/locationFilter/location-filter.tsx +4 -4
  34. package/src/preact/mutationComparison/getMutationComparisonTableData.ts +1 -3
  35. package/src/preact/mutationComparison/mutation-comparison-venn.tsx +1 -1
  36. package/src/preact/mutationComparison/mutation-comparison.tsx +0 -5
  37. package/src/preact/mutationFilter/mutation-filter-info.tsx +2 -2
  38. package/src/preact/mutationFilter/mutation-filter.tsx +1 -1
  39. package/src/preact/mutations/getMutationsGridData.ts +2 -6
  40. package/src/preact/mutations/getMutationsTableData.ts +1 -1
  41. package/src/preact/mutations/mutations-grid.tsx +1 -1
  42. package/src/preact/mutations/mutations.tsx +0 -5
  43. package/src/preact/mutationsOverTime/mutations-over-time.tsx +3 -9
  44. package/src/preact/numberRangeFilter/number-range-filter.tsx +4 -4
  45. package/src/preact/numberSequencesOverTime/getNumberOfSequencesOverTimeTableData.ts +1 -4
  46. package/src/preact/numberSequencesOverTime/number-sequences-over-time.tsx +0 -5
  47. package/src/preact/prevalenceOverTime/prevalence-over-time-bar-chart.tsx +1 -1
  48. package/src/preact/prevalenceOverTime/prevalence-over-time-bubble-chart.tsx +1 -1
  49. package/src/preact/prevalenceOverTime/prevalence-over-time-line-chart.tsx +1 -1
  50. package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +1 -1
  51. package/src/preact/relativeGrowthAdvantage/relative-growth-advantage-chart.tsx +5 -5
  52. package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.tsx +1 -4
  53. package/src/preact/sequencesByLocation/loadMapSource.tsx +5 -2
  54. package/src/preact/shared/aspectRatio/AspectRatio.tsx +1 -1
  55. package/src/preact/shared/floating-ui/hooks.ts +2 -2
  56. package/src/preact/shared/sort/sortMutationPositions.ts +2 -2
  57. package/src/preact/shared/tanstackTable/pagination.tsx +2 -2
  58. package/src/preact/shared/tanstackTable/tanstackTable.tsx +1 -1
  59. package/src/preact/statistic/statistics.tsx +0 -5
  60. package/src/preact/textFilter/fetchStringAutocompleteList.ts +1 -10
  61. package/src/preact/textFilter/text-filter.tsx +1 -6
  62. package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.tsx +1 -1
  63. package/src/preact/webWorkers/useWebWorker.ts +2 -1
  64. package/src/preact/webWorkers/workerFunction.ts +2 -2
  65. package/src/query/computeMapLocationData.ts +1 -1
  66. package/src/query/queryAggregatedDataOverTime.ts +3 -3
  67. package/src/query/queryMutationsOverTime.spec.ts +9 -9
  68. package/src/query/queryMutationsOverTime.ts +22 -16
  69. package/src/query/queryRelativeGrowthAdvantage.ts +5 -9
  70. package/src/query/queryWastewaterMutationsOverTime.ts +1 -1
  71. package/src/types.ts +1 -1
  72. package/src/utils/mutations.ts +10 -10
  73. package/src/utils/type-utils.ts +1 -1
  74. package/src/utils/typeAssertions.spec.ts +1 -1
  75. package/src/web-components/gs-app.spec-d.ts +1 -1
  76. package/src/web-components/gs-app.stories.ts +1 -1
  77. package/src/web-components/input/gs-date-range-filter.tsx +2 -2
  78. package/src/web-components/input/gs-lineage-filter.tsx +2 -2
  79. package/src/web-components/input/gs-location-filter.tsx +3 -3
  80. package/src/web-components/input/gs-mutation-filter.tsx +2 -2
  81. package/src/web-components/input/gs-number-range-filter.spec.ts +1 -1
  82. package/src/web-components/input/gs-text-filter.tsx +2 -2
  83. package/src/web-components/visualization/gs-aggregate.tsx +2 -2
  84. package/src/web-components/visualization/gs-genome-data-viewer.spec-d.ts +1 -1
  85. package/src/web-components/visualization/gs-mutation-comparison.tsx +2 -2
  86. package/src/web-components/visualization/gs-mutations.tsx +2 -2
  87. package/src/web-components/visualization/gs-number-sequences-over-time.tsx +2 -2
  88. package/src/web-components/visualization/gs-prevalence-over-time.tsx +2 -2
  89. package/src/web-components/visualization/gs-relative-growth-advantage.tsx +2 -2
  90. package/src/web-components/visualization/gs-sequences-by-location.tsx +2 -2
  91. package/src/web-components/visualization/gs-statistics.tsx +2 -2
  92. package/standalone-bundle/assets/mutationOverTimeWorker-CZVvQBze.js.map +1 -0
  93. package/standalone-bundle/dashboard-components.js +1444 -1456
  94. package/standalone-bundle/dashboard-components.js.map +1 -1
  95. package/dist/NumberRangeFilterChangedEvent-B64OQZjX.js.map +0 -1
  96. package/dist/assets/mutationOverTimeWorker-DjH04AQB.js.map +0 -1
  97. package/standalone-bundle/assets/mutationOverTimeWorker-B6bf3R3j.js.map +0 -1
@@ -110,8 +110,8 @@ function filterByInputValue(item: SelectItem, inputValue: string | null) {
110
110
  return true;
111
111
  }
112
112
  return (
113
- item?.label?.toLowerCase().includes(inputValue.toLowerCase()) ||
114
- item?.description.toLowerCase().includes(inputValue.toLowerCase())
113
+ !!item.label?.toLowerCase().includes(inputValue.toLowerCase()) ||
114
+ item.description.toLowerCase().includes(inputValue.toLowerCase())
115
115
  );
116
116
  }
117
117
 
@@ -143,8 +143,8 @@ function concatenateLocation(locationFilter: LapisLocationFilter, fields: string
143
143
  }
144
144
 
145
145
  function emptyLocationFilter(fields: string[]) {
146
- return fields.reduce((acc, field) => {
146
+ return fields.reduce<LapisLocationFilter>((acc, field) => {
147
147
  acc[field] = undefined;
148
148
  return acc;
149
- }, {} as LapisLocationFilter);
149
+ }, {});
150
150
  }
@@ -3,9 +3,7 @@ import { type Dataset } from '../../operator/Dataset';
3
3
  import { type DeletionClass, type SubstitutionClass } from '../../utils/mutations';
4
4
  import { type ProportionInterval } from '../components/proportion-selector';
5
5
 
6
- type Proportions = {
7
- [displayName: string]: number;
8
- };
6
+ type Proportions = Record<string, number>;
9
7
 
10
8
  type MutationComparisonRow = {
11
9
  mutation: SubstitutionClass | DeletionClass;
@@ -110,7 +110,7 @@ export const MutationComparisonVenn: FunctionComponent<MutationComparisonVennPro
110
110
  <div className='flex-1'>
111
111
  <GsChart configuration={config} />
112
112
  </div>
113
- <p class='flex flex-wrap break-words m-2'>
113
+ <p className='flex flex-wrap break-words m-2'>
114
114
  <SelectedMutationsDescription
115
115
  selectedDatasetIndex={selectedDatasetIndex}
116
116
  sets={sets}
@@ -16,7 +16,6 @@ import Info, { InfoComponentCode, InfoHeadline1, InfoHeadline2, InfoParagraph }
16
16
  import { LoadingDisplay } from '../components/loading-display';
17
17
  import { DeletionsLink, ProportionExplanation, SubstitutionsLink } from '../components/mutation-info';
18
18
  import { type DisplayedMutationType, MutationTypeSelector } from '../components/mutation-type-selector';
19
- import { NoDataDisplay } from '../components/no-data-display';
20
19
  import { type ProportionInterval } from '../components/proportion-selector';
21
20
  import { ProportionSelectorDropdown } from '../components/proportion-selector-dropdown';
22
21
  import { ResizeContainer } from '../components/resize-container';
@@ -68,10 +67,6 @@ const MutationComparisonInner: FunctionComponent<MutationComparisonProps> = (com
68
67
  throw error;
69
68
  }
70
69
 
71
- if (data === null) {
72
- return <NoDataDisplay />;
73
- }
74
-
75
70
  return <MutationComparisonTabs data={data.mutationData} originalComponentProps={componentProps} />;
76
71
  };
77
72
 
@@ -191,10 +191,10 @@ const exampleSegmentString = (referenceGenome: ReferenceGenome) => {
191
191
 
192
192
  const exampleSegment = (referenceGenome: ReferenceGenome) => {
193
193
  if (referenceGenome.genes.length > 0) {
194
- return `${referenceGenome.genes[0].name}`;
194
+ return referenceGenome.genes[0].name;
195
195
  }
196
196
  if (referenceGenome.nucleotideSequences.length > 1) {
197
- return `${referenceGenome.nucleotideSequences[0].name}`;
197
+ return referenceGenome.nucleotideSequences[0].name;
198
198
  }
199
199
  return '';
200
200
  };
@@ -112,7 +112,7 @@ function MutationFilterInner({ initialValue }: MutationFilterInnerProps) {
112
112
  const handleInputChange = (newInputValue: string | undefined) => {
113
113
  setShowErrorIndicator(false);
114
114
  if (newInputValue?.includes(',')) {
115
- const values = newInputValue?.split(',').map((value) => {
115
+ const values = newInputValue.split(',').map((value) => {
116
116
  return { value, parsedValue: parseAndValidateMutation(value.trim(), referenceGenome) };
117
117
  });
118
118
  const validEntries = values.map((value) => value.parsedValue).filter((value) => value !== null);
@@ -18,9 +18,7 @@ const accumulateByPosition = (data: SubstitutionOrDeletionEntry[], sequenceType:
18
18
  const referenceBases = new Map<string, string | undefined>();
19
19
 
20
20
  for (const mutationEntry of data) {
21
- const position =
22
- (mutationEntry.mutation.segment ? `${mutationEntry.mutation.segment}:` : '') +
23
- mutationEntry.mutation.position;
21
+ const position = `${mutationEntry.mutation.segment ? `${mutationEntry.mutation.segment}:` : ''}${mutationEntry.mutation.position}`;
24
22
  referenceBases.set(position, mutationEntry.mutation.valueAtReference);
25
23
 
26
24
  const initiallyFillPositionsToProportionAtBase = () => {
@@ -69,9 +67,7 @@ const accumulateByPosition = (data: SubstitutionOrDeletionEntry[], sequenceType:
69
67
  });
70
68
  };
71
69
 
72
- export type BasesData = {
73
- [base: string]: BaseCell;
74
- };
70
+ export type BasesData = Record<string, BaseCell>;
75
71
  export type MutationsGridDataRow = BasesData & { position: string };
76
72
 
77
73
  const byProportion = (row: MutationsGridDataRow, proportionInterval: ProportionInterval) => {
@@ -30,7 +30,7 @@ export function getMutationsTableData(
30
30
  }, new Map<string, number>());
31
31
 
32
32
  return tableData.map((datum) => {
33
- const baselineMutationCount = baselineMutationCounts.get(datum.mutation.code) || 0;
33
+ const baselineMutationCount = baselineMutationCounts.get(datum.mutation.code) ?? 0;
34
34
  const jaccardSimilarity = calculateJaccardSimilarity(overallVariantCount, baselineMutationCount, datum.count);
35
35
 
36
36
  return {
@@ -62,7 +62,7 @@ export const MutationsGrid: FunctionComponent<MutationsGridProps> = ({
62
62
  },
63
63
  formatter: (cell: BaseCell) => formatProportion(cell.proportion),
64
64
  attributes: (cell: BaseCell, row: Row) => {
65
- // grid-js: the cell and row are null for header cells
65
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- grid-js: the cell and row are null for header cells
66
66
  if (row === null) {
67
67
  return {};
68
68
  }
@@ -24,7 +24,6 @@ import Info, { InfoComponentCode, InfoHeadline1, InfoParagraph } from '../compon
24
24
  import { LoadingDisplay } from '../components/loading-display';
25
25
  import { DeletionsLink, InsertionsLink, ProportionExplanation, SubstitutionsLink } from '../components/mutation-info';
26
26
  import { type DisplayedMutationType, MutationTypeSelector } from '../components/mutation-type-selector';
27
- import { NoDataDisplay } from '../components/no-data-display';
28
27
  import type { ProportionInterval } from '../components/proportion-selector';
29
28
  import { ProportionSelectorDropdown } from '../components/proportion-selector-dropdown';
30
29
  import { ResizeContainer } from '../components/resize-container';
@@ -75,10 +74,6 @@ export const MutationsInner: FunctionComponent<MutationsProps> = (componentProps
75
74
  throw error;
76
75
  }
77
76
 
78
- if (data === null) {
79
- return <NoDataDisplay />;
80
- }
81
-
82
77
  return <MutationsTabs mutationsData={data} originalComponentProps={componentProps} />;
83
78
  };
84
79
 
@@ -12,7 +12,6 @@ import {
12
12
  } from './getFilteredMutationsOverTimeData';
13
13
  import { type MutationOverTimeWorkerResponse } from './mutationOverTimeWorker';
14
14
  import MutationsOverTimeGrid from './mutations-over-time-grid';
15
- import { type MutationOverTimeQuery } from '../../query/queryMutationsOverTime';
16
15
  import {
17
16
  lapisFilterSchema,
18
17
  sequenceTypeSchema,
@@ -91,7 +90,7 @@ export const MutationsOverTimeInner: FunctionComponent<MutationsOverTimeProps> =
91
90
  };
92
91
  }, [granularity, lapis, lapisDateField, lapisFilter, sequenceType]);
93
92
 
94
- const { data, error, isLoading } = useWebWorker<MutationOverTimeQuery, MutationOverTimeWorkerResponse>(
93
+ const { data, error, isLoading } = useWebWorker<MutationOverTimeWorkerResponse>(
95
94
  messageToWorker,
96
95
  // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
97
96
  MutationOverTimeWorker,
@@ -105,7 +104,7 @@ export const MutationsOverTimeInner: FunctionComponent<MutationsOverTimeProps> =
105
104
  throw error;
106
105
  }
107
106
 
108
- if (data === null || data === undefined || data.overallMutationData.length === 0) {
107
+ if (data === undefined || data.overallMutationData.length === 0) {
109
108
  return <NoDataDisplay />;
110
109
  }
111
110
 
@@ -173,13 +172,8 @@ const MutationsOverTimeTabs: FunctionComponent<MutationOverTimeTabsProps> = ({
173
172
  ]);
174
173
 
175
174
  const getTab = (view: MutationsOverTimeView) => {
176
- if (filteredData === undefined) {
177
- return {
178
- title: 'Calculating',
179
- content: <LoadingDisplay />,
180
- };
181
- }
182
175
  switch (view) {
176
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- for extensibility
183
177
  case 'grid':
184
178
  return {
185
179
  title: 'Grid',
@@ -90,10 +90,10 @@ const NumberRangeFilterInner: FunctionComponent<NumberRangeFilterInnerProps> = (
90
90
  <div className={`join-item w-full flex input px-2 ${inputError}`}>
91
91
  <input
92
92
  type='text'
93
- inputmode='numeric'
93
+ inputMode='numeric'
94
94
  className='w-full grow capitalize'
95
95
  placeholder={`${lapisField} from`}
96
- value={currentRange.inputState.min ?? ''}
96
+ value={currentRange.inputState.min}
97
97
  onInput={(e) => {
98
98
  dispatchRange({
99
99
  type: SetRangeActionType.SET_MIN,
@@ -116,10 +116,10 @@ const NumberRangeFilterInner: FunctionComponent<NumberRangeFilterInnerProps> = (
116
116
  <div className={`join-item w-full flex input px-2 ${inputError}`}>
117
117
  <input
118
118
  type='text'
119
- inputmode='numeric'
119
+ inputMode='numeric'
120
120
  className='w-full grow capitalize'
121
121
  placeholder={`${lapisField} to`}
122
- value={currentRange.inputState.max ?? ''}
122
+ value={currentRange.inputState.max}
123
123
  onInput={(e) => {
124
124
  dispatchRange({
125
125
  type: SetRangeActionType.SET_MAX,
@@ -1,7 +1,7 @@
1
1
  import type { NumberOfSequencesDatasets } from '../../query/queryNumberOfSequencesOverTime';
2
2
  import { generateAllInRange, getMinMaxTemporal, type TemporalClass } from '../../utils/temporalClass';
3
3
 
4
- type TableRow<DateRangeKey extends string> = { [K in DateRangeKey]: string } & { [key: string]: number };
4
+ type TableRow<DateRangeKey extends string> = Record<DateRangeKey, string> & Record<string, number>;
5
5
 
6
6
  export const getNumberOfSequencesOverTimeTableData = <DateRangeKey extends string>(
7
7
  data: NumberOfSequencesDatasets,
@@ -17,9 +17,6 @@ export const getNumberOfSequencesOverTimeTableData = <DateRangeKey extends strin
17
17
  .reduce((acc, keys) => new Set([...acc, ...keys]), new Set<TemporalClass | null>());
18
18
 
19
19
  const minMax = getMinMaxTemporal(allDateRangesThatOccurInData);
20
- if (minMax === null) {
21
- return [];
22
- }
23
20
 
24
21
  const allDateRanges: (TemporalClass | null)[] = generateAllInRange(minMax.min, minMax.max);
25
22
 
@@ -18,7 +18,6 @@ import { ErrorBoundary } from '../components/error-boundary';
18
18
  import { Fullscreen } from '../components/fullscreen';
19
19
  import Info, { InfoComponentCode, InfoHeadline1, InfoParagraph } from '../components/info';
20
20
  import { LoadingDisplay } from '../components/loading-display';
21
- import { NoDataDisplay } from '../components/no-data-display';
22
21
  import { ResizeContainer } from '../components/resize-container';
23
22
  import { ScalingSelector } from '../components/scaling-selector';
24
23
  import Tabs from '../components/tabs';
@@ -76,10 +75,6 @@ const NumberSequencesOverTimeInner = (componentProps: NumberSequencesOverTimePro
76
75
  throw error;
77
76
  }
78
77
 
79
- if (data === null) {
80
- return <NoDataDisplay />;
81
- }
82
-
83
78
  return <NumberSequencesOverTimeTabs data={data} originalComponentProps={componentProps} />;
84
79
  };
85
80
 
@@ -56,7 +56,7 @@ const PrevalenceOverTimeBarChart = ({
56
56
 
57
57
  const maxY =
58
58
  yAxisScaleType !== 'logit'
59
- ? getYAxisMax(maxInData(nullFirstData), yAxisMaxConfig?.[yAxisScaleType])
59
+ ? getYAxisMax(maxInData(nullFirstData), yAxisMaxConfig[yAxisScaleType])
60
60
  : undefined;
61
61
 
62
62
  return {
@@ -54,7 +54,7 @@ const PrevalenceOverTimeBubbleChart = ({
54
54
 
55
55
  const maxY =
56
56
  yAxisScaleType !== 'logit'
57
- ? getYAxisMax(maxInData(nonNullDateRangeData), yAxisMaxConfig?.[yAxisScaleType])
57
+ ? getYAxisMax(maxInData(nonNullDateRangeData), yAxisMaxConfig[yAxisScaleType])
58
58
  : undefined;
59
59
 
60
60
  return {
@@ -55,7 +55,7 @@ const PrevalenceOverTimeLineChart = ({
55
55
 
56
56
  const maxY =
57
57
  yAxisScaleType !== 'logit'
58
- ? getYAxisMax(maxInData(nonNullDateRangeData), yAxisMaxConfig?.[yAxisScaleType])
58
+ ? getYAxisMax(maxInData(nonNullDateRangeData), yAxisMaxConfig[yAxisScaleType])
59
59
  : undefined;
60
60
 
61
61
  return {
@@ -90,7 +90,7 @@ export const PrevalenceOverTimeInner: FunctionComponent<PrevalenceOverTimeProps>
90
90
  throw error;
91
91
  }
92
92
 
93
- if (data === null || data.every((variant) => variant.content.length === 0)) {
93
+ if (data.every((variant) => variant.content.length === 0)) {
94
94
  return <NoDataDisplay />;
95
95
  }
96
96
 
@@ -43,7 +43,7 @@ const RelativeGrowthAdvantageChart = ({
43
43
  const config = useMemo<ChartConfiguration>(() => {
44
44
  const maxY =
45
45
  yAxisScaleType !== 'logit'
46
- ? getYAxisMax(Math.max(...data.proportion), yAxisMaxConfig?.[yAxisScaleType])
46
+ ? getYAxisMax(Math.max(...data.proportion), yAxisMaxConfig[yAxisScaleType])
47
47
  : undefined;
48
48
 
49
49
  return {
@@ -92,11 +92,11 @@ const RelativeGrowthAdvantageDisplay = ({
92
92
  relativeAdvantageUpperBound: number;
93
93
  }) => {
94
94
  return (
95
- <div class='mx-auto flex items-end flex-wrap'>
96
- <span class='text-[#606060]'>Relative advantage:</span>
95
+ <div className='mx-auto flex items-end flex-wrap'>
96
+ <span className='text-[#606060]'>Relative advantage:</span>
97
97
  <div>
98
- <span class='text-2xl ml-3'> {formatProportion(relativeAdvantage)} </span>
99
- <span class='ml-2.5'>
98
+ <span className='text-2xl ml-3'> {formatProportion(relativeAdvantage)} </span>
99
+ <span className='ml-2.5'>
100
100
  ({formatProportion(relativeAdvantageLowerBound)} - {formatProportion(relativeAdvantageUpperBound)})
101
101
  </span>
102
102
  </div>
@@ -77,10 +77,6 @@ export const RelativeGrowthAdvantageInner: FunctionComponent<RelativeGrowthAdvan
77
77
  throw error;
78
78
  }
79
79
 
80
- if (data === null) {
81
- return <NoDataDisplay />;
82
- }
83
-
84
80
  return (
85
81
  <RelativeGrowthAdvantageTabs
86
82
  data={data}
@@ -110,6 +106,7 @@ const RelativeGrowthAdvantageTabs: FunctionComponent<RelativeGrowthAdvantageTabs
110
106
 
111
107
  const getTab = (view: RelativeGrowthAdvantageView) => {
112
108
  switch (view) {
109
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- for extensibility
113
110
  case 'line':
114
111
  return {
115
112
  title: 'Line',
@@ -18,6 +18,7 @@ export type GeoJsonFeatureProperties = {
18
18
 
19
19
  export async function loadMapSource(mapSource: MapSource) {
20
20
  switch (mapSource.type) {
21
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- for future extensibility
21
22
  case 'topojson':
22
23
  return await loadTopojsonMap(mapSource);
23
24
  }
@@ -28,17 +29,19 @@ async function loadTopojsonMap(
28
29
  ): Promise<FeatureCollection<GeometryObject, GeoJsonFeatureProperties>> {
29
30
  const response = await fetch(mapSource.url);
30
31
  const topology = (await response.json()) as Topology;
32
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- for future extensibility
31
33
  if (topology?.type !== 'Topology') {
32
34
  throw new UserFacingError(
33
35
  'Invalid map source',
34
36
  `JSON downloaded from ${mapSource.url} does not look like a topojson Topology definition: missing 'type: "Topology"', got '${JSON.stringify(topology).substring(0, 100)}'`,
35
37
  );
36
38
  }
37
- const object = topology?.objects[mapSource.topologyObjectsKey] as GeometryCollection<GeoJsonFeatureProperties>;
39
+ const object = topology.objects[mapSource.topologyObjectsKey] as GeometryCollection<GeoJsonFeatureProperties>;
40
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- to be safe
38
41
  if (object?.type !== 'GeometryCollection') {
39
42
  throw new UserFacingError(
40
43
  'Invalid map source',
41
- `JSON downloaded from ${mapSource.url} does not have a GeometryCollection at key objects.${mapSource.topologyObjectsKey}, got '${JSON.stringify(topology)?.substring(0, 100)}'`,
44
+ `JSON downloaded from ${mapSource.url} does not have a GeometryCollection at key objects.${mapSource.topologyObjectsKey}, got '${JSON.stringify(topology).substring(0, 100)}'`,
42
45
  );
43
46
  }
44
47
  return topojson.feature(topology, object);
@@ -6,7 +6,7 @@ export function AspectRatio({ children, aspectRatio }: PropsWithChildren<{ aspec
6
6
  }
7
7
 
8
8
  return (
9
- <div class={`w-full relative`} style={{ paddingTop: `${aspectRatio}%` }}>
9
+ <div className={`w-full relative`} style={{ paddingTop: `${aspectRatio}%` }}>
10
10
  <div className='absolute inset-0'>{children}</div>
11
11
  </div>
12
12
  );
@@ -6,10 +6,10 @@ import type { MutableRefObject } from 'react';
6
6
  export function useFloatingUi(
7
7
  referenceRef: MutableRefObject<HTMLElement | null>,
8
8
  floatingRef: MutableRefObject<HTMLElement | null>,
9
- middleware?: Array<Middleware | null | undefined | false>,
9
+ middleware?: (Middleware | null | undefined | false)[],
10
10
  placement?: Placement,
11
11
  ) {
12
- const cleanupRef = useRef<() => void | null>(null);
12
+ const cleanupRef = useRef<(() => void) | null>(null);
13
13
 
14
14
  useEffect(() => {
15
15
  if (!referenceRef.current || !floatingRef.current) {
@@ -1,8 +1,8 @@
1
1
  const pattern = /(?:([A-Za-z0-9]+):)?(\d+)$/;
2
2
 
3
3
  export const sortMutationPositions = (a: string, b: string) => {
4
- const aMatch = a.match(pattern);
5
- const bMatch = b.match(pattern);
4
+ const aMatch = pattern.exec(a);
5
+ const bMatch = pattern.exec(b);
6
6
 
7
7
  if (aMatch && bMatch) {
8
8
  if (aMatch[1] !== bMatch[1]) {
@@ -67,10 +67,10 @@ function PageSizeSelector({
67
67
  className={`select select-ghost select-sm ${heightForSmallerLines}`}
68
68
  value={pageSize}
69
69
  onChange={(e) => {
70
- const pageSize = Number(e.currentTarget?.value);
70
+ const pageSize = Number(e.currentTarget.value);
71
71
  if (Number.isNaN(pageSize)) {
72
72
  throw new Error(
73
- `Invalid page size selected: The value ${e.currentTarget?.value} could not be parsed as a number.`,
73
+ `Invalid page size selected: The value ${e.currentTarget.value} could not be parsed as a number.`,
74
74
  );
75
75
  }
76
76
  setPageSize(pageSize);
@@ -1,5 +1,5 @@
1
1
  import { createTable, type RowData, type TableOptions, type TableOptionsResolved } from '@tanstack/table-core';
2
- import { type ComponentType, h, type VNode } from 'preact';
2
+ import { type ComponentType, type VNode } from 'preact';
3
3
  import { useEffect, useState } from 'preact/hooks';
4
4
 
5
5
  import { usePageSizeContext } from './pagination-context';
@@ -7,7 +7,6 @@ import { useDispatchFinishedLoadingEvent } from '../../utils/useDispatchFinished
7
7
  import { useLapisUrl } from '../LapisUrlContext';
8
8
  import { ErrorBoundary } from '../components/error-boundary';
9
9
  import { LoadingDisplay } from '../components/loading-display';
10
- import { NoDataDisplay } from '../components/no-data-display';
11
10
  import { ResizeContainer } from '../components/resize-container';
12
11
  import { formatProportion } from '../shared/table/formatProportion';
13
12
  import { useQuery } from '../useQuery';
@@ -49,10 +48,6 @@ export const StatisticsInner: FunctionComponent<StatisticsProps> = (componentPro
49
48
  throw error;
50
49
  }
51
50
 
52
- if (data === null) {
53
- return <NoDataDisplay />;
54
- }
55
-
56
51
  return <MetricDataTabs data={data} />;
57
52
  };
58
53
 
@@ -21,14 +21,5 @@ export async function fetchStringAutocompleteList({
21
21
  return data
22
22
  .map((item) => ({ count: item.count, value: item[field] }))
23
23
  .filter((item): item is { count: number; value: string } => item.value !== null)
24
- .sort((a, b) => {
25
- if (a.value === null) {
26
- return 1;
27
- }
28
- if (b.value === null) {
29
- return -1;
30
- }
31
-
32
- return a.value.localeCompare(b.value);
33
- });
24
+ .sort((a, b) => a.value.localeCompare(b.value));
34
25
  }
@@ -8,7 +8,6 @@ import { lapisFilterSchema } from '../../types';
8
8
  import { DownshiftCombobox } from '../components/downshift-combobox';
9
9
  import { ErrorBoundary } from '../components/error-boundary';
10
10
  import { LoadingDisplay } from '../components/loading-display';
11
- import { NoDataDisplay } from '../components/no-data-display';
12
11
  import { ResizeContainer } from '../components/resize-container';
13
12
  import { useQuery } from '../useQuery';
14
13
 
@@ -60,10 +59,6 @@ const TextFilterInner: FunctionComponent<TextFilterInnerProps> = ({
60
59
  throw error;
61
60
  }
62
61
 
63
- if (data === null) {
64
- return <NoDataDisplay />;
65
- }
66
-
67
62
  return <TextSelector lapisField={lapisField} value={value} placeholderText={placeholderText} data={data} />;
68
63
  };
69
64
 
@@ -106,5 +101,5 @@ function filterByInputValue(item: SelectItem, inputValue: string | null) {
106
101
  if (inputValue === null || inputValue === '') {
107
102
  return true;
108
103
  }
109
- return item.value?.toLowerCase().includes(inputValue?.toLowerCase() || '');
104
+ return item.value.toLowerCase().includes(inputValue.toLowerCase());
110
105
  }
@@ -100,7 +100,7 @@ function useDisplayedSegments(mutations: MutationOverTimeDataPerLocation) {
100
100
  const displayedSegments = useMemo(() => {
101
101
  const unique = [
102
102
  ...new Set(
103
- mutations.flatMap(({ data }) => data.getFirstAxisKeys().map((mutation) => mutation.segment || '')),
103
+ mutations.flatMap(({ data }) => data.getFirstAxisKeys().map((mutation) => mutation.segment ?? '')),
104
104
  ),
105
105
  ];
106
106
 
@@ -23,7 +23,8 @@ export type ErrorWorkerStatus =
23
23
  };
24
24
  export type WorkerStatus<Response> = LoadingWorkerStatus | SuccessWorkerStatus<Response> | ErrorWorkerStatus;
25
25
 
26
- export function useWebWorker<Request, Response>(messageToWorker: Request, WorkerConstructor: new () => Worker) {
26
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- We need the return type
27
+ export function useWebWorker<Response>(messageToWorker: unknown, WorkerConstructor: new () => Worker) {
27
28
  const [data, setData] = useState<Response | undefined>(undefined);
28
29
  const [error, setError] = useState<Error | undefined>(undefined);
29
30
  const [isLoading, setIsLoading] = useState(true);
@@ -1,7 +1,7 @@
1
1
  import { type ErrorWorkerStatus, type LoadingWorkerStatus, type SuccessWorkerStatus } from './useWebWorker';
2
2
  import { UserFacingError } from '../components/error-display';
3
3
 
4
- export async function workerFunction<R>(queryFunction: () => R) {
4
+ export async function workerFunction(queryFunction: () => unknown) {
5
5
  try {
6
6
  postMessage({ status: 'loading' } satisfies LoadingWorkerStatus);
7
7
 
@@ -10,7 +10,7 @@ export async function workerFunction<R>(queryFunction: () => R) {
10
10
  postMessage({
11
11
  status: 'success',
12
12
  data: workerResponse,
13
- } satisfies SuccessWorkerStatus<R>);
13
+ } satisfies SuccessWorkerStatus<unknown>);
14
14
  } catch (error) {
15
15
  postMessage(
16
16
  (error instanceof UserFacingError
@@ -83,7 +83,7 @@ function matchLocationDataAndGeoJsonFeatures(
83
83
 
84
84
  const locations: Feature<GeometryObject, EnhancedGeoJsonFeatureProperties>[] = geojsonData.features.map(
85
85
  (feature) => {
86
- const name = feature?.properties?.name;
86
+ const name = feature.properties.name;
87
87
  if (typeof name !== 'string') {
88
88
  throw new Error(
89
89
  `GeoJSON feature with id '${feature.id}' does not have 'properties.name' of type string, was: '${name}'`,
@@ -14,13 +14,13 @@ import {
14
14
  type TemporalClass,
15
15
  } from '../utils/temporalClass';
16
16
 
17
- export function queryAggregatedDataOverTime<LapisDateField extends string>(
17
+ export function queryAggregatedDataOverTime(
18
18
  filter: LapisFilter,
19
19
  granularity: TemporalGranularity,
20
20
  smoothingWindow: number,
21
- lapisDateField: LapisDateField,
21
+ lapisDateField: string,
22
22
  ) {
23
- const fetchData = new FetchAggregatedOperator<{ [key in LapisDateField]: string | null }>(filter, [lapisDateField]);
23
+ const fetchData = new FetchAggregatedOperator<Record<string, string | null>>(filter, [lapisDateField]);
24
24
  const dataWithFixedDateKey = new RenameFieldOperator(fetchData, lapisDateField, 'date');
25
25
  const mapData = new MapOperator(dataWithFixedDateKey, (d) => mapDateToGranularityRange(d, granularity));
26
26
  const groupByData = new GroupByAndSumOperator(mapData, 'dateRange', 'count');
@@ -231,18 +231,14 @@ describe('queryMutationsOverTime', () => {
231
231
  minProportion: 0.001,
232
232
  },
233
233
  response: {
234
- data: [
235
- getSomeTestMutation(0.1, 1),
236
- getSomeTestMutation(0.3, 3),
237
- getSomeOtherTestMutation(0.4, 4),
238
- ],
234
+ data: [getSomeTestMutation(0.2, 4), getSomeOtherTestMutation(0.4, 4)],
239
235
  },
240
236
  },
241
237
  ],
242
238
  'nucleotide',
243
239
  );
244
240
 
245
- const { mutationOverTimeData } = await queryMutationsOverTimeData({
241
+ const { mutationOverTimeData, overallMutationData } = await queryMutationsOverTimeData({
246
242
  lapisFilter,
247
243
  sequenceType: 'nucleotide',
248
244
  lapis: DUMMY_LAPIS_URL,
@@ -263,6 +259,8 @@ describe('queryMutationsOverTime', () => {
263
259
  ],
264
260
  ]);
265
261
 
262
+ expect(overallMutationData.length).toBe(2);
263
+
266
264
  const sequences = mutationOverTimeData.getFirstAxisKeys();
267
265
  expect(sequences[0].code).toBe('otherSequenceName:G234C');
268
266
  expect(sequences[1].code).toBe('sequenceName:A123T');
@@ -435,14 +433,14 @@ describe('queryMutationsOverTime', () => {
435
433
  minProportion: 0.001,
436
434
  },
437
435
  response: {
438
- data: [getSomeTestMutation(0.2, 2), getSomeTestMutation(0.3, 3)],
436
+ data: [getSomeTestMutation(0.25, 5)],
439
437
  },
440
438
  },
441
439
  ],
442
440
  'nucleotide',
443
441
  );
444
442
 
445
- const { mutationOverTimeData } = await queryMutationsOverTimeData({
443
+ const { mutationOverTimeData, overallMutationData } = await queryMutationsOverTimeData({
446
444
  lapisFilter,
447
445
  sequenceType: 'nucleotide',
448
446
  lapis: DUMMY_LAPIS_URL,
@@ -450,6 +448,8 @@ describe('queryMutationsOverTime', () => {
450
448
  granularity: 'day',
451
449
  });
452
450
 
451
+ expect(overallMutationData.length).toBe(1);
452
+
453
453
  expect(mutationOverTimeData.getAsArray()).to.deep.equal([
454
454
  [
455
455
  { type: 'value', proportion: 0.2, count: 2, totalCount: 11 },
@@ -527,7 +527,7 @@ describe('queryMutationsOverTime', () => {
527
527
  minProportion: 0.001,
528
528
  },
529
529
  response: {
530
- data: [getSomeTestMutation(0.1, 1), getSomeTestMutation(0.2, 2)],
530
+ data: [getSomeTestMutation(0.15, 3)],
531
531
  },
532
532
  },
533
533
  ],