diff options
author | Valerij Fredriksen <valerijf@yahooinc.com> | 2022-08-14 11:45:22 +0200 |
---|---|---|
committer | Valerij Fredriksen <valerijf@yahooinc.com> | 2022-08-14 12:10:08 +0200 |
commit | cba5caee77fd1b574888dfde8c86e0ff43869626 (patch) | |
tree | 1a61a8750604882e0700b83a990398b3da3964b7 /client/js | |
parent | 647d74bf81a2bd85f993df685717b749234b9340 (diff) |
Collapse input and children
Diffstat (limited to 'client/js')
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> |