persist es query value (#18360)

(cherry picked from commit 3c244da0516efcff150ddf5abfb8f556f3b8b642)
This commit is contained in:
Karan Hotchandani 2024-10-23 12:49:07 +05:30 committed by karanh37
parent 540ecefdd0
commit 4af077fbd6
4 changed files with 445 additions and 0 deletions

View File

@ -21,6 +21,7 @@ import {
Builder,
Config,
ImmutableTree,
JsonTree,
Query,
Utils as QbUtils,
} from 'react-awesome-query-builder';
@ -28,6 +29,7 @@ import { getExplorePath } from '../../../../../../constants/constants';
import { EntityType } from '../../../../../../enums/entity.enum';
import { SearchIndex } from '../../../../../../enums/search.enum';
import { searchQuery } from '../../../../../../rest/searchAPI';
import { getJsonTreeFromQueryFilter } from '../../../../../../utils/QueryBuilderUtils';
import searchClassBase from '../../../../../../utils/SearchClassBase';
import { withAdvanceSearch } from '../../../../../AppRouter/withAdvanceSearch';
import { useAdvanceSearch } from '../../../../../Explore/AdvanceSearchProvider/AdvanceSearchProvider.component';
@ -106,6 +108,21 @@ const QueryBuilderWidget: FC<WidgetProps> = ({
onChangeSearchIndex(searchIndex);
}, [searchIndex]);
useEffect(() => {
if (
!isEmpty(value) &&
outputType === QueryBuilderOutputType.ELASTICSEARCH
) {
const tree = QbUtils.checkTree(
QbUtils.loadTree(
getJsonTreeFromQueryFilter(JSON.parse(value || '')) as JsonTree
),
config
);
onTreeUpdate(tree, config);
}
}, []);
return (
<div
className="query-builder-form-field"

View File

@ -38,6 +38,12 @@ export interface EsExistsQuery {
field: string;
}
export interface EsWildCard {
wildcard: {
[key: string]: { value: string };
};
}
export interface EsBoolQuery {
filter?: QueryFieldInterface | QueryFieldInterface[];
must?: QueryFieldInterface | QueryFieldInterface[];
@ -56,6 +62,12 @@ export interface QueryFilterInterface {
query: QueryFieldInterface;
}
export interface EsTerm {
term: {
[key: string]: string | boolean;
};
}
export enum ExploreSidebarTab {
ASSETS = 'assets',
TREE = 'tree',

View File

@ -0,0 +1,103 @@
/*
* Copyright 2024 Collate.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { QueryFilterInterface } from '../pages/ExplorePage/ExplorePage.interface';
import { getJsonTreeFromQueryFilter } from './QueryBuilderUtils';
jest.mock('./StringsUtils', () => ({
generateUUID: jest.fn(),
}));
describe('getJsonTreeFromQueryFilter', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('should return a valid JSON tree structure for a given query filter', () => {
const mockUUIDs = ['uuid1', 'uuid2', 'uuid3', 'uuid4'];
(
jest.requireMock('./StringsUtils').generateUUID as jest.Mock
).mockImplementation(() => mockUUIDs.shift());
const queryFilter: QueryFilterInterface = {
query: {
bool: {
must: [
{
bool: {
must: [
{
term: {
field1: 'value1',
},
},
],
},
},
],
},
},
};
const result = getJsonTreeFromQueryFilter(queryFilter);
expect(result).toEqual({
type: 'group',
properties: { conjunction: 'AND', not: false },
children1: {
uuid2: {
type: 'group',
properties: { conjunction: 'AND', not: false },
children1: {
uuid3: {
type: 'rule',
properties: {
field: 'field1',
operator: 'select_equals',
value: ['value1'],
valueSrc: ['value'],
operatorOptions: null,
valueType: ['select'],
asyncListValues: [
{
key: 'value1',
value: 'value1',
children: 'value1',
},
],
},
id: 'uuid3',
path: ['uuid1', 'uuid2', 'uuid3'],
},
},
id: 'uuid2',
path: ['uuid1', 'uuid2'],
},
},
id: 'uuid1',
path: ['uuid1'],
});
});
it('should return an empty object if an error occurs', () => {
const queryFilter: QueryFilterInterface = {
query: {
bool: {
must: [],
},
},
};
const result = getJsonTreeFromQueryFilter(queryFilter);
expect(result).toEqual({});
});
});

View File

@ -0,0 +1,313 @@
/*
* Copyright 2024 Collate.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { isUndefined } from 'lodash';
import {
EsBoolQuery,
EsExistsQuery,
EsTerm,
EsWildCard,
QueryFieldInterface,
QueryFilterInterface,
} from '../pages/ExplorePage/ExplorePage.interface';
import { generateUUID } from './StringsUtils';
export const getSelectEqualsNotEqualsProperties = (
parentPath: Array<string>,
field: string,
value: string,
operator: string
) => {
const id = generateUUID();
return {
[id]: {
type: 'rule',
properties: {
field: field,
operator,
value: [value],
valueSrc: ['value'],
operatorOptions: null,
valueType: ['select'],
asyncListValues: [
{
key: value,
value,
children: value,
},
],
},
id,
path: [...parentPath, id],
},
};
};
export const getSelectAnyInProperties = (
parentPath: Array<string>,
termObjects: Array<EsTerm>
) => {
const values = termObjects.map(
(termObject) => Object.values(termObject.term)[0]
);
const id = generateUUID();
return {
[id]: {
type: 'rule',
properties: {
field: Object.keys(termObjects[0].term)[0],
operator: 'select_any_in',
value: [values],
valueSrc: ['value'],
operatorOptions: null,
valueType: ['multiselect'],
asyncListValues: values.map((value) => ({
key: value,
value,
children: value,
})),
},
id,
path: [...parentPath, id],
},
};
};
export const getSelectNotAnyInProperties = (
parentPath: Array<string>,
termObjects: QueryFieldInterface[]
) => {
const values = termObjects.map(
(termObject) =>
Object.values((termObject?.bool?.must_not as EsTerm)?.term)[0]
);
const id = generateUUID();
return {
[id]: {
type: 'rule',
properties: {
field: Object.keys((termObjects[0].bool?.must_not as EsTerm).term)[0],
operator: 'select_not_any_in',
value: [values],
valueSrc: ['value'],
operatorOptions: null,
valueType: ['multiselect'],
asyncListValues: values.map((value) => ({
key: value,
value,
children: value,
})),
},
id,
path: [...parentPath, id],
},
};
};
export const getCommonFieldProperties = (
parentPath: Array<string>,
field: string,
operator: string,
value?: string
) => {
const id = generateUUID();
return {
[id]: {
type: 'rule',
properties: {
field,
operator,
value: isUndefined(value) ? [] : [value.replaceAll(/(^\*)|(\*$)/g, '')],
valueSrc: isUndefined(value) ? [] : ['value'],
operatorOptions: null,
valueType: isUndefined(value) ? [] : ['text'],
},
id,
path: [...parentPath, id],
},
};
};
export const getEqualFieldProperties = (
parentPath: Array<string>,
value: boolean
) => {
const id = generateUUID();
return {
[id]: {
type: 'rule',
properties: {
field: 'deleted',
operator: 'equal',
value: [value],
valueSrc: ['value'],
operatorOptions: null,
valueType: ['boolean'],
},
id,
path: [...parentPath, id],
},
};
};
export const getJsonTreePropertyFromQueryFilter = (
parentPath: Array<string>,
queryFilter: QueryFieldInterface[]
) => {
const convertedObj = queryFilter.reduce(
(acc, curr: QueryFieldInterface): Record<string, any> => {
if (!isUndefined(curr.term?.deleted)) {
return {
...acc,
...getEqualFieldProperties(parentPath, curr.term?.deleted as boolean),
};
} else if (!isUndefined(curr.term)) {
return {
...acc,
...getSelectEqualsNotEqualsProperties(
parentPath,
Object.keys(curr.term)[0],
Object.values(curr.term)[0] as string,
'select_equals'
),
};
} else if (
!isUndefined((curr.bool?.must_not as QueryFieldInterface).term)
) {
return {
...acc,
...getSelectEqualsNotEqualsProperties(
parentPath,
Object.keys((curr.bool?.must_not as EsTerm)?.term)[0],
Object.values((curr.bool?.must_not as EsTerm)?.term)[0] as string,
'select_not_equals'
),
};
} else if (
!isUndefined(
((curr.bool?.should as QueryFieldInterface[])?.[0] as EsTerm)?.term
)
) {
return {
...acc,
...getSelectAnyInProperties(
parentPath,
curr?.bool?.should as EsTerm[]
),
};
} else if (
!isUndefined(
(
(curr.bool?.should as QueryFieldInterface[])?.[0]?.bool
?.must_not as EsTerm
)?.term
)
) {
return {
...acc,
...getSelectNotAnyInProperties(
parentPath,
curr?.bool?.should as QueryFieldInterface[]
),
};
} else if (
!isUndefined(
(curr.bool?.must_not as QueryFieldInterface)?.exists?.field
)
) {
return {
...acc,
...getCommonFieldProperties(
parentPath,
(curr.bool?.must_not as QueryFieldInterface)?.exists
?.field as string,
'is_null'
),
};
} else if (!isUndefined(curr.exists?.field)) {
return {
...acc,
...getCommonFieldProperties(
parentPath,
(curr.exists as EsExistsQuery).field,
'is_not_null'
),
};
} else if (!isUndefined((curr as EsWildCard).wildcard)) {
return {
...acc,
...getCommonFieldProperties(
parentPath,
Object.keys((curr as EsWildCard).wildcard)[0],
'like',
Object.values((curr as EsWildCard).wildcard)[0]?.value
),
};
} else if (!isUndefined((curr.bool?.must_not as EsWildCard)?.wildcard)) {
return {
...acc,
...getCommonFieldProperties(
parentPath,
Object.keys(
(curr.bool?.must_not as EsWildCard)?.wildcard
)[0] as string,
'not_like',
Object.values((curr.bool?.must_not as EsWildCard)?.wildcard)[0]
?.value
),
};
}
return acc;
},
{} as Record<string, any>
);
return convertedObj;
};
export const getJsonTreeFromQueryFilter = (
queryFilter: QueryFilterInterface
) => {
try {
const id1 = generateUUID();
const id2 = generateUUID();
const mustFilters = queryFilter?.query?.bool?.must as QueryFieldInterface[];
return {
type: 'group',
properties: { conjunction: 'AND', not: false },
children1: {
[id2]: {
type: 'group',
properties: { conjunction: 'AND', not: false },
children1: getJsonTreePropertyFromQueryFilter(
[id1, id2],
(mustFilters?.[0]?.bool as EsBoolQuery)
.must as QueryFieldInterface[]
),
id: id2,
path: [id1, id2],
},
},
id: id1,
path: [id1],
};
} catch {
return {};
}
};