aboutsummaryrefslogtreecommitdiffstats
path: root/client/js
diff options
context:
space:
mode:
authorValerij Fredriksen <valerijf@yahooinc.com>2022-08-14 11:45:22 +0200
committerValerij Fredriksen <valerijf@yahooinc.com>2022-08-14 12:10:08 +0200
commitcba5caee77fd1b574888dfde8c86e0ff43869626 (patch)
tree1a61a8750604882e0700b83a990398b3da3964b7 /client/js
parent647d74bf81a2bd85f993df685717b749234b9340 (diff)
Collapse input and children
Diffstat (limited to 'client/js')
-rw-r--r--client/js/app/src/app/pages/querybuilder/context/__test__/query-builder-provider.test.jsx67
-rw-r--r--client/js/app/src/app/pages/querybuilder/context/query-builder-provider.jsx105
-rw-r--r--client/js/app/src/app/pages/querybuilder/query-filters/query-filters.jsx24
3 files changed, 88 insertions, 108 deletions
diff --git a/client/js/app/src/app/pages/querybuilder/context/__test__/query-builder-provider.test.jsx b/client/js/app/src/app/pages/querybuilder/context/__test__/query-builder-provider.test.jsx
index d37347a5580..5ccad2a054e 100644
--- a/client/js/app/src/app/pages/querybuilder/context/__test__/query-builder-provider.test.jsx
+++ b/client/js/app/src/app/pages/querybuilder/context/__test__/query-builder-provider.test.jsx
@@ -12,7 +12,7 @@ test('default state', () => {
expect(fixed).toEqual({
http: {},
params: {
- children: [{ id: '0', input: '', type: 'yql' }],
+ value: [{ id: '0', value: '', type: 'yql' }],
},
query: {
input: '{\n "yql": ""\n}',
@@ -28,44 +28,43 @@ test('default state', () => {
test('manipulates inputs', () => {
function assert(state, queryJson, querySearchParams, params) {
- expect(hideTypes(state.params).children).toEqual(params);
+ expect(hideTypes(state.params).value).toEqual(params);
expect({ ...state.query, input: JSON.parse(state.query.input) }).toEqual(
queryJson
);
const spState = reducer(state, { action: ACTION.SET_METHOD, data: 'GET' });
- expect(hideTypes(spState.params).children).toEqual(params);
+ expect(hideTypes(spState.params).value).toEqual(params);
expect(spState.query).toEqual(querySearchParams);
}
const s1 = reduce(state, [[ACTION.INPUT_ADD, { type: 'hits' }]]);
assert(s1, { input: { yql: '', hits: null } }, { input: 'yql=&hits=' }, [
- { id: '0', input: '', type: 'yql' },
- { id: '1', input: '', type: 'hits' },
+ { id: '0', value: '', type: 'yql' },
+ { id: '1', value: '', type: 'hits' },
]);
const s2 = reduce(s1, [
- [ACTION.INPUT_UPDATE, { id: '1', input: '12' }],
+ [ACTION.INPUT_UPDATE, { id: '1', value: '12' }],
[ACTION.INPUT_ADD, { type: 'ranking' }],
[ACTION.INPUT_UPDATE, { id: '1', type: 'offset' }],
[ACTION.INPUT_REMOVE, '0'],
[ACTION.INPUT_ADD, { id: '2', type: 'location' }],
[ACTION.INPUT_ADD, { id: '2', type: 'matchPhase' }],
- [ACTION.INPUT_UPDATE, { id: '2.0', input: 'us' }],
+ [ACTION.INPUT_UPDATE, { id: '2.0', value: 'us' }],
]);
assert(
s2,
{ input: { offset: 12, ranking: { location: 'us', matchPhase: {} } } },
{ input: 'offset=12&ranking.location=us' },
[
- { id: '1', input: '12', type: 'offset' },
+ { id: '1', value: '12', type: 'offset' },
{
id: '2',
- input: '',
type: 'ranking',
- children: [
- { id: '2.0', input: 'us', type: 'location' },
- { id: '2.1', input: '', type: 'matchPhase', children: [] },
+ value: [
+ { id: '2.0', value: 'us', type: 'location' },
+ { id: '2.1', type: 'matchPhase', value: [] },
],
},
]
@@ -76,8 +75,8 @@ test('manipulates inputs', () => {
{ input: { offset: 12, noCache: false } },
{ input: 'offset=12&noCache=' },
[
- { id: '1', input: '12', type: 'offset' },
- { id: '2', input: '', type: 'noCache' },
+ { id: '1', value: '12', type: 'offset' },
+ { id: '2', value: '', type: 'noCache' },
]
);
@@ -85,7 +84,7 @@ test('manipulates inputs', () => {
reduce(s2, [[ACTION.INPUT_REMOVE, '2']]),
{ input: { offset: 12 } },
{ input: 'offset=12' },
- [{ id: '1', input: '12', type: 'offset' }]
+ [{ id: '1', value: '12', type: 'offset' }]
);
});
@@ -98,63 +97,53 @@ test('set query', () => {
]);
function assert(inputJson, inputSearchParams, params) {
const s2 = query('POST', inputJson);
- expect(hideTypes(s2.params).children).toEqual(params);
+ expect(hideTypes(s2.params).value).toEqual(params);
expect(s2.query.input).toEqual(inputJson);
expect(s2.query.error).toBeUndefined();
if (inputSearchParams == null) return;
const s3 = query('GET', inputSearchParams);
- expect(hideTypes(s3.params).children).toEqual(params);
+ expect(hideTypes(s3.params).value).toEqual(params);
expect(s3.query.input).toEqual(inputSearchParams);
expect(s3.query.error).toBeUndefined();
}
function error(method, input, error) {
const s = query(method, input);
- expect(s.params.children).toEqual([]);
+ expect(s.params.value).toEqual([]);
expect(s.query.input).toEqual(input);
expect(s.query.error).toEqual(error);
}
- assert('{"yql":"abc"}', '?yql=abc', [{ id: '0', input: 'abc', type: 'yql' }]);
+ assert('{"yql":"abc"}', '?yql=abc', [{ id: '0', value: 'abc', type: 'yql' }]);
assert(
'{"hits":12,"ranking":{"location":"us","matchPhase":{"attribute":"[\\"a b\\"]"}},"noCache":true,"offset":""}',
'hits=12&ranking.location=us&noCache=true&ranking.matchPhase.attribute=%5B%22a+b%22%5D&offset',
[
- { id: '0', input: '12', type: 'hits' },
+ { id: '0', value: '12', type: 'hits' },
{
id: '1',
- input: '',
type: 'ranking',
- children: [
- { id: '1.0', input: 'us', type: 'location' },
+ value: [
+ { id: '1.0', value: 'us', type: 'location' },
{
id: '1.1',
- input: '',
type: 'matchPhase',
- children: [{ id: '1.1.0', input: '["a b"]', type: 'attribute' }],
+ value: [{ id: '1.1.0', value: '["a b"]', type: 'attribute' }],
},
],
},
- { id: '2', input: 'true', type: 'noCache' },
- { id: '3', input: '', type: 'offset' },
+ { id: '2', value: 'true', type: 'noCache' },
+ { id: '3', value: '', type: 'offset' },
]
);
assert('{"ranking":{"matchPhase":{}}}', null, [
{
id: '0',
- input: '',
type: 'ranking',
- children: [
- {
- id: '0.0',
- input: '',
- type: 'matchPhase',
- children: [],
- },
- ],
+ value: [{ id: '0.0', type: 'matchPhase', value: [] }],
},
]);
@@ -169,16 +158,16 @@ test('set query', () => {
error('POST', '{"yql":"test}', 'Unexpected end of JSON input');
msg =
- "Property 'ranking' cannot have value, supported children: features,freshness,listFeatures,location,matchPhase,profile,properties,queryCache,sorting";
+ "Property 'ranking' cannot have a value, supported children: features,freshness,listFeatures,location,matchPhase,profile,properties,queryCache,sorting";
error('POST', '{"ranking":123}', msg);
error('GET', 'ranking=123', msg);
error('POST', '{"yql":{}}', "Expected property 'yql' to be String");
});
-function hideTypes({ type, children, ...copy }) {
+function hideTypes({ type, value, ...copy }) {
if (type.name) copy.type = type.name;
- if (children) copy.children = children.map(hideTypes);
+ copy.value = type.children ? value.map(hideTypes) : value;
return copy;
}
diff --git a/client/js/app/src/app/pages/querybuilder/context/query-builder-provider.jsx b/client/js/app/src/app/pages/querybuilder/context/query-builder-provider.jsx
index 1b1faa39e59..74585e1340c 100644
--- a/client/js/app/src/app/pages/querybuilder/context/query-builder-provider.jsx
+++ b/client/js/app/src/app/pages/querybuilder/context/query-builder-provider.jsx
@@ -19,37 +19,43 @@ export const ACTION = Object.freeze({
});
function cloneParams(params) {
- if (!params.children) return { ...params };
- return { ...params, children: params.children.map(cloneParams) };
+ if (!Array.isArray(params.value)) return { ...params };
+ return { ...params, value: params.value.map(cloneParams) };
}
-function inputsToSearchParams(inputs, parent) {
- return inputs.reduce((acc, { input, type: { name }, children }) => {
- const key = parent ? `${parent}.${name}` : name;
- return Object.assign(
- acc,
- children ? inputsToSearchParams(children, key) : { [key]: input }
- );
- }, {});
+function inputsToQuery(method, inputs) {
+ if (method === 'POST') {
+ const inputsToJson = (inputs, parent) =>
+ Object.fromEntries(
+ inputs.map(({ value, type: { name, type, children } }) => [
+ name,
+ children ? inputsToJson(value) : parseInput(value, type),
+ ])
+ );
+ return JSON.stringify(inputsToJson(inputs), null, 4);
+ }
+
+ const inputsToSearchParams = (inputs, parent) =>
+ inputs.reduce((acc, { value, type: { name, children } }) => {
+ const key = parent ? `${parent}.${name}` : name;
+ return Object.assign(
+ acc,
+ children ? inputsToSearchParams(value, key) : { [key]: value }
+ );
+ }, {});
+ return new URLSearchParams(inputsToSearchParams(inputs)).toString();
}
-function searchParamsToInputs(search) {
- const json = [...new URLSearchParams(search).entries()].reduce(
+function queryToInputs(method, query) {
+ if (method === 'POST') return jsonToInputs(JSON.parse(query));
+
+ const json = [...new URLSearchParams(query).entries()].reduce(
(acc, [key, value]) => set(acc, key, value),
{}
);
return jsonToInputs(json);
}
-function inputsToJson(inputs) {
- return Object.fromEntries(
- inputs.map(({ children, input, type: { name, type } }) => [
- name,
- children ? inputsToJson(children) : parseInput(input, type),
- ])
- );
-}
-
function jsonToInputs(json, parent = root) {
return Object.entries(json).map(([key, value], i) => {
const node = {
@@ -65,55 +71,52 @@ function jsonToInputs(json, parent = root) {
if (value != null && typeof value === 'object') {
if (!node.type.children)
throw new Error(`Expected property '${key}' to be ${node.type.type}`);
- node.input = '';
- node.children = jsonToInputs(value, node);
+ node.value = jsonToInputs(value, node);
} else {
if (node.type.children)
throw new Error(
- `Property '${key}' cannot have value, supported children: ${Object.keys(
+ `Property '${key}' cannot have a value, supported children: ${Object.keys(
node.type.children
).sort()}`
);
- node.input = value?.toString();
+ node.value = value?.toString();
}
return node;
});
}
-function parseInput(input, type) {
- if (type === 'Integer' || type === 'Long') return parseInt(input);
- if (type === 'Float') return parseFloat(input);
- if (type === 'Boolean') return input.toLowerCase() === 'true';
- return input;
+function parseInput(value, type) {
+ if (type === 'Integer' || type === 'Long') return parseInt(value);
+ if (type === 'Float') return parseFloat(value);
+ if (type === 'Boolean') return value.toLowerCase() === 'true';
+ return value;
}
function inputAdd(params, { id: parentId, type: typeName }) {
const cloned = cloneParams(params);
const parent = findInput(cloned, parentId);
- const nextId =
- parseInt(last(last(parent.children)?.id?.split('.')) ?? -1) + 1;
+ const nextId = parseInt(last(last(parent.value)?.id?.split('.')) ?? -1) + 1;
const id = parentId ? `${parentId}.${nextId}` : nextId.toString();
const type = parent.type.children[typeName];
- parent.children.push(
- Object.assign({ id, input: '', type }, type.children && { children: [] })
- );
+ parent.value.push({ id, value: type.children ? [] : '', type });
return cloned;
}
-function inputUpdate(params, { id, type, input }) {
+function inputUpdate(params, { id, type, value }) {
const cloned = cloneParams(params);
const node = findInput(cloned, id);
if (type) {
const parent = findInput(cloned, id.substring(0, id.lastIndexOf('.')));
- node.type = parent.type.children[type];
+ const newType = parent.type.children[type];
+ if ((node.type.children != null) !== (newType.children != null))
+ node.value = newType.children ? [] : '';
+ node.type = newType;
}
- if (input) node.input = input;
+ if (value) node.value = value;
- if (node.type.children) node.children = [];
- else delete node.children;
return cloned;
}
@@ -121,9 +124,9 @@ function findInput(params, id, Delete = false) {
if (!id) return params;
let end = -1;
while ((end = id.indexOf('.', end + 1)) > 0)
- params = params.children.find((input) => input.id === id.substring(0, end));
- const index = params.children.findIndex((input) => input.id === id);
- return Delete ? params.children.splice(index, 1)[0] : params.children[index];
+ params = params.value.find((input) => input.id === id.substring(0, end));
+ const index = params.value.findIndex((input) => input.id === id);
+ return Delete ? params.value.splice(index, 1)[0] : params.value[index];
}
export function reducer(state, action) {
@@ -141,13 +144,8 @@ export function reducer(state, action) {
const { request: sr, params: sp, query: sq } = state;
const { request: rr, params: rp, query: rq } = result;
- if ((sp.children !== rp.children && sq === rq) || sr.method !== rr.method)
- result.query = {
- input:
- rr.method === 'POST'
- ? JSON.stringify(inputsToJson(rp.children), null, 4)
- : new URLSearchParams(inputsToSearchParams(rp.children)).toString(),
- };
+ if ((sp.value !== rp.value && sq === rq) || sr.method !== rr.method)
+ result.query = { input: inputsToQuery(rr.method, rp.value) };
const input = result.query.input;
if (sr.url !== rr.url || sq.input !== input || sr.method !== rr.method) {
@@ -171,13 +169,10 @@ function preReducer(state, { action, data }) {
switch (action) {
case ACTION.SET_QUERY: {
try {
- const children =
- state.request.method === 'POST'
- ? jsonToInputs(JSON.parse(data))
- : searchParamsToInputs(data);
+ const value = queryToInputs(state.request.method, data);
return {
...state,
- params: { ...root, children },
+ params: { ...root, value },
query: { input: data },
};
} catch (error) {
diff --git a/client/js/app/src/app/pages/querybuilder/query-filters/query-filters.jsx b/client/js/app/src/app/pages/querybuilder/query-filters/query-filters.jsx
index 82e9fc6c8c1..178270eb71f 100644
--- a/client/js/app/src/app/pages/querybuilder/query-filters/query-filters.jsx
+++ b/client/js/app/src/app/pages/querybuilder/query-filters/query-filters.jsx
@@ -25,7 +25,7 @@ function AddProperty(props) {
);
}
-function Input({ id, input, types, type, children }) {
+function Input({ id, value, types, type }) {
const options = { [type.name]: type, ...types };
return (
<>
@@ -37,24 +37,24 @@ function Input({ id, input, types, type, children }) {
value={type.name}
searchable
/>
- {!children && (
+ {!type.children && (
<TextInput
sx={{ flex: 1 }}
onChange={(event) =>
dispatch(ACTION.INPUT_UPDATE, {
id,
- input: event.currentTarget.value,
+ value: event.currentTarget.value,
})
}
placeholder={type.type}
- value={input ?? ''}
+ value={value}
/>
)}
<ActionIcon onClick={() => dispatch(ACTION.INPUT_REMOVE, id)}>
<Icon name="circle-minus" />
</ActionIcon>
</Box>
- {children && (
+ {type.children && (
<Box
py={8}
sx={(theme) => ({
@@ -66,7 +66,7 @@ function Input({ id, input, types, type, children }) {
paddingLeft: '13px',
})}
>
- <Inputs id={id} type={type.children} inputs={children} />
+ <Inputs id={id} type={type.children} inputs={value} />
</Box>
)}
</>
@@ -81,12 +81,8 @@ function Inputs({ id, type, inputs }) {
const firstRemaining = Object.keys(remainingTypes)[0];
return (
<Container sx={{ rowGap: '5px' }}>
- {inputs.map(({ id, input, type, children }) => (
- <Input
- key={id}
- types={remainingTypes}
- {...{ id, input, type, children }}
- />
+ {inputs.map(({ id, value, type }) => (
+ <Input key={id} types={remainingTypes} {...{ id, value, type }} />
))}
{firstRemaining && (
<>
@@ -116,7 +112,7 @@ function Inputs({ id, type, inputs }) {
}
export function QueryFilters() {
- const { children, type } = useQueryBuilderContext('params');
+ const { value, type } = useQueryBuilderContext('params');
return (
<Stack>
<Group>
@@ -124,7 +120,7 @@ export function QueryFilters() {
</Group>
<Container sx={{ alignContent: 'start' }}>
<Content padding={0}>
- <Inputs type={type.children} inputs={children} />
+ <Inputs type={type.children} inputs={value} />
</Content>
</Container>
</Stack>