@genspectrum/dashboard-components 0.13.0 → 0.13.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.
- package/custom-elements.json +178 -0
- package/dist/assets/mutationOverTimeWorker-B1-WrM4b.js.map +1 -0
- package/dist/components.d.ts +108 -43
- package/dist/components.js +564 -295
- package/dist/components.js.map +1 -1
- package/dist/style.css +3 -0
- package/dist/util.d.ts +59 -43
- package/package.json +2 -2
- package/src/constants.ts +6 -0
- package/src/lapisApi/__mockData__/wiseReferenceGenome.json +9 -0
- package/src/lapisApi/lapisApi.ts +17 -0
- package/src/lapisApi/lapisTypes.ts +7 -1
- package/src/operator/FetchDetailsOperator.ts +28 -0
- package/src/preact/components/downshift-combobox.tsx +18 -20
- package/src/preact/components/tabs.tsx +1 -1
- package/src/preact/mutationsOverTime/MutationOverTimeData.ts +9 -5
- package/src/preact/mutationsOverTime/mutations-over-time-grid.tsx +5 -3
- package/src/preact/shared/sort/sortSubstitutionsAndDeletions.ts +4 -7
- package/src/preact/textInput/fetchStringAutocompleteList.spec.ts +34 -0
- package/src/preact/textInput/fetchStringAutocompleteList.ts +16 -2
- package/src/preact/textInput/text-input.tsx +22 -8
- package/src/preact/wastewater/mutationsOverTime/__mockData__/details.json +88 -0
- package/src/preact/wastewater/mutationsOverTime/computeWastewaterMutationsOverTimeDataPerLocation.spec.ts +159 -0
- package/src/preact/wastewater/mutationsOverTime/computeWastewaterMutationsOverTimeDataPerLocation.ts +51 -0
- package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.stories.tsx +71 -0
- package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.tsx +151 -0
- package/src/query/queryMutationsOverTime.ts +6 -14
- package/src/query/queryWastewaterMutationsOverTime.spec.ts +94 -0
- package/src/query/queryWastewaterMutationsOverTime.ts +55 -0
- package/src/utils/map2d.ts +39 -0
- package/src/web-components/index.ts +1 -0
- package/src/web-components/wastewaterVisualization/gs-wastewater-mutations-over-time.stories.ts +82 -0
- package/src/web-components/wastewaterVisualization/gs-wastewater-mutations-over-time.tsx +112 -0
- package/src/web-components/wastewaterVisualization/index.ts +1 -0
- package/standalone-bundle/assets/{mutationOverTimeWorker-DEybsZ5r.js.map → mutationOverTimeWorker-Cls1J0cl.js.map} +1 -1
- package/standalone-bundle/dashboard-components.js +6228 -6008
- package/standalone-bundle/dashboard-components.js.map +1 -1
- package/standalone-bundle/style.css +1 -1
- package/dist/assets/mutationOverTimeWorker-DTv93Ere.js.map +0 -1
package/dist/style.css
CHANGED
package/dist/util.d.ts
CHANGED
|
@@ -838,7 +838,11 @@ declare global {
|
|
|
838
838
|
|
|
839
839
|
declare global {
|
|
840
840
|
interface HTMLElementTagNameMap {
|
|
841
|
-
'gs-
|
|
841
|
+
'gs-date-range-selector': DateRangeSelectorComponent;
|
|
842
|
+
}
|
|
843
|
+
interface HTMLElementEventMap {
|
|
844
|
+
'gs-date-range-filter-changed': CustomEvent<Record<string, string>>;
|
|
845
|
+
'gs-date-range-option-changed': DateRangeOptionChangedEvent;
|
|
842
846
|
}
|
|
843
847
|
}
|
|
844
848
|
|
|
@@ -846,7 +850,7 @@ declare global {
|
|
|
846
850
|
declare global {
|
|
847
851
|
namespace JSX {
|
|
848
852
|
interface IntrinsicElements {
|
|
849
|
-
'gs-
|
|
853
|
+
'gs-date-range-selector': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
850
854
|
}
|
|
851
855
|
}
|
|
852
856
|
}
|
|
@@ -854,7 +858,10 @@ declare global {
|
|
|
854
858
|
|
|
855
859
|
declare global {
|
|
856
860
|
interface HTMLElementTagNameMap {
|
|
857
|
-
'gs-
|
|
861
|
+
'gs-location-filter': LocationFilterComponent;
|
|
862
|
+
}
|
|
863
|
+
interface HTMLElementEventMap {
|
|
864
|
+
'gs-location-changed': LocationChangedEvent;
|
|
858
865
|
}
|
|
859
866
|
}
|
|
860
867
|
|
|
@@ -862,7 +869,7 @@ declare global {
|
|
|
862
869
|
declare global {
|
|
863
870
|
namespace JSX {
|
|
864
871
|
interface IntrinsicElements {
|
|
865
|
-
'gs-
|
|
872
|
+
'gs-location-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
866
873
|
}
|
|
867
874
|
}
|
|
868
875
|
}
|
|
@@ -870,7 +877,10 @@ declare global {
|
|
|
870
877
|
|
|
871
878
|
declare global {
|
|
872
879
|
interface HTMLElementTagNameMap {
|
|
873
|
-
'gs-
|
|
880
|
+
'gs-text-input': TextInputComponent;
|
|
881
|
+
}
|
|
882
|
+
interface HTMLElementEventMap {
|
|
883
|
+
'gs-text-input-changed': TextInputChangedEvent;
|
|
874
884
|
}
|
|
875
885
|
}
|
|
876
886
|
|
|
@@ -878,7 +888,7 @@ declare global {
|
|
|
878
888
|
declare global {
|
|
879
889
|
namespace JSX {
|
|
880
890
|
interface IntrinsicElements {
|
|
881
|
-
'gs-
|
|
891
|
+
'gs-text-input': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
882
892
|
}
|
|
883
893
|
}
|
|
884
894
|
}
|
|
@@ -886,7 +896,10 @@ declare global {
|
|
|
886
896
|
|
|
887
897
|
declare global {
|
|
888
898
|
interface HTMLElementTagNameMap {
|
|
889
|
-
'gs-
|
|
899
|
+
'gs-mutation-filter': MutationFilterComponent;
|
|
900
|
+
}
|
|
901
|
+
interface HTMLElementEventMap {
|
|
902
|
+
'gs-mutation-filter-changed': CustomEvent<MutationsFilter>;
|
|
890
903
|
}
|
|
891
904
|
}
|
|
892
905
|
|
|
@@ -894,7 +907,7 @@ declare global {
|
|
|
894
907
|
declare global {
|
|
895
908
|
namespace JSX {
|
|
896
909
|
interface IntrinsicElements {
|
|
897
|
-
'gs-
|
|
910
|
+
'gs-mutation-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
898
911
|
}
|
|
899
912
|
}
|
|
900
913
|
}
|
|
@@ -902,7 +915,10 @@ declare global {
|
|
|
902
915
|
|
|
903
916
|
declare global {
|
|
904
917
|
interface HTMLElementTagNameMap {
|
|
905
|
-
'gs-
|
|
918
|
+
'gs-lineage-filter': LineageFilterComponent;
|
|
919
|
+
}
|
|
920
|
+
interface HTMLElementEventMap {
|
|
921
|
+
'gs-lineage-filter-changed': LineageFilterChangedEvent;
|
|
906
922
|
}
|
|
907
923
|
}
|
|
908
924
|
|
|
@@ -910,7 +926,7 @@ declare global {
|
|
|
910
926
|
declare global {
|
|
911
927
|
namespace JSX {
|
|
912
928
|
interface IntrinsicElements {
|
|
913
|
-
'gs-
|
|
929
|
+
'gs-lineage-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
914
930
|
}
|
|
915
931
|
}
|
|
916
932
|
}
|
|
@@ -918,7 +934,7 @@ declare global {
|
|
|
918
934
|
|
|
919
935
|
declare global {
|
|
920
936
|
interface HTMLElementTagNameMap {
|
|
921
|
-
'gs-
|
|
937
|
+
'gs-wastewater-mutations-over-time': WastewaterMutationsOverTimeComponent;
|
|
922
938
|
}
|
|
923
939
|
}
|
|
924
940
|
|
|
@@ -926,7 +942,7 @@ declare global {
|
|
|
926
942
|
declare global {
|
|
927
943
|
namespace JSX {
|
|
928
944
|
interface IntrinsicElements {
|
|
929
|
-
'gs-
|
|
945
|
+
'gs-wastewater-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
930
946
|
}
|
|
931
947
|
}
|
|
932
948
|
}
|
|
@@ -934,7 +950,7 @@ declare global {
|
|
|
934
950
|
|
|
935
951
|
declare global {
|
|
936
952
|
interface HTMLElementTagNameMap {
|
|
937
|
-
'gs-
|
|
953
|
+
'gs-mutation-comparison-component': MutationComparisonComponent;
|
|
938
954
|
}
|
|
939
955
|
}
|
|
940
956
|
|
|
@@ -942,7 +958,7 @@ declare global {
|
|
|
942
958
|
declare global {
|
|
943
959
|
namespace JSX {
|
|
944
960
|
interface IntrinsicElements {
|
|
945
|
-
'gs-
|
|
961
|
+
'gs-mutation-comparison-component': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
946
962
|
}
|
|
947
963
|
}
|
|
948
964
|
}
|
|
@@ -950,7 +966,7 @@ declare global {
|
|
|
950
966
|
|
|
951
967
|
declare global {
|
|
952
968
|
interface HTMLElementTagNameMap {
|
|
953
|
-
'gs-
|
|
969
|
+
'gs-mutations-component': MutationsComponent;
|
|
954
970
|
}
|
|
955
971
|
}
|
|
956
972
|
|
|
@@ -958,7 +974,7 @@ declare global {
|
|
|
958
974
|
declare global {
|
|
959
975
|
namespace JSX {
|
|
960
976
|
interface IntrinsicElements {
|
|
961
|
-
'gs-
|
|
977
|
+
'gs-mutations-component': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
962
978
|
}
|
|
963
979
|
}
|
|
964
980
|
}
|
|
@@ -966,7 +982,7 @@ declare global {
|
|
|
966
982
|
|
|
967
983
|
declare global {
|
|
968
984
|
interface HTMLElementTagNameMap {
|
|
969
|
-
'gs-
|
|
985
|
+
'gs-prevalence-over-time': PrevalenceOverTimeComponent;
|
|
970
986
|
}
|
|
971
987
|
}
|
|
972
988
|
|
|
@@ -974,7 +990,7 @@ declare global {
|
|
|
974
990
|
declare global {
|
|
975
991
|
namespace JSX {
|
|
976
992
|
interface IntrinsicElements {
|
|
977
|
-
'gs-
|
|
993
|
+
'gs-prevalence-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
978
994
|
}
|
|
979
995
|
}
|
|
980
996
|
}
|
|
@@ -982,11 +998,7 @@ declare global {
|
|
|
982
998
|
|
|
983
999
|
declare global {
|
|
984
1000
|
interface HTMLElementTagNameMap {
|
|
985
|
-
'gs-
|
|
986
|
-
}
|
|
987
|
-
interface HTMLElementEventMap {
|
|
988
|
-
'gs-date-range-filter-changed': CustomEvent<Record<string, string>>;
|
|
989
|
-
'gs-date-range-option-changed': DateRangeOptionChangedEvent;
|
|
1001
|
+
'gs-relative-growth-advantage': RelativeGrowthAdvantageComponent;
|
|
990
1002
|
}
|
|
991
1003
|
}
|
|
992
1004
|
|
|
@@ -994,7 +1006,7 @@ declare global {
|
|
|
994
1006
|
declare global {
|
|
995
1007
|
namespace JSX {
|
|
996
1008
|
interface IntrinsicElements {
|
|
997
|
-
'gs-
|
|
1009
|
+
'gs-relative-growth-advantage': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
998
1010
|
}
|
|
999
1011
|
}
|
|
1000
1012
|
}
|
|
@@ -1002,10 +1014,7 @@ declare global {
|
|
|
1002
1014
|
|
|
1003
1015
|
declare global {
|
|
1004
1016
|
interface HTMLElementTagNameMap {
|
|
1005
|
-
'gs-
|
|
1006
|
-
}
|
|
1007
|
-
interface HTMLElementEventMap {
|
|
1008
|
-
'gs-location-changed': LocationChangedEvent;
|
|
1017
|
+
'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
|
|
1009
1018
|
}
|
|
1010
1019
|
}
|
|
1011
1020
|
|
|
@@ -1013,7 +1022,7 @@ declare global {
|
|
|
1013
1022
|
declare global {
|
|
1014
1023
|
namespace JSX {
|
|
1015
1024
|
interface IntrinsicElements {
|
|
1016
|
-
'gs-
|
|
1025
|
+
'gs-number-sequences-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1017
1026
|
}
|
|
1018
1027
|
}
|
|
1019
1028
|
}
|
|
@@ -1021,10 +1030,7 @@ declare global {
|
|
|
1021
1030
|
|
|
1022
1031
|
declare global {
|
|
1023
1032
|
interface HTMLElementTagNameMap {
|
|
1024
|
-
'gs-
|
|
1025
|
-
}
|
|
1026
|
-
interface HTMLElementEventMap {
|
|
1027
|
-
'gs-text-input-changed': TextInputChangedEvent;
|
|
1033
|
+
'gs-aggregate': AggregateComponent;
|
|
1028
1034
|
}
|
|
1029
1035
|
}
|
|
1030
1036
|
|
|
@@ -1032,7 +1038,7 @@ declare global {
|
|
|
1032
1038
|
declare global {
|
|
1033
1039
|
namespace JSX {
|
|
1034
1040
|
interface IntrinsicElements {
|
|
1035
|
-
'gs-
|
|
1041
|
+
'gs-aggregate': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1036
1042
|
}
|
|
1037
1043
|
}
|
|
1038
1044
|
}
|
|
@@ -1040,10 +1046,7 @@ declare global {
|
|
|
1040
1046
|
|
|
1041
1047
|
declare global {
|
|
1042
1048
|
interface HTMLElementTagNameMap {
|
|
1043
|
-
'gs-
|
|
1044
|
-
}
|
|
1045
|
-
interface HTMLElementEventMap {
|
|
1046
|
-
'gs-mutation-filter-changed': CustomEvent<MutationsFilter>;
|
|
1049
|
+
'gs-mutations-over-time': MutationsOverTimeComponent;
|
|
1047
1050
|
}
|
|
1048
1051
|
}
|
|
1049
1052
|
|
|
@@ -1051,7 +1054,7 @@ declare global {
|
|
|
1051
1054
|
declare global {
|
|
1052
1055
|
namespace JSX {
|
|
1053
1056
|
interface IntrinsicElements {
|
|
1054
|
-
'gs-
|
|
1057
|
+
'gs-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1055
1058
|
}
|
|
1056
1059
|
}
|
|
1057
1060
|
}
|
|
@@ -1059,10 +1062,23 @@ declare global {
|
|
|
1059
1062
|
|
|
1060
1063
|
declare global {
|
|
1061
1064
|
interface HTMLElementTagNameMap {
|
|
1062
|
-
'gs-
|
|
1065
|
+
'gs-sequences-by-location': SequencesByLocationComponent;
|
|
1063
1066
|
}
|
|
1064
|
-
|
|
1065
|
-
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
|
|
1070
|
+
declare global {
|
|
1071
|
+
namespace JSX {
|
|
1072
|
+
interface IntrinsicElements {
|
|
1073
|
+
'gs-sequences-by-location': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
|
|
1079
|
+
declare global {
|
|
1080
|
+
interface HTMLElementTagNameMap {
|
|
1081
|
+
'gs-statistics': StatisticsComponent;
|
|
1066
1082
|
}
|
|
1067
1083
|
}
|
|
1068
1084
|
|
|
@@ -1070,7 +1086,7 @@ declare global {
|
|
|
1070
1086
|
declare global {
|
|
1071
1087
|
namespace JSX {
|
|
1072
1088
|
interface IntrinsicElements {
|
|
1073
|
-
'gs-
|
|
1089
|
+
'gs-statistics': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1074
1090
|
}
|
|
1075
1091
|
}
|
|
1076
1092
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genspectrum/dashboard-components",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.2",
|
|
4
4
|
"description": "GenSpectrum web components for building dashboards",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "AGPL-3.0-only",
|
|
@@ -142,6 +142,6 @@
|
|
|
142
142
|
"typescript": "~5.7.2",
|
|
143
143
|
"vite": "^6.0.3",
|
|
144
144
|
"vite-plugin-dts": "^4.0.3",
|
|
145
|
-
"vitest": "^
|
|
145
|
+
"vitest": "^3.0.2"
|
|
146
146
|
}
|
|
147
147
|
}
|
package/src/constants.ts
CHANGED
|
@@ -5,3 +5,9 @@ export const NUCLEOTIDE_MUTATIONS_ENDPOINT = `${LAPIS_URL}/sample/nucleotideMuta
|
|
|
5
5
|
export const AMINO_ACID_MUTATIONS_ENDPOINT = `${LAPIS_URL}/sample/aminoAcidMutations`;
|
|
6
6
|
export const NUCLEOTIDE_INSERTIONS_ENDPOINT = `${LAPIS_URL}/sample/nucleotideInsertions`;
|
|
7
7
|
export const REFERENCE_GENOME_ENDPOINT = `${LAPIS_URL}/sample/referenceGenome`;
|
|
8
|
+
|
|
9
|
+
// WISE Wastewater
|
|
10
|
+
// This is a special instance for storing Swiss wastewater data generated by the WISE consortium
|
|
11
|
+
export const WISE_LAPIS_URL = 'https://api.wise-loculus.genspectrum.org/rsv';
|
|
12
|
+
export const WISE_DETAILS_ENDPOINT = `${WISE_LAPIS_URL}/sample/details`;
|
|
13
|
+
export const WISE_REFERENCE_GENOME_ENDPOINT = `${WISE_LAPIS_URL}/sample/referenceGenome`;
|
package/src/lapisApi/lapisApi.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { referenceGenomeResponse } from './ReferenceGenome';
|
|
2
2
|
import {
|
|
3
3
|
aggregatedResponse,
|
|
4
|
+
detailsResponse,
|
|
4
5
|
insertionsResponse,
|
|
5
6
|
type LapisBaseRequest,
|
|
6
7
|
lapisError,
|
|
@@ -51,6 +52,21 @@ export async function fetchAggregated(lapisUrl: string, body: LapisBaseRequest,
|
|
|
51
52
|
return aggregatedResponse.parse(await response.json());
|
|
52
53
|
}
|
|
53
54
|
|
|
55
|
+
export async function fetchDetails(lapisUrl: string, body: LapisBaseRequest, signal?: AbortSignal) {
|
|
56
|
+
const response = await fetch(detailsEndpoint(lapisUrl), {
|
|
57
|
+
method: 'POST',
|
|
58
|
+
headers: {
|
|
59
|
+
'Content-Type': 'application/json',
|
|
60
|
+
},
|
|
61
|
+
body: JSON.stringify(body),
|
|
62
|
+
signal,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
await handleErrors(response, 'aggregated data');
|
|
66
|
+
|
|
67
|
+
return detailsResponse.parse(await response.json());
|
|
68
|
+
}
|
|
69
|
+
|
|
54
70
|
export async function fetchInsertions(
|
|
55
71
|
lapisUrl: string,
|
|
56
72
|
body: LapisBaseRequest,
|
|
@@ -163,6 +179,7 @@ const handleErrors = async (response: Response, requestedData: string) => {
|
|
|
163
179
|
};
|
|
164
180
|
|
|
165
181
|
export const aggregatedEndpoint = (lapisUrl: string) => `${lapisUrl}/sample/aggregated`;
|
|
182
|
+
export const detailsEndpoint = (lapisUrl: string) => `${lapisUrl}/sample/details`;
|
|
166
183
|
export const insertionsEndpoint = (lapisUrl: string, sequenceType: SequenceType) => {
|
|
167
184
|
return sequenceType === 'amino acid'
|
|
168
185
|
? `${lapisUrl}/sample/aminoAcidInsertions`
|
|
@@ -41,10 +41,16 @@ const insertionCount = z.object({
|
|
|
41
41
|
});
|
|
42
42
|
export const insertionsResponse = makeLapisResponse(z.array(insertionCount));
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
const baseResponseValueSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]);
|
|
45
|
+
|
|
46
|
+
export const aggregatedItem = z.object({ count: z.number() }).catchall(baseResponseValueSchema);
|
|
45
47
|
export const aggregatedResponse = makeLapisResponse(z.array(aggregatedItem));
|
|
46
48
|
export type AggregatedItem = z.infer<typeof aggregatedItem>;
|
|
47
49
|
|
|
50
|
+
export const detailsItem = z.object({}).catchall(baseResponseValueSchema);
|
|
51
|
+
export const detailsResponse = makeLapisResponse(z.array(detailsItem));
|
|
52
|
+
export type DetailsItem = z.infer<typeof detailsItem>;
|
|
53
|
+
|
|
48
54
|
function makeLapisResponse<T extends ZodTypeAny>(data: T) {
|
|
49
55
|
return z.object({
|
|
50
56
|
data,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type Dataset } from './Dataset';
|
|
2
|
+
import { type Operator } from './Operator';
|
|
3
|
+
import { fetchDetails } from '../lapisApi/lapisApi';
|
|
4
|
+
import { type LapisFilter } from '../types';
|
|
5
|
+
|
|
6
|
+
type Details<Fields extends string> = { [field in Fields]: string | number | boolean | null };
|
|
7
|
+
|
|
8
|
+
export class FetchDetailsOperator<Fields extends string> implements Operator<Details<Fields>> {
|
|
9
|
+
constructor(
|
|
10
|
+
private filter: LapisFilter,
|
|
11
|
+
private fields: Fields[] = [],
|
|
12
|
+
) {}
|
|
13
|
+
|
|
14
|
+
async evaluate(lapisUrl: string, signal?: AbortSignal): Promise<Dataset<Details<Fields>>> {
|
|
15
|
+
const detailsResponse = (
|
|
16
|
+
await fetchDetails(
|
|
17
|
+
lapisUrl,
|
|
18
|
+
{
|
|
19
|
+
...this.filter,
|
|
20
|
+
fields: this.fields,
|
|
21
|
+
},
|
|
22
|
+
signal,
|
|
23
|
+
)
|
|
24
|
+
).data;
|
|
25
|
+
|
|
26
|
+
return { content: detailsResponse as Details<Fields>[] };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -120,26 +120,24 @@ export function DownshiftCombobox<Item>({
|
|
|
120
120
|
</button>
|
|
121
121
|
</div>
|
|
122
122
|
</div>
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
>
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
</ul>
|
|
142
|
-
)}
|
|
123
|
+
<ul
|
|
124
|
+
className={`absolute bg-white mt-1 shadow-md max-h-80 overflow-scroll z-10 w-full min-w-32 ${isOpen ? '' : 'hidden'}`}
|
|
125
|
+
{...getMenuProps()}
|
|
126
|
+
>
|
|
127
|
+
{items.length > 0 ? (
|
|
128
|
+
items.map((item, index) => (
|
|
129
|
+
<li
|
|
130
|
+
className={`${highlightedIndex === index ? 'bg-blue-300' : ''} ${selectedItem !== null && itemToString(selectedItem) === itemToString(item) ? 'font-bold' : ''} py-2 px-3 shadow-sm flex flex-col`}
|
|
131
|
+
key={itemToString(item)}
|
|
132
|
+
{...getItemProps({ item, index })}
|
|
133
|
+
>
|
|
134
|
+
{formatItemInList(item)}
|
|
135
|
+
</li>
|
|
136
|
+
))
|
|
137
|
+
) : (
|
|
138
|
+
<li className='py-2 px-3 shadow-sm flex flex-col'>No elements to select.</li>
|
|
139
|
+
)}
|
|
140
|
+
</ul>
|
|
143
141
|
</div>
|
|
144
142
|
);
|
|
145
143
|
}
|
|
@@ -16,7 +16,7 @@ const Tabs: FunctionComponent<ComponentTabsProps> = ({ tabs, toolbar }) => {
|
|
|
16
16
|
const [activeTab, setActiveTab] = useState(tabs[0]?.title);
|
|
17
17
|
|
|
18
18
|
const tabElements = (
|
|
19
|
-
<div className='flex flex-row'>
|
|
19
|
+
<div className='flex flex-row flex-wrap'>
|
|
20
20
|
{tabs.map((tab) => {
|
|
21
21
|
return (
|
|
22
22
|
<button
|
|
@@ -5,16 +5,20 @@ import {
|
|
|
5
5
|
} from '../../query/queryMutationsOverTime';
|
|
6
6
|
import { type Map2d, Map2dBase, type Map2DContents } from '../../utils/map2d';
|
|
7
7
|
import type { Deletion, Substitution } from '../../utils/mutations';
|
|
8
|
-
import type { Temporal } from '../../utils/temporalClass';
|
|
8
|
+
import type { Temporal, TemporalClass } from '../../utils/temporalClass';
|
|
9
9
|
|
|
10
|
-
export type MutationOverTimeDataMap
|
|
10
|
+
export type MutationOverTimeDataMap<T extends Temporal | TemporalClass = Temporal> = Map2d<
|
|
11
|
+
Substitution | Deletion,
|
|
12
|
+
T,
|
|
13
|
+
MutationOverTimeMutationValue
|
|
14
|
+
>;
|
|
11
15
|
|
|
12
|
-
export class BaseMutationOverTimeDataMap extends Map2dBase<
|
|
16
|
+
export class BaseMutationOverTimeDataMap<T extends Temporal | TemporalClass = Temporal> extends Map2dBase<
|
|
13
17
|
Substitution | Deletion,
|
|
14
|
-
|
|
18
|
+
T,
|
|
15
19
|
MutationOverTimeMutationValue
|
|
16
20
|
> {
|
|
17
|
-
constructor(initialContent?: Map2DContents<Substitution | Deletion,
|
|
21
|
+
constructor(initialContent?: Map2DContents<Substitution | Deletion, T, MutationOverTimeMutationValue>) {
|
|
18
22
|
super(serializeSubstitutionOrDeletion, serializeTemporal, initialContent);
|
|
19
23
|
}
|
|
20
24
|
}
|
|
@@ -111,9 +111,11 @@ const ProportionCell: FunctionComponent<{
|
|
|
111
111
|
) : (
|
|
112
112
|
<>
|
|
113
113
|
<p>Proportion: {formatProportion(value.proportion)}</p>
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
114
|
+
{value.count !== null && value.totalCount !== null && (
|
|
115
|
+
<p>
|
|
116
|
+
Count: {value.count} / {value.totalCount} total
|
|
117
|
+
</p>
|
|
118
|
+
)}
|
|
117
119
|
</>
|
|
118
120
|
)}
|
|
119
121
|
</div>
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Deletion, Substitution } from '../../../utils/mutations';
|
|
2
2
|
|
|
3
|
-
export const sortSubstitutionsAndDeletions = (
|
|
4
|
-
a: SubstitutionClass | DeletionClass,
|
|
5
|
-
b: SubstitutionClass | DeletionClass,
|
|
6
|
-
) => {
|
|
3
|
+
export const sortSubstitutionsAndDeletions = (a: Substitution | Deletion, b: Substitution | Deletion) => {
|
|
7
4
|
if (a.segment !== b.segment) {
|
|
8
5
|
return compareSegments(a.segment, b.segment);
|
|
9
6
|
}
|
|
@@ -12,8 +9,8 @@ export const sortSubstitutionsAndDeletions = (
|
|
|
12
9
|
return comparePositions(a.position, b.position);
|
|
13
10
|
}
|
|
14
11
|
|
|
15
|
-
const aIsDeletion = a
|
|
16
|
-
const bIsDeletion = b
|
|
12
|
+
const aIsDeletion = a.type === 'deletion';
|
|
13
|
+
const bIsDeletion = b.type === 'deletion';
|
|
17
14
|
|
|
18
15
|
if (aIsDeletion !== bIsDeletion) {
|
|
19
16
|
return aIsDeletion ? 1 : -1;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { fetchStringAutocompleteList } from './fetchStringAutocompleteList';
|
|
4
|
+
import { DUMMY_LAPIS_URL, lapisRequestMocks } from '../../../vitest.setup';
|
|
5
|
+
|
|
6
|
+
describe('fetchStringAutocompleteList', () => {
|
|
7
|
+
test('should fetch autocompletion list and sort by field value', async () => {
|
|
8
|
+
const field = 'host';
|
|
9
|
+
|
|
10
|
+
lapisRequestMocks.aggregated(
|
|
11
|
+
{ fields: [field], country: 'Germany' },
|
|
12
|
+
{
|
|
13
|
+
data: [
|
|
14
|
+
{ count: 1, host: 'host_c' },
|
|
15
|
+
{ count: 2, host: 'host_b' },
|
|
16
|
+
{ count: 3, host: null },
|
|
17
|
+
{ count: 4, host: 'host_a' },
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const result = await fetchStringAutocompleteList({
|
|
23
|
+
field,
|
|
24
|
+
lapis: DUMMY_LAPIS_URL,
|
|
25
|
+
lapisFilter: { country: 'Germany' },
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
expect(result).to.deep.equal([
|
|
29
|
+
{ count: 4, value: 'host_a' },
|
|
30
|
+
{ count: 2, value: 'host_b' },
|
|
31
|
+
{ count: 1, value: 'host_c' },
|
|
32
|
+
]);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -12,9 +12,23 @@ export async function fetchStringAutocompleteList({
|
|
|
12
12
|
lapisFilter?: LapisFilter;
|
|
13
13
|
signal?: AbortSignal;
|
|
14
14
|
}) {
|
|
15
|
-
const fetchAggregatedOperator = new FetchAggregatedOperator<Record<string, string>>(lapisFilter ?? {}, [
|
|
15
|
+
const fetchAggregatedOperator = new FetchAggregatedOperator<Record<string, string | null>>(lapisFilter ?? {}, [
|
|
16
|
+
field,
|
|
17
|
+
]);
|
|
16
18
|
|
|
17
19
|
const data = (await fetchAggregatedOperator.evaluate(lapis, signal)).content;
|
|
18
20
|
|
|
19
|
-
return data
|
|
21
|
+
return data
|
|
22
|
+
.map((item) => ({ count: item.count, value: item[field] }))
|
|
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
|
+
});
|
|
20
34
|
}
|
|
@@ -68,32 +68,46 @@ const TextInputInner: FunctionComponent<TextInputInnerProps> = ({
|
|
|
68
68
|
return <TextSelector lapisField={lapisField} value={value} placeholderText={placeholderText} data={data} />;
|
|
69
69
|
};
|
|
70
70
|
|
|
71
|
+
type SelectItem = {
|
|
72
|
+
count: number;
|
|
73
|
+
value: string;
|
|
74
|
+
};
|
|
75
|
+
|
|
71
76
|
const TextSelector = ({
|
|
72
77
|
lapisField,
|
|
73
78
|
value,
|
|
74
79
|
placeholderText,
|
|
75
80
|
data,
|
|
76
81
|
}: TextSelectorProps & {
|
|
77
|
-
data:
|
|
82
|
+
data: SelectItem[];
|
|
78
83
|
}) => {
|
|
84
|
+
const initialSelectedItem = data.find((candidate) => candidate.value == value);
|
|
85
|
+
|
|
79
86
|
return (
|
|
80
87
|
<DownshiftCombobox
|
|
81
88
|
allItems={data}
|
|
82
|
-
value={
|
|
89
|
+
value={initialSelectedItem}
|
|
83
90
|
filterItemsByInputValue={filterByInputValue}
|
|
84
|
-
createEvent={(item:
|
|
85
|
-
|
|
91
|
+
createEvent={(item: SelectItem | null) =>
|
|
92
|
+
new TextInputChangedEvent({ [lapisField]: item?.value ?? undefined })
|
|
93
|
+
}
|
|
94
|
+
itemToString={(item: SelectItem | undefined | null) => item?.value ?? ''}
|
|
86
95
|
placeholderText={placeholderText}
|
|
87
|
-
formatItemInList={(item:
|
|
88
|
-
return
|
|
96
|
+
formatItemInList={(item: SelectItem) => {
|
|
97
|
+
return (
|
|
98
|
+
<p>
|
|
99
|
+
<span>{item.value}</span>
|
|
100
|
+
<span className='ml-2 text-gray-500'>({item.count})</span>
|
|
101
|
+
</p>
|
|
102
|
+
);
|
|
89
103
|
}}
|
|
90
104
|
/>
|
|
91
105
|
);
|
|
92
106
|
};
|
|
93
107
|
|
|
94
|
-
function filterByInputValue(item:
|
|
108
|
+
function filterByInputValue(item: SelectItem, inputValue: string | undefined | null) {
|
|
95
109
|
if (inputValue === undefined || inputValue === null || inputValue === '') {
|
|
96
110
|
return true;
|
|
97
111
|
}
|
|
98
|
-
return item?.toLowerCase().includes(inputValue?.toLowerCase() || '');
|
|
112
|
+
return item.value?.toLowerCase().includes(inputValue?.toLowerCase() || '');
|
|
99
113
|
}
|