Skip to content

Commit 749bbac

Browse files
authored
Merge pull request #306 from RobokopU24/feature/enriched-queries
Enrichment analysis
2 parents aaa8769 + 298c89f commit 749bbac

File tree

8 files changed

+1169
-388
lines changed

8 files changed

+1169
-388
lines changed

package-lock.json

Lines changed: 348 additions & 375 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,15 @@
4646
},
4747
"dependencies": {
4848
"@auth0/auth0-react": "^1.6.0",
49+
"@floating-ui/react": "^0.27.8",
4950
"@material-ui/core": "^4.11.0",
5051
"@material-ui/data-grid": "^4.0.0-alpha.2",
5152
"@material-ui/icons": "^4.9.1",
5253
"@material-ui/lab": "^4.0.0-alpha.56",
5354
"@tanstack/react-table": "^8.20.5",
5455
"ag-grid-community": "^23.2.1",
5556
"ag-grid-react": "^23.2.1",
56-
"axios": "^0.21.1",
57+
"axios": "^0.22.0",
5758
"axios-retry": "^3.1.8",
5859
"body-parser": "^1.20.0",
5960
"bootstrap": "^3.4.1",
@@ -72,10 +73,10 @@
7273
"path": "^0.12.7",
7374
"rc-slider": "^9.3.1",
7475
"rc-tooltip": "^4.2.1",
75-
"react": "^16.13.1",
76+
"react": "^17.0.2",
7677
"react-bootstrap": "^0.33.1",
7778
"react-bootstrap-dialog": "^0.13.0",
78-
"react-dom": "^16.13.1",
79+
"react-dom": "^17.0.2",
7980
"react-dropzone": "^11.0.2",
8081
"react-graph-vis": "^1.0.5",
8182
"react-icons": "^3.10.0",
@@ -89,6 +90,7 @@
8990
"react-virtualized": "^9.21.2",
9091
"react-widgets": "^4.5.0",
9192
"regenerator-runtime": "^0.13.7",
93+
"rich-textarea": "^0.26.4",
9294
"shortid": "^2.2.15",
9395
"slugify": "^1.4.5",
9496
"sqlite3": "^5.1.7"

src/hooks/use-query.js

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import React, {
2+
createContext, useCallback, useContext, useEffect, useRef, useState,
3+
} from 'react';
4+
5+
const CACHE_TTL_MS = 30 * 60 * 1000;
6+
const PRUNE_INTERVAL_MS = 5 * 60 * 1000;
7+
8+
const QueryCacheContext = createContext(null);
9+
10+
export const QueryCacheProvider = ({ children }) => {
11+
const [cache, setCache] = useState(new Map());
12+
13+
const get = useCallback((key) => {
14+
const entry = cache.get(key);
15+
if (!entry) return undefined;
16+
17+
const now = Date.now();
18+
if (now - entry.timestamp > CACHE_TTL_MS) {
19+
setCache((prev) => {
20+
const next = new Map(prev);
21+
next.delete(key);
22+
return next;
23+
});
24+
return undefined;
25+
}
26+
27+
return entry.data;
28+
}, [cache]);
29+
30+
const set = useCallback((key, value) => {
31+
setCache((prev) => {
32+
const next = new Map(prev);
33+
next.set(key, { data: value, timestamp: Date.now() });
34+
return next;
35+
});
36+
}, []);
37+
38+
useEffect(() => {
39+
const interval = setInterval(() => {
40+
setCache((prev) => {
41+
const now = Date.now();
42+
const next = new Map(prev);
43+
// eslint-disable-next-line no-restricted-syntax
44+
for (const [key, entry] of next.entries()) {
45+
if (now - entry.timestamp > CACHE_TTL_MS) {
46+
next.delete(key);
47+
}
48+
}
49+
return next;
50+
});
51+
}, PRUNE_INTERVAL_MS);
52+
53+
return () => clearInterval(interval);
54+
}, []);
55+
56+
return (
57+
<QueryCacheContext.Provider value={{ get, set }}>
58+
{children}
59+
</QueryCacheContext.Provider>
60+
);
61+
};
62+
63+
export const useQuery = ({
64+
queryFn,
65+
queryKey,
66+
debounceMs = 0,
67+
keepStaleData = false,
68+
}) => {
69+
const [data, setData] = useState(null);
70+
const [isLoading, setIsLoading] = useState(true);
71+
const [error, setError] = useState(null);
72+
73+
const cache = useContext(QueryCacheContext);
74+
if (!cache) throw new Error('You must use `useQuery` hook within `QueryCacheProvider`');
75+
76+
const timeoutRef = useRef(null);
77+
const abortControllerRef = useRef(null);
78+
79+
useEffect(() => {
80+
let ignore = false;
81+
82+
if (timeoutRef.current) {
83+
clearTimeout(timeoutRef.current);
84+
}
85+
86+
if (abortControllerRef.current) {
87+
abortControllerRef.current.abort();
88+
}
89+
90+
timeoutRef.current = setTimeout(() => {
91+
(async () => {
92+
if (!keepStaleData) setData(null);
93+
setIsLoading(true);
94+
setError(null);
95+
96+
const cachedData = cache.get(queryKey);
97+
if (cachedData) {
98+
setData(cachedData);
99+
setIsLoading(false);
100+
return;
101+
}
102+
103+
const controller = new AbortController();
104+
abortControllerRef.current = controller;
105+
106+
try {
107+
const d = await queryFn(controller.signal);
108+
109+
if (ignore) return;
110+
111+
cache.set(queryKey, d);
112+
setData(d);
113+
setIsLoading(false);
114+
} catch (err) {
115+
if (!controller.signal.aborted) {
116+
setError((Boolean(err) && err.message) || 'Unknown error');
117+
setIsLoading(false);
118+
}
119+
}
120+
})();
121+
}, debounceMs);
122+
123+
return () => {
124+
ignore = true;
125+
if (timeoutRef.current) {
126+
clearTimeout(timeoutRef.current);
127+
}
128+
if (abortControllerRef.current) {
129+
abortControllerRef.current.abort();
130+
}
131+
};
132+
133+
// Don't rerender if queryFn is different, this can cause an endless render loop if
134+
// the component calling useQuery uses a closure like queryFn: () => fetchFn(someLocalVar).
135+
136+
// This could be fixed by wrapping the closure in a useCallback in the calling component
137+
// or including a dependency array in this hook. All should work fine if all the queryFn
138+
// depends on is included in the queryKey.
139+
140+
// eslint-disable-next-line react-hooks/exhaustive-deps
141+
}, [queryKey, cache, debounceMs]);
142+
143+
return { data, isLoading, error };
144+
};

0 commit comments

Comments
 (0)