Skip to content

Commit 1c0e6b2

Browse files
AKarmanovThomas Draier
andauthored
QA-14194 Cherry pick code and update tests (#220)
* BACKLOG-20024: Aggregate useNodeInfo queries * Handle refetches * Improved watch * QA-14194 Clean up code * QA-14194 Update useNodeInfo tests Co-authored-by: Thomas Draier <tdraier@jahia.com>
1 parent 8fa2cf5 commit 1c0e6b2

File tree

6 files changed

+370
-148
lines changed

6 files changed

+370
-148
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import {validOptions} from './useNodeInfo.gql-queries';
2+
import {useRef} from 'react';
3+
import deepEquals from 'fast-deep-equal';
4+
5+
const clean = obj => obj && Object.fromEntries(Object.entries(obj).filter(([_, v]) => v !== null && v !== undefined));
6+
const cleanVariables = obj => obj && Object.fromEntries(Object.entries(obj).filter(([_, v]) => v !== null && v !== undefined));
7+
const cleanOptions = obj => obj && Object.fromEntries(Object.entries(obj).filter(([k, v]) => v !== null && v !== undefined && validOptions.indexOf(k) !== -1));
8+
9+
export function useMemoRequest(variables, queryOptions, options, setResult) {
10+
variables = cleanVariables(variables);
11+
queryOptions = clean(queryOptions);
12+
options = cleanOptions(options);
13+
14+
const requestValue = {variables, queryOptions, options, setResult};
15+
const requestRef = useRef(requestValue);
16+
17+
if (!deepEquals(variables, requestRef.current.variables) || !deepEquals(queryOptions, requestRef.current.queryOptions) || !deepEquals(options, requestRef.current.options)) {
18+
requestRef.current = requestValue;
19+
}
20+
21+
return [requestRef.current, requestRef.current === requestValue];
22+
}

packages/data-helper/src/hooks/useNodeInfo/useNodeInfo.gql-queries.js

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,30 @@ const getBaseQueryAndVariables = variables => {
105105
};
106106
};
107107

108+
export const validOptions = [
109+
'getDisplayName',
110+
'getPrimaryNodeType',
111+
'getParent',
112+
'getAggregatedPublicationInfo',
113+
'getOperationSupport',
114+
'getPermissions',
115+
'getSitePermissions',
116+
'getIsNodeTypes',
117+
'getProperties',
118+
'getSiteInstalledModules',
119+
'getSiteLanguages',
120+
'getDisplayableNodePath',
121+
'getLockInfo',
122+
'getChildNodeTypes',
123+
'getContributeTypesRestrictions',
124+
'getSubNodesCount',
125+
'getMimeType'
126+
];
127+
108128
export const getQuery = (variables, schemaResult, options = {}) => {
109129
const fragments = [];
110130

111131
const {baseQuery, generatedVariables, skip} = getBaseQueryAndVariables(variables);
112-
const {error, loading, data} = schemaResult;
113132

114133
if (!skip) {
115134
if (options.getDisplayName) {
@@ -137,13 +156,11 @@ export const getQuery = (variables, schemaResult, options = {}) => {
137156
}
138157

139158
if (options.getAggregatedPublicationInfo) {
140-
if (!error && !loading && data) {
141-
let supportsExistsInLive = data.__type && data.__type.fields && data.__type.fields.find(field => field.name === 'existsInLive') !== undefined;
142-
if (supportsExistsInLive) {
143-
fragments.push(aggregatedPublicationInfoWithExistInLive);
144-
} else {
145-
fragments.push(aggregatedPublicationInfo);
146-
}
159+
let supportsExistsInLive = schemaResult && schemaResult.__type && schemaResult.__type.fields && schemaResult.__type.fields.find(field => field.name === 'existsInLive') !== undefined;
160+
if (supportsExistsInLive) {
161+
fragments.push(aggregatedPublicationInfoWithExistInLive);
162+
} else {
163+
fragments.push(aggregatedPublicationInfo);
147164
}
148165

149166
if (!variables.language) {
@@ -231,7 +248,6 @@ export const getQuery = (variables, schemaResult, options = {}) => {
231248
return {
232249
query: replaceFragmentsInDocument(baseQuery, fragments),
233250
generatedVariables,
234-
skip,
235-
loading
251+
skip
236252
};
237253
};

packages/data-helper/src/hooks/useNodeInfo/useNodeInfo.js

Lines changed: 111 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,139 @@
1-
import {useMemo} from 'react';
2-
import {useQuery} from 'react-apollo';
1+
import {useEffect, useState} from 'react';
2+
import {useApolloClient} from 'react-apollo';
33
import {getQuery} from './useNodeInfo.gql-queries';
4-
import {useDeepCompareMemoize} from '../useDeepCompareMemo';
54
import {getEncodedPermissionName} from '../../fragments/getPermissionFragment';
65
import {getEncodedNodeTypeName} from '../../fragments/getIsNodeTypeFragment';
7-
import {useSchemaFields} from '../useSchemaFields';
6+
import {SCHEMA_FIELDS_QUERY} from '../useSchemaFields/useSchemaFields.gql-queries';
7+
import {isSubset, merge} from './useNodeInfo.utils';
8+
import {useMemoRequest} from './useMemoRequest';
9+
import deepEquals from 'fast-deep-equal';
10+
11+
let queue = [];
12+
let schemaResult;
13+
let timeout;
14+
let observedQueries = [];
15+
16+
function scheduleQueue(client) {
17+
if (!timeout && schemaResult) {
18+
timeout = setTimeout(() => {
19+
timeoutHandler(client);
20+
clearTimeout(timeout);
21+
timeout = null;
22+
}, 0);
23+
}
24+
}
25+
26+
const timeoutHandler = client => {
27+
const mergedQueue = [];
28+
29+
queue.forEach(request => {
30+
const toInsert = {
31+
variables: request.variables,
32+
queryOptions: request.queryOptions,
33+
options: request.options,
34+
originals: [request]
35+
};
36+
37+
const mergeable = mergedQueue.find(q => JSON.stringify(q.queryOptions) === JSON.stringify(toInsert.queryOptions) && (isSubset(q.variables, toInsert.variables) || isSubset(toInsert.variables, q.variables)));
38+
39+
if (mergeable) {
40+
merge(mergeable, toInsert);
41+
} else {
42+
mergedQueue.push({
43+
variables: toInsert.variables && {...toInsert.variables},
44+
queryOptions: toInsert.queryOptions && {...toInsert.queryOptions},
45+
options: toInsert.options && {...toInsert.options},
46+
originals: toInsert.originals
47+
});
48+
}
49+
});
50+
51+
observedQueries.forEach(obs => obs.unsubscribe());
52+
observedQueries = [];
53+
54+
mergedQueue.forEach(mergedRequest => {
55+
const {variables, queryOptions, options, originals} = mergedRequest;
56+
const {query, generatedVariables, skip} = getQuery(variables, schemaResult, options);
57+
if (skip) {
58+
// No query to execute
59+
originals.forEach(request => {
60+
request.setResult({
61+
loading: false
62+
});
63+
});
64+
} else {
65+
const watchedQuery = client.watchQuery({query, errorPolicy: 'ignore', ...queryOptions, variables: generatedVariables}).subscribe(({data, ...others}) => {
66+
const result = getResult(data, others, options, query, generatedVariables);
67+
68+
originals.forEach(request => {
69+
if (!deepEquals(request.result, result)) {
70+
request.result = result;
71+
request.setResult({
72+
...result,
73+
refetch: () => {
74+
client.refetchQueries({include: [query]});
75+
}
76+
});
77+
}
78+
});
79+
});
80+
observedQueries.push(watchedQuery);
81+
}
82+
});
83+
};
884

985
export const useNodeInfo = (variables, options, queryOptions) => {
10-
let schemaResult = useSchemaFields({type: 'GqlPublicationInfo'});
11-
// Use ref to avoid infinite loop, as query object will be regenerated every time
12-
const memoizedVariables = useDeepCompareMemoize(variables);
13-
const memoizedOptions = useDeepCompareMemoize(options);
86+
const [result, setResult] = useState({
87+
loading: true
88+
});
89+
90+
const client = useApolloClient();
91+
92+
if (!schemaResult) {
93+
client.query({query: SCHEMA_FIELDS_QUERY, variables: {type: 'GqlPublicationInfo'}}).then(({data}) => {
94+
schemaResult = data;
95+
scheduleQueue(client);
96+
});
97+
}
98+
99+
// Normalize and memoize request
100+
const [currentRequest] = useMemoRequest(variables, queryOptions, options, setResult);
101+
useEffect(() => {
102+
queue.push(currentRequest);
103+
scheduleQueue(client);
14104

15-
const {query, generatedVariables, skip, loading} = useMemo(() => getQuery(memoizedVariables, schemaResult, memoizedOptions), [memoizedVariables, schemaResult, memoizedOptions]);
105+
return () => {
106+
queue.splice(queue.indexOf(currentRequest), 1);
107+
};
108+
}, [currentRequest]);
16109

17-
const {data, ...others} = useQuery(query, {errorPolicy: 'ignore', ...queryOptions, variables: generatedVariables, skip: (skip || loading)});
110+
return result;
111+
};
18112

113+
const getResult = (data, others, options, query, generatedVariables) => {
19114
const node = (data && data.jcr && (data.jcr.nodeByPath || data.jcr.nodeById)) || null;
20115
const nodes = (data && data.jcr && (data.jcr.nodesByPath || data.jcr.nodesById)) || null;
21-
22-
if (loading) {
23-
return {loading};
24-
}
116+
let result = others;
25117

26118
if (node) {
27-
return {
28-
node: decodeResult(node, memoizedOptions),
119+
result = {
120+
node: decodeResult(node, options),
29121
...others,
30122
query,
31123
variables: generatedVariables
32124
};
33125
}
34126

35127
if (nodes) {
36-
return {
37-
nodes: nodes.map(n => decodeResult(n, memoizedOptions)),
128+
result = {
129+
nodes: nodes.map(n => decodeResult(n, options)),
38130
...others,
39131
query,
40132
variables: generatedVariables
41133
};
42134
}
43135

44-
return {
45-
...others
46-
};
136+
return result;
47137
};
48138

49139
const decodeResult = (nodeOrig, options) => {

0 commit comments

Comments
 (0)