mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-11-01 19:18:05 +00:00
persist es query value (#18360)
(cherry picked from commit 3c244da0516efcff150ddf5abfb8f556f3b8b642)
This commit is contained in:
parent
540ecefdd0
commit
4af077fbd6
@ -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"
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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({});
|
||||
});
|
||||
});
|
||||
@ -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 {};
|
||||
}
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user