@vitessce/vit-s 3.6.7 → 3.6.8
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/dist/index.js +1383 -881
- package/dist-tsc/TitleInfo.d.ts.map +1 -1
- package/dist-tsc/TitleInfo.js +28 -7
- package/dist-tsc/VitS.d.ts.map +1 -1
- package/dist-tsc/VitS.js +11 -2
- package/dist-tsc/data-hook-utils.d.ts +6 -12
- package/dist-tsc/data-hook-utils.d.ts.map +1 -1
- package/dist-tsc/data-hook-utils.js +11 -38
- package/dist-tsc/data-hooks-multilevel-utils.d.ts +51 -5
- package/dist-tsc/data-hooks-multilevel-utils.d.ts.map +1 -1
- package/dist-tsc/data-hooks-multilevel-utils.js +68 -42
- package/dist-tsc/data-hooks-multilevel.d.ts +63 -7
- package/dist-tsc/data-hooks-multilevel.d.ts.map +1 -1
- package/dist-tsc/data-hooks-multilevel.js +70 -14
- package/dist-tsc/data-hooks.d.ts +275 -34
- package/dist-tsc/data-hooks.d.ts.map +1 -1
- package/dist-tsc/data-hooks.js +282 -53
- package/dist-tsc/hooks.js +1 -1
- package/package.json +12 -11
- package/src/TitleInfo.js +64 -3
- package/src/VitS.js +11 -1
- package/src/data-hook-utils.js +10 -44
- package/src/data-hooks-multilevel-utils.js +66 -48
- package/src/data-hooks-multilevel.js +70 -14
- package/src/data-hooks.js +285 -57
- package/src/hooks.js +1 -1
package/src/TitleInfo.js
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import React, { useState, useMemo } from 'react';
|
|
2
2
|
import clsx from 'clsx';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
makeStyles,
|
|
5
|
+
MenuItem,
|
|
6
|
+
IconButton,
|
|
7
|
+
Link,
|
|
8
|
+
List,
|
|
9
|
+
Alert,
|
|
4
10
|
CloudDownload as CloudDownloadIcon,
|
|
5
11
|
ArrowDropDown as ArrowDropDownIcon,
|
|
6
12
|
ArrowDropUp as ArrowDropUpIcon,
|
|
7
13
|
Settings as SettingsIcon,
|
|
8
14
|
Close as CloseIcon,
|
|
9
15
|
Help as HelpIcon,
|
|
16
|
+
Warning as WarningIcon,
|
|
10
17
|
} from '@vitessce/styles';
|
|
11
18
|
|
|
12
19
|
import { TOOLTIP_ANCESTOR } from './classNames.js';
|
|
@@ -54,6 +61,18 @@ const useStyles = makeStyles()(theme => ({
|
|
|
54
61
|
boxShadow: '0 0 10px rgba(0, 0, 0, 0.2)',
|
|
55
62
|
border: '10px solid grey',
|
|
56
63
|
},
|
|
64
|
+
errorList: {
|
|
65
|
+
width: '100%',
|
|
66
|
+
maxWidth: 300,
|
|
67
|
+
padding: '0 4px',
|
|
68
|
+
},
|
|
69
|
+
errorListAlert: {
|
|
70
|
+
marginTop: '2px',
|
|
71
|
+
marginBottom: '2px',
|
|
72
|
+
},
|
|
73
|
+
errorListItemText: {
|
|
74
|
+
fontSize: '12px',
|
|
75
|
+
},
|
|
57
76
|
}));
|
|
58
77
|
|
|
59
78
|
function SettingsIconWithArrow({ open }) {
|
|
@@ -78,6 +97,7 @@ function PlotOptions(props) {
|
|
|
78
97
|
buttonIcon={buttonIcon}
|
|
79
98
|
buttonClassName={classes.iconButton}
|
|
80
99
|
placement="bottom-end"
|
|
100
|
+
title="Plot Options"
|
|
81
101
|
aria-label="Open plot options menu"
|
|
82
102
|
>
|
|
83
103
|
{options}
|
|
@@ -106,6 +126,7 @@ function DownloadOptions(props) {
|
|
|
106
126
|
buttonIcon={buttonIcon}
|
|
107
127
|
buttonClassName={classes.iconButton}
|
|
108
128
|
placement="bottom-end"
|
|
129
|
+
title="Download Options"
|
|
109
130
|
aria-label="Open download options menu"
|
|
110
131
|
>
|
|
111
132
|
{urls.map(({ url, name }) => (
|
|
@@ -130,6 +151,7 @@ function HelpButton(props) {
|
|
|
130
151
|
buttonIcon={<HelpIcon />}
|
|
131
152
|
buttonClassName={classes.iconButton}
|
|
132
153
|
placement="bottom-end"
|
|
154
|
+
title="Help Info"
|
|
133
155
|
aria-label="Open help info"
|
|
134
156
|
withPaper={false}
|
|
135
157
|
>
|
|
@@ -138,6 +160,38 @@ function HelpButton(props) {
|
|
|
138
160
|
);
|
|
139
161
|
}
|
|
140
162
|
|
|
163
|
+
function ErrorInfo(props) {
|
|
164
|
+
const { errors } = props;
|
|
165
|
+
const [open, setOpen] = useState(false);
|
|
166
|
+
const { classes } = useStyles();
|
|
167
|
+
return (
|
|
168
|
+
<PopperMenu
|
|
169
|
+
open={open}
|
|
170
|
+
setOpen={setOpen}
|
|
171
|
+
buttonIcon={<WarningIcon color="error" />}
|
|
172
|
+
buttonClassName={classes.iconButton}
|
|
173
|
+
placement="bottom-end"
|
|
174
|
+
title="View Errors"
|
|
175
|
+
aria-label="Open error info"
|
|
176
|
+
>
|
|
177
|
+
<List className={classes.errorList}>
|
|
178
|
+
{errors.map((error, index) => (
|
|
179
|
+
<Alert
|
|
180
|
+
// eslint-disable-next-line react/no-array-index-key
|
|
181
|
+
key={`${index}-${error.name}-${error.message}`}
|
|
182
|
+
severity="error"
|
|
183
|
+
className={classes.errorListAlert}
|
|
184
|
+
slots={{ message: 'span' }}
|
|
185
|
+
slotProps={{ message: { className: classes.errorListItemText } }}
|
|
186
|
+
>
|
|
187
|
+
{error.name}: {error.message}
|
|
188
|
+
</Alert>
|
|
189
|
+
))}
|
|
190
|
+
</List>
|
|
191
|
+
</PopperMenu>
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
141
195
|
|
|
142
196
|
function ClosePaneButton(props) {
|
|
143
197
|
const { removeGridComponent } = props;
|
|
@@ -147,7 +201,7 @@ function ClosePaneButton(props) {
|
|
|
147
201
|
onClick={removeGridComponent}
|
|
148
202
|
size="small"
|
|
149
203
|
className={classes.iconButton}
|
|
150
|
-
title="
|
|
204
|
+
title="Close View"
|
|
151
205
|
aria-label="Close panel button"
|
|
152
206
|
>
|
|
153
207
|
<CloseIcon />
|
|
@@ -159,9 +213,11 @@ export function TitleInfo(props) {
|
|
|
159
213
|
const {
|
|
160
214
|
title, info, children, isScroll, isSpatial, removeGridComponent, urls,
|
|
161
215
|
isReady, options, closeButtonVisible = true, downloadButtonVisible = true,
|
|
162
|
-
helpText, withPadding = true,
|
|
216
|
+
helpText, withPadding = true, errors: errorsProp,
|
|
163
217
|
} = props;
|
|
164
218
|
|
|
219
|
+
const errors = errorsProp?.filter(Boolean);
|
|
220
|
+
|
|
165
221
|
const { classes } = useTitleStyles();
|
|
166
222
|
|
|
167
223
|
return (
|
|
@@ -183,6 +239,11 @@ export function TitleInfo(props) {
|
|
|
183
239
|
urls={urls}
|
|
184
240
|
/>
|
|
185
241
|
) : null}
|
|
242
|
+
{Array.isArray(errors) && errors.length > 0 ? (
|
|
243
|
+
<ErrorInfo
|
|
244
|
+
errors={errors}
|
|
245
|
+
/>
|
|
246
|
+
) : null}
|
|
186
247
|
{helpText ? (
|
|
187
248
|
<HelpButton
|
|
188
249
|
helpText={helpText}
|
package/src/VitS.js
CHANGED
|
@@ -5,12 +5,14 @@ import {
|
|
|
5
5
|
createCache,
|
|
6
6
|
} from '@vitessce/styles';
|
|
7
7
|
import {
|
|
8
|
+
QueryCache,
|
|
8
9
|
QueryClient,
|
|
9
10
|
QueryClientProvider,
|
|
10
11
|
} from '@tanstack/react-query';
|
|
11
12
|
import { isEqual } from 'lodash-es';
|
|
12
13
|
import { buildConfigSchema, latestConfigSchema } from '@vitessce/schemas';
|
|
13
14
|
import {
|
|
15
|
+
log,
|
|
14
16
|
setLogLevel, setDebugMode,
|
|
15
17
|
DEFAULT_LOG_LEVEL, DEFAULT_DEBUG_MODE,
|
|
16
18
|
} from '@vitessce/globals';
|
|
@@ -21,7 +23,6 @@ import {
|
|
|
21
23
|
AuxiliaryProvider,
|
|
22
24
|
createAuxiliaryStore,
|
|
23
25
|
} from './state/hooks.js';
|
|
24
|
-
|
|
25
26
|
import VitessceGrid from './VitessceGrid.js';
|
|
26
27
|
import { Warning } from './Warning.js';
|
|
27
28
|
import { DebugWindow } from './DebugWindow.js';
|
|
@@ -206,6 +207,15 @@ export function VitS(props) {
|
|
|
206
207
|
retry: 2,
|
|
207
208
|
},
|
|
208
209
|
},
|
|
210
|
+
// Reference: https://tkdodo.eu/blog/react-query-error-handling#the-global-callbacks
|
|
211
|
+
queryCache: new QueryCache({
|
|
212
|
+
onError: (error) => {
|
|
213
|
+
log.error(error);
|
|
214
|
+
if (onWarn) {
|
|
215
|
+
onWarn(error.message);
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
}),
|
|
209
219
|
// TODO: should the queryClient be shared? Or have an option to be shared
|
|
210
220
|
// (e.g., based on remountOnUidChange)?
|
|
211
221
|
}), [configKey]);
|
package/src/data-hook-utils.js
CHANGED
|
@@ -4,32 +4,15 @@ import {
|
|
|
4
4
|
capitalize,
|
|
5
5
|
getInitialCoordinationScopePrefix,
|
|
6
6
|
} from '@vitessce/utils';
|
|
7
|
-
import { log } from '@vitessce/globals';
|
|
8
7
|
import { STATUS } from '@vitessce/constants-internal';
|
|
9
8
|
import {
|
|
10
|
-
AbstractLoaderError,
|
|
11
9
|
LoaderNotFoundError,
|
|
12
|
-
} from '@vitessce/
|
|
10
|
+
} from '@vitessce/error';
|
|
13
11
|
import {
|
|
14
12
|
getMatchingLoader,
|
|
15
13
|
useMatchingLoader,
|
|
16
|
-
useSetWarning,
|
|
17
14
|
} from './state/hooks.js';
|
|
18
15
|
|
|
19
|
-
/**
|
|
20
|
-
* Warn via publishing to the console
|
|
21
|
-
* and to the global warning store.
|
|
22
|
-
* @param {AbstractLoaderError} error An error instance.
|
|
23
|
-
*/
|
|
24
|
-
export function warn(error, setWarning) {
|
|
25
|
-
setWarning(error.message);
|
|
26
|
-
log.warn(error.message);
|
|
27
|
-
log.error(error.stack);
|
|
28
|
-
if (error instanceof AbstractLoaderError) {
|
|
29
|
-
error.warnInConsole();
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
16
|
/**
|
|
34
17
|
* Initialize values in the coordination space.
|
|
35
18
|
* @param {object} values Object where
|
|
@@ -76,7 +59,7 @@ export async function dataQueryFn(ctx) {
|
|
|
76
59
|
// No loader was found.
|
|
77
60
|
if (isRequired) {
|
|
78
61
|
// Status: error
|
|
79
|
-
throw new LoaderNotFoundError(
|
|
62
|
+
throw new LoaderNotFoundError(`Loader not found for parameters: ${dataset}, ${dataType}, ${JSON.stringify(matchOn)}`);
|
|
80
63
|
} else {
|
|
81
64
|
// Status: success
|
|
82
65
|
return { data: placeholderObject };
|
|
@@ -100,7 +83,7 @@ export async function dataQueryFn(ctx) {
|
|
|
100
83
|
* @param {object} initialCoordinationValues Object where
|
|
101
84
|
* keys are coordination type names with the prefix 'initialize',
|
|
102
85
|
* values are initialization preferences as boolean values.
|
|
103
|
-
* @returns {array} [data, status, urls] where
|
|
86
|
+
* @returns {array} [data, status, urls, error] where
|
|
104
87
|
* cells is an object and cellsCount is the
|
|
105
88
|
* number of items in the cells object.
|
|
106
89
|
*/
|
|
@@ -108,7 +91,6 @@ export function useDataType(
|
|
|
108
91
|
dataType, loaders, dataset, isRequired,
|
|
109
92
|
coordinationSetters, initialCoordinationValues, matchOn,
|
|
110
93
|
) {
|
|
111
|
-
const setWarning = useSetWarning();
|
|
112
94
|
const placeholderObject = useMemo(() => ({}), []);
|
|
113
95
|
const dataQuery = useQuery({
|
|
114
96
|
// TODO: only enable when loaders has been initialized?
|
|
@@ -130,8 +112,6 @@ export function useDataType(
|
|
|
130
112
|
|
|
131
113
|
const coordinationValues = data?.coordinationValues;
|
|
132
114
|
const urls = data?.urls;
|
|
133
|
-
const requestInit = data?.requestInit;
|
|
134
|
-
|
|
135
115
|
|
|
136
116
|
useEffect(() => {
|
|
137
117
|
initCoordinationSpace(
|
|
@@ -141,14 +121,8 @@ export function useDataType(
|
|
|
141
121
|
);
|
|
142
122
|
}, [coordinationValues]);
|
|
143
123
|
|
|
144
|
-
useEffect(() => {
|
|
145
|
-
if (error) {
|
|
146
|
-
warn(error, setWarning);
|
|
147
|
-
}
|
|
148
|
-
}, [error, setWarning]);
|
|
149
|
-
|
|
150
124
|
const dataStatus = isFetching ? STATUS.LOADING : status;
|
|
151
|
-
return [loadedData, dataStatus, urls,
|
|
125
|
+
return [loadedData, dataStatus, urls, error];
|
|
152
126
|
}
|
|
153
127
|
|
|
154
128
|
/**
|
|
@@ -168,9 +142,10 @@ export function useDataType(
|
|
|
168
142
|
* @param {object} initialCoordinationValues Object where
|
|
169
143
|
* keys are coordination type names with the prefix 'initialize',
|
|
170
144
|
* values are initialization preferences as boolean values.
|
|
171
|
-
* @
|
|
172
|
-
*
|
|
173
|
-
*
|
|
145
|
+
* @param {object} matchOnObj Coordination values used to obtain a matching loader.
|
|
146
|
+
* @param {function} mergeCoordination Function to merge coordination values.
|
|
147
|
+
* @param {string} viewUid The view UID to use for merging coordination values.
|
|
148
|
+
* @returns {array} [data, status, urls, errors]
|
|
174
149
|
*/
|
|
175
150
|
export function useDataTypeMulti(
|
|
176
151
|
dataType, loaders, dataset, isRequired,
|
|
@@ -178,8 +153,6 @@ export function useDataTypeMulti(
|
|
|
178
153
|
mergeCoordination, viewUid,
|
|
179
154
|
) {
|
|
180
155
|
const placeholderObject = useMemo(() => ({}), []);
|
|
181
|
-
const setWarning = useSetWarning();
|
|
182
|
-
|
|
183
156
|
const matchOnEntries = matchOnObj ? Object.entries(matchOnObj) : [];
|
|
184
157
|
const dataQueries = useQueries({
|
|
185
158
|
// eslint-disable-next-line no-unused-vars
|
|
@@ -201,6 +174,7 @@ export function useDataTypeMulti(
|
|
|
201
174
|
|
|
202
175
|
const anyLoading = dataQueries.some(q => q.isFetching);
|
|
203
176
|
const anyError = dataQueries.some(q => q.isError);
|
|
177
|
+
const errors = anyError ? dataQueries.filter(q => q.isError).map(q => q.error) : [];
|
|
204
178
|
// eslint-disable-next-line no-nested-ternary
|
|
205
179
|
const dataStatus = anyLoading ? STATUS.LOADING : (anyError ? STATUS.ERROR : STATUS.SUCCESS);
|
|
206
180
|
const isSuccess = dataStatus === STATUS.SUCCESS;
|
|
@@ -229,14 +203,6 @@ export function useDataTypeMulti(
|
|
|
229
203
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
230
204
|
}, [isSuccess]);
|
|
231
205
|
|
|
232
|
-
useEffect(() => {
|
|
233
|
-
dataQueries.map(q => q.error).filter(e => Boolean(e)).forEach((error) => {
|
|
234
|
-
warn(error, setWarning);
|
|
235
|
-
});
|
|
236
|
-
// Deliberate dependency omissions: use indirect dependencies for efficiency.
|
|
237
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
238
|
-
}, [anyError, setWarning]);
|
|
239
|
-
|
|
240
206
|
// Convert data to object keyed by scopeKey.
|
|
241
207
|
const data = useMemo(() => Object.fromEntries(
|
|
242
208
|
matchOnEntries.map(([scopeKey], i) => ([scopeKey, dataQueries[i].data?.data])),
|
|
@@ -253,7 +219,7 @@ export function useDataTypeMulti(
|
|
|
253
219
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
254
220
|
), [dataQueries]);
|
|
255
221
|
|
|
256
|
-
return [data, dataStatus, urls];
|
|
222
|
+
return [data, dataStatus, urls, errors];
|
|
257
223
|
}
|
|
258
224
|
|
|
259
225
|
export function useHasLoader(loaders, dataset, dataType, matchOn) {
|
|
@@ -1,16 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
2
|
import { useQueries } from '@tanstack/react-query';
|
|
3
3
|
import { extent } from 'd3-array';
|
|
4
4
|
import { DataType, STATUS } from '@vitessce/constants-internal';
|
|
5
5
|
import {
|
|
6
6
|
LoaderNotFoundError,
|
|
7
|
-
} from '@vitessce/
|
|
7
|
+
} from '@vitessce/error';
|
|
8
8
|
import {
|
|
9
9
|
getMatchingLoader,
|
|
10
|
-
useSetWarning,
|
|
11
10
|
} from './state/hooks.js';
|
|
12
11
|
import {
|
|
13
|
-
warn,
|
|
14
12
|
dataQueryFn,
|
|
15
13
|
} from './data-hook-utils.js';
|
|
16
14
|
|
|
@@ -324,18 +322,26 @@ async function featureSelectionQueryFn(ctx) {
|
|
|
324
322
|
}
|
|
325
323
|
// No loader was found.
|
|
326
324
|
if (isRequired) {
|
|
327
|
-
throw new LoaderNotFoundError(
|
|
325
|
+
throw new LoaderNotFoundError(`Loader not found for parameters: ${dataset}, ${dataType}, ${JSON.stringify(matchOn)}`);
|
|
328
326
|
} else {
|
|
329
327
|
return { data: null, dataKey: null };
|
|
330
328
|
}
|
|
331
329
|
}
|
|
332
330
|
|
|
331
|
+
/**
|
|
332
|
+
* Get feature values for a multi-level feature selection.
|
|
333
|
+
* @param {object} loaders
|
|
334
|
+
* @param {string} dataset
|
|
335
|
+
* @param {boolean} isRequired
|
|
336
|
+
* @param {object} matchOnObj
|
|
337
|
+
* @param {array} selections
|
|
338
|
+
* @param {number} depth
|
|
339
|
+
* @returns [data, geneName, extents, normData, status, errors]
|
|
340
|
+
*/
|
|
333
341
|
export function useFeatureSelectionMultiLevel(
|
|
334
342
|
loaders, dataset, isRequired, matchOnObj, selections,
|
|
335
343
|
depth,
|
|
336
344
|
) {
|
|
337
|
-
const setWarning = useSetWarning();
|
|
338
|
-
|
|
339
345
|
// Create a flat list of tuples (queryKey, scopeInfo).
|
|
340
346
|
const queryKeyScopeTuples = useMemo(() => getFeatureSelectionQueryKeyScopeTuples(
|
|
341
347
|
selections, matchOnObj, depth, dataset, DataType.OBS_FEATURE_MATRIX, isRequired,
|
|
@@ -353,6 +359,7 @@ export function useFeatureSelectionMultiLevel(
|
|
|
353
359
|
|
|
354
360
|
const anyLoading = featureQueries.some(q => q.isFetching);
|
|
355
361
|
const anyError = featureQueries.some(q => q.isError);
|
|
362
|
+
const errors = anyError ? featureQueries.filter(q => q.isError).map(q => q.error) : [];
|
|
356
363
|
// eslint-disable-next-line no-nested-ternary
|
|
357
364
|
const dataStatus = anyLoading ? STATUS.LOADING : (anyError ? STATUS.ERROR : STATUS.SUCCESS);
|
|
358
365
|
const flatGeneData = featureQueries.map(q => q.data?.data || null);
|
|
@@ -360,17 +367,6 @@ export function useFeatureSelectionMultiLevel(
|
|
|
360
367
|
const flatExtents = featureQueries.map(q => q.data?.dataExtent || null);
|
|
361
368
|
const flatNormData = featureQueries.map(q => q.data?.normData || null);
|
|
362
369
|
|
|
363
|
-
useEffect(() => {
|
|
364
|
-
featureQueries
|
|
365
|
-
.map(q => q.error)
|
|
366
|
-
.filter(e => Boolean(e))
|
|
367
|
-
.forEach((error) => {
|
|
368
|
-
warn(error, setWarning);
|
|
369
|
-
});
|
|
370
|
-
// Deliberate dependency omissions: use indirect dependencies for efficiency.
|
|
371
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
372
|
-
}, [anyError, setWarning]);
|
|
373
|
-
|
|
374
370
|
// Need to re-nest the geneData and the loadedGeneName info.
|
|
375
371
|
const [geneData, loadedGeneName, extents, normData] = useMemo(() => {
|
|
376
372
|
const nestedGeneData = nestFeatureSelectionQueryResults(queryKeyScopeTuples, flatGeneData);
|
|
@@ -390,7 +386,7 @@ export function useFeatureSelectionMultiLevel(
|
|
|
390
386
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
391
387
|
}, [featureQueries.reduce((a, h) => a + h.dataUpdatedAt, 0), queryKeyScopeTuples]);
|
|
392
388
|
|
|
393
|
-
return [geneData, loadedGeneName, extents, normData, dataStatus];
|
|
389
|
+
return [geneData, loadedGeneName, extents, normData, dataStatus, errors];
|
|
394
390
|
}
|
|
395
391
|
|
|
396
392
|
async function matrixIndicesQueryFn(ctx) {
|
|
@@ -424,18 +420,25 @@ async function matrixIndicesQueryFn(ctx) {
|
|
|
424
420
|
}
|
|
425
421
|
// No loader was found.
|
|
426
422
|
if (isRequired) {
|
|
427
|
-
throw new LoaderNotFoundError(
|
|
423
|
+
throw new LoaderNotFoundError(`Loader not found for parameters: ${dataset}, ${dataType}, ${JSON.stringify(matchOn)}`);
|
|
428
424
|
} else {
|
|
429
425
|
return { data: null, dataKey: null };
|
|
430
426
|
}
|
|
431
427
|
}
|
|
432
428
|
|
|
429
|
+
/**
|
|
430
|
+
* Get matrix indices for a multi-level coordination.
|
|
431
|
+
* @param {object} loaders
|
|
432
|
+
* @param {string} dataset
|
|
433
|
+
* @param {boolean} isRequired
|
|
434
|
+
* @param {object} matchOnObj
|
|
435
|
+
* @param {number} depth
|
|
436
|
+
* @returns [data, dataStatus, errors]
|
|
437
|
+
*/
|
|
433
438
|
export function useObsFeatureMatrixIndicesMultiLevel(
|
|
434
439
|
loaders, dataset, isRequired, matchOnObj,
|
|
435
440
|
depth,
|
|
436
441
|
) {
|
|
437
|
-
const setWarning = useSetWarning();
|
|
438
|
-
|
|
439
442
|
// Create a flat list of tuples (queryKey, scopeInfo).
|
|
440
443
|
const queryKeyScopeTuples = useMemo(() => getMatrixIndicesQueryKeyScopeTuples(
|
|
441
444
|
matchOnObj, depth, dataset, DataType.OBS_FEATURE_MATRIX, isRequired,
|
|
@@ -453,21 +456,11 @@ export function useObsFeatureMatrixIndicesMultiLevel(
|
|
|
453
456
|
|
|
454
457
|
const anyLoading = indicesQueries.some(q => q.isFetching);
|
|
455
458
|
const anyError = indicesQueries.some(q => q.isError);
|
|
459
|
+
const errors = anyError ? indicesQueries.filter(q => q.isError).map(q => q.error) : [];
|
|
456
460
|
// eslint-disable-next-line no-nested-ternary
|
|
457
461
|
const dataStatus = anyLoading ? STATUS.LOADING : (anyError ? STATUS.ERROR : STATUS.SUCCESS);
|
|
458
462
|
const flatIndicesData = indicesQueries.map(q => q.data?.data || null);
|
|
459
463
|
|
|
460
|
-
useEffect(() => {
|
|
461
|
-
indicesQueries
|
|
462
|
-
.map(q => q.error)
|
|
463
|
-
.filter(e => Boolean(e))
|
|
464
|
-
.forEach((error) => {
|
|
465
|
-
warn(error, setWarning);
|
|
466
|
-
});
|
|
467
|
-
// Deliberate dependency omissions: use indirect dependencies for efficiency.
|
|
468
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
469
|
-
}, [anyError, setWarning]);
|
|
470
|
-
|
|
471
464
|
// Need to re-nest the geneData and the loadedGeneName info.
|
|
472
465
|
const indicesData = useMemo(() => {
|
|
473
466
|
const nestedIndicesData = nestQueryResults(queryKeyScopeTuples, flatIndicesData);
|
|
@@ -484,15 +477,23 @@ export function useObsFeatureMatrixIndicesMultiLevel(
|
|
|
484
477
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
485
478
|
}, [indicesQueries.reduce((a, h) => a + h.dataUpdatedAt, 0), queryKeyScopeTuples]);
|
|
486
479
|
|
|
487
|
-
return [indicesData, dataStatus];
|
|
480
|
+
return [indicesData, dataStatus, errors];
|
|
488
481
|
}
|
|
489
482
|
|
|
483
|
+
/**
|
|
484
|
+
* Get data for multi-level coordination.
|
|
485
|
+
* @param {object} loaders
|
|
486
|
+
* @param {string} dataset
|
|
487
|
+
* @param {boolean} isRequired
|
|
488
|
+
* @param {object} matchOnObj
|
|
489
|
+
* @param {number} depth
|
|
490
|
+
* @param {string} dataType
|
|
491
|
+
* @returns {array} [data, status, errors]
|
|
492
|
+
*/
|
|
490
493
|
function useDataTypeMultiLevel(
|
|
491
494
|
loaders, dataset, isRequired, matchOnObj,
|
|
492
495
|
depth, dataType,
|
|
493
496
|
) {
|
|
494
|
-
const setWarning = useSetWarning();
|
|
495
|
-
|
|
496
497
|
// Create a flat list of tuples (queryKey, scopeInfo).
|
|
497
498
|
const queryKeyScopeTuples = useMemo(() => getQueryKeyScopeTuples(
|
|
498
499
|
matchOnObj, depth, dataset, dataType, isRequired,
|
|
@@ -510,21 +511,11 @@ function useDataTypeMultiLevel(
|
|
|
510
511
|
|
|
511
512
|
const anyLoading = locationsQueries.some(q => q.isFetching);
|
|
512
513
|
const anyError = locationsQueries.some(q => q.isError);
|
|
514
|
+
const errors = anyError ? locationsQueries.filter(q => q.isError).map(q => q.error) : [];
|
|
513
515
|
// eslint-disable-next-line no-nested-ternary
|
|
514
516
|
const dataStatus = anyLoading ? STATUS.LOADING : (anyError ? STATUS.ERROR : STATUS.SUCCESS);
|
|
515
517
|
const flatIndicesData = locationsQueries.map(q => q.data?.data || null);
|
|
516
518
|
|
|
517
|
-
useEffect(() => {
|
|
518
|
-
locationsQueries
|
|
519
|
-
.map(q => q.error)
|
|
520
|
-
.filter(e => Boolean(e))
|
|
521
|
-
.forEach((error) => {
|
|
522
|
-
warn(error, setWarning);
|
|
523
|
-
});
|
|
524
|
-
// Deliberate dependency omissions: use indirect dependencies for efficiency.
|
|
525
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
526
|
-
}, [anyError, setWarning]);
|
|
527
|
-
|
|
528
519
|
// Need to re-nest the geneData and the loadedGeneName info.
|
|
529
520
|
const locationsData = useMemo(() => {
|
|
530
521
|
const nestedIndicesData = nestQueryResults(queryKeyScopeTuples, flatIndicesData);
|
|
@@ -539,9 +530,18 @@ function useDataTypeMultiLevel(
|
|
|
539
530
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
540
531
|
}, [locationsQueries.reduce((a, h) => a + h.dataUpdatedAt, 0), queryKeyScopeTuples]);
|
|
541
532
|
|
|
542
|
-
return [locationsData, dataStatus];
|
|
533
|
+
return [locationsData, dataStatus, errors];
|
|
543
534
|
}
|
|
544
535
|
|
|
536
|
+
/**
|
|
537
|
+
* Wrapper around useDataTypeMultiLevel.
|
|
538
|
+
* @param {object} loaders
|
|
539
|
+
* @param {string} dataset
|
|
540
|
+
* @param {boolean} isRequired
|
|
541
|
+
* @param {object} matchOnObj
|
|
542
|
+
* @param {number} depth
|
|
543
|
+
* @returns {array} [data, status, errors]
|
|
544
|
+
*/
|
|
545
545
|
export function useObsLocationsMultiLevel(
|
|
546
546
|
loaders, dataset, isRequired, matchOnObj,
|
|
547
547
|
depth,
|
|
@@ -552,6 +552,15 @@ export function useObsLocationsMultiLevel(
|
|
|
552
552
|
);
|
|
553
553
|
}
|
|
554
554
|
|
|
555
|
+
/**
|
|
556
|
+
* Wrapper around useDataTypeMultiLevel.
|
|
557
|
+
* @param {object} loaders
|
|
558
|
+
* @param {string} dataset
|
|
559
|
+
* @param {boolean} isRequired
|
|
560
|
+
* @param {object} matchOnObj
|
|
561
|
+
* @param {number} depth
|
|
562
|
+
* @returns {array} [data, status, errors]
|
|
563
|
+
*/
|
|
555
564
|
export function useObsSetsMultiLevel(
|
|
556
565
|
loaders, dataset, isRequired, matchOnObj,
|
|
557
566
|
depth,
|
|
@@ -562,6 +571,15 @@ export function useObsSetsMultiLevel(
|
|
|
562
571
|
);
|
|
563
572
|
}
|
|
564
573
|
|
|
574
|
+
/**
|
|
575
|
+
* Wrapper around useDataTypeMultiLevel.
|
|
576
|
+
* @param {object} loaders
|
|
577
|
+
* @param {string} dataset
|
|
578
|
+
* @param {boolean} isRequired
|
|
579
|
+
* @param {object} matchOnObj
|
|
580
|
+
* @param {number} depth
|
|
581
|
+
* @returns {array} [data, status, errors]
|
|
582
|
+
*/
|
|
565
583
|
export function useObsLabelsMultiLevel(
|
|
566
584
|
loaders, dataset, isRequired, matchOnObj,
|
|
567
585
|
depth,
|