From 69dc6b4c38de1764fce178f33296dc6665ad6345 Mon Sep 17 00:00:00 2001 From: Karan Hotchandani <33024356+karanh37@users.noreply.github.com> Date: Thu, 24 Oct 2024 15:47:11 +0530 Subject: [PATCH] add query builder widget ui improvements (#18389) * add query builder improvements * fix alert flicker (cherry picked from commit c8e2ed0653ac8eaf44f3fbfc788a091aceeefc10) --- .../AppRouter/withAdvanceSearch.tsx | 8 +- .../QueryBuilderWidget/QueryBuilderWidget.tsx | 142 +++++++++++------- .../query-builder-widget.less | 137 +++++++++++++++++ .../resources/ui/src/styles/variables.less | 7 +- .../ui/src/utils/AdvancedSearchClassBase.ts | 5 +- ...yBuilderUtils.ts => QueryBuilderUtils.tsx} | 40 ++++- 6 files changed, 276 insertions(+), 63 deletions(-) rename openmetadata-ui/src/main/resources/ui/src/utils/{QueryBuilderUtils.ts => QueryBuilderUtils.tsx} (89%) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/withAdvanceSearch.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/withAdvanceSearch.tsx index 19b05f55ec2..db88f2c9330 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/withAdvanceSearch.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/withAdvanceSearch.tsx @@ -12,12 +12,16 @@ */ import React, { FC } from 'react'; import { AdvanceSearchProvider } from '../../components/Explore/AdvanceSearchProvider/AdvanceSearchProvider.component'; +import { AdvanceSearchProviderProps } from '../Explore/AdvanceSearchProvider/AdvanceSearchProvider.interface'; export const withAdvanceSearch = -

>(Component: FC

) => +

>( + Component: FC

, + providerProps?: Omit + ) => (props: P) => { return ( - + ); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/QueryBuilderWidget/QueryBuilderWidget.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/QueryBuilderWidget/QueryBuilderWidget.tsx index 42b703adff2..21d181da82b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/QueryBuilderWidget/QueryBuilderWidget.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/QueryBuilderWidget/QueryBuilderWidget.tsx @@ -12,7 +12,7 @@ */ import { InfoCircleOutlined } from '@ant-design/icons'; import { WidgetProps } from '@rjsf/utils'; -import { Alert, Button, Col, Typography } from 'antd'; +import { Alert, Button, Card, Col, Row, Skeleton, Typography } from 'antd'; import { t } from 'i18next'; import { debounce, isEmpty, isUndefined } from 'lodash'; import Qs from 'qs'; @@ -29,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 { elasticSearchFormat } from '../../../../../../utils/QueryBuilderElasticsearchFormatUtils'; import { getJsonTreeFromQueryFilter } from '../../../../../../utils/QueryBuilderUtils'; import searchClassBase from '../../../../../../utils/SearchClassBase'; import { withAdvanceSearch } from '../../../../../AppRouter/withAdvanceSearch'; @@ -44,7 +45,8 @@ const QueryBuilderWidget: FC = ({ }: WidgetProps) => { const { config, treeInternal, onTreeUpdate, onChangeSearchIndex } = useAdvanceSearch(); - const [searchResults, setSearchResults] = useState(0); + const [searchResults, setSearchResults] = useState(); + const [isCountLoading, setIsCountLoading] = useState(false); const entityType = (props.formContext?.entityType ?? schema?.entityType) || EntityType.ALL; const searchIndexMapping = searchClassBase.getEntityTypeSearchIndexMapping(); @@ -54,6 +56,7 @@ const QueryBuilderWidget: FC = ({ const fetchEntityCount = useCallback( async (queryFilter: Record) => { try { + setIsCountLoading(true); const res = await searchQuery({ query: '', pageNumber: 0, @@ -67,6 +70,8 @@ const QueryBuilderWidget: FC = ({ setSearchResults(res.hits.total.value ?? 0); } catch (_) { // silent fail + } finally { + setIsCountLoading(false); } }, [] @@ -85,11 +90,20 @@ const QueryBuilderWidget: FC = ({ return `${getExplorePath({})}${queryFilterString}`; }, [treeInternal]); + const showFilteredResourceCount = useMemo( + () => + outputType === QueryBuilderOutputType.ELASTICSEARCH && + !isUndefined(value) && + searchResults !== undefined && + !isCountLoading, + [outputType, value, isCountLoading] + ); + const handleChange = (nTree: ImmutableTree, nConfig: Config) => { onTreeUpdate(nTree, nConfig); if (outputType === QueryBuilderOutputType.ELASTICSEARCH) { - const data = QbUtils.elasticSearchFormat(nTree, config) ?? {}; + const data = elasticSearchFormat(nTree, config) ?? {}; const qFilter = { query: data, }; @@ -109,17 +123,21 @@ const QueryBuilderWidget: FC = ({ }, [searchIndex]); useEffect(() => { - if ( - !isEmpty(value) && - outputType === QueryBuilderOutputType.ELASTICSEARCH - ) { - const tree = QbUtils.checkTree( - QbUtils.loadTree( - getJsonTreeFromQueryFilter(JSON.parse(value || '')) as JsonTree - ), - config - ); - onTreeUpdate(tree, config); + if (!isEmpty(value)) { + if (outputType === QueryBuilderOutputType.ELASTICSEARCH) { + const tree = QbUtils.checkTree( + QbUtils.loadTree( + getJsonTreeFromQueryFilter(JSON.parse(value || '')) as JsonTree + ), + config + ); + onTreeUpdate(tree, config); + } else { + const tree = QbUtils.loadFromJsonLogic(JSON.parse(value || ''), config); + if (tree) { + onTreeUpdate(tree, config); + } + } } }, []); @@ -127,50 +145,66 @@ const QueryBuilderWidget: FC = ({

- ( -
- -
- )} - value={treeInternal} - onChange={handleChange} - /> - {outputType === QueryBuilderOutputType.ELASTICSEARCH && - !isUndefined(value) && ( - - + )} + + {showFilteredResourceCount && ( +
+ +
+ )} - )} + +
); }; -export default withAdvanceSearch(QueryBuilderWidget); +export default withAdvanceSearch(QueryBuilderWidget, { isExplorePage: false }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/QueryBuilderWidget/query-builder-widget.less b/openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/QueryBuilderWidget/query-builder-widget.less index 43d768729e2..f7b038a2c95 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/QueryBuilderWidget/query-builder-widget.less +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/QueryBuilderWidget/query-builder-widget.less @@ -10,6 +10,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +@import (reference) url('../../../../../../styles/variables.less'); + +.query-builder-card { + background-color: @grey-6; + + .ant-alert-info { + background-color: @blue-8; + border-color: @blue-7; + + .ant-alert-icon { + color: @blue-7; + } + } +} + .query-builder-form-field { .hide--line.one--child { margin-top: 0; @@ -39,4 +55,125 @@ margin-bottom: 6px; } } + + .query-builder-container { + .group-or-rule-container.rule-container { + padding: 0px; + + .rule.group-or-rule { + .rule--header { + .ant-btn-group { + margin: 0px; + align-self: flex-start; + } + } + } + } + + .group-or-rule-container.group-container { + padding: 0px; + + .group.rule_group { + .group--field { + margin: 0px; + flex: 0 1 25%; + + .ant-select { + min-width: 100% !important; // override the inline min-width style of the select provided by antd + } + } + } + + & > .group.group-or-rule { + display: flex; + flex-direction: column; + + & > .group--header { + order: 9999; + + .group--actions.group--actions--tr { + justify-content: flex-start; + margin: 0px; + } + + .action.action--ADD-GROUP { + display: none; + } + + .rule-container .ant-btn-group { + visibility: visible; + } + + .action.action--ADD-RULE { + position: static; + margin-top: 8px; + } + } + + .rule--body--wrapper { + .rule--body { + margin: 0px; + display: flex; + gap: 16px; + + .group--field, + .rule--field, + .rule--operator, + .rule--value, + .widget--widget { + margin: 0px; + flex: 1; + + .ant-col { + padding: 0px !important; // remove padding from ant-col inline styling by antd + } + } + + .rule--operator, + .rule--value .rule--widget { + width: 100%; + + .ant-select { + min-width: 100% !important; // override the inline min-width style of the select provided by antd + } + } + } + } + } + + & > .group.group-or-rule.rule_group { + flex-direction: row; + align-items: center; + } + } + + .group--children { + padding: 0px; + margin: 0px; + + .group-or-rule-container.group-container, + .group-or-rule-container.rule-container { + .group.group-or-rule, + .rule.group-or-rule { + background: inherit; + padding: 0px; + border: none; + + .group--header { + .group--conjunctions { + display: none; + } + } + + &:not(:first-child) { + padding: 16px 8px; + } + } + } + + .rule-container .ant-btn-group { + visibility: visible; + } + } + } } diff --git a/openmetadata-ui/src/main/resources/ui/src/styles/variables.less b/openmetadata-ui/src/main/resources/ui/src/styles/variables.less index 7dca1705868..5a086f7893c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/styles/variables.less +++ b/openmetadata-ui/src/main/resources/ui/src/styles/variables.less @@ -52,6 +52,8 @@ @blue-4: #f1f9ff; @blue-5: #f2f6fc; @blue-6: #eff5ff; +@blue-7: #3062d4; +@blue-8: #f5f8ff; @partial-success-1: #06a4a4; @partial-success-2: #bdeeee; @black: #000000; @@ -112,9 +114,8 @@ // 172px - navbar height @entity-details-tab-height: calc(100vh - 172px - @om-navbar-height); -@users-page-tabs-height: calc( - 100vh - @om-navbar-height - 58px -); /* navbar+tab_height+padding = 64+46+12 */ +@users-page-tabs-height: calc(100vh - @om-navbar-height - 58px); +/* navbar+tab_height+padding = 64+46+12 */ // 142px - navbar height @glossary-page-height: calc(100vh - 142px - @om-navbar-height); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchClassBase.ts b/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchClassBase.ts index e5d2d2ee2ff..d71aad8dae6 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchClassBase.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchClassBase.ts @@ -27,6 +27,7 @@ import { SearchIndex } from '../enums/search.enum'; import { getAggregateFieldOptions } from '../rest/miscAPI'; import { renderAdvanceSearchButtons } from './AdvancedSearchUtils'; import { getCombinedQueryFilterObject } from './ExplorePage/ExplorePageUtils'; +import { renderQueryBuilderFilterButtons } from './QueryBuilderUtils'; class AdvancedSearchClassBase { baseConfig = AntdConfig as BasicConfig; @@ -408,7 +409,9 @@ class AdvancedSearchClassBase { operatorLabel: t('label.condition') + ':', showNot: false, valueLabel: t('label.criteria') + ':', - renderButton: renderAdvanceSearchButtons, + renderButton: isExplorePage + ? renderAdvanceSearchButtons + : renderQueryBuilderFilterButtons, }, }; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderUtils.tsx similarity index 89% rename from openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderUtils.ts rename to openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderUtils.tsx index 1f53db69ac0..75149e12f5d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderUtils.tsx @@ -10,7 +10,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { CloseOutlined } from '@ant-design/icons'; +import { Button } from 'antd'; +import { t } from 'i18next'; import { isUndefined } from 'lodash'; +import React from 'react'; +import { RenderSettings } from 'react-awesome-query-builder'; import { EsBoolQuery, EsExistsQuery, @@ -262,9 +267,7 @@ export const getJsonTreePropertyFromQueryFilter = ( ...acc, ...getCommonFieldProperties( parentPath, - Object.keys( - (curr.bool?.must_not as EsWildCard)?.wildcard - )[0] as string, + Object.keys((curr.bool?.must_not as EsWildCard)?.wildcard)[0], 'not_like', Object.values((curr.bool?.must_not as EsWildCard)?.wildcard)[0] ?.value @@ -311,3 +314,34 @@ export const getJsonTreeFromQueryFilter = ( return {}; } }; + +export const renderQueryBuilderFilterButtons: RenderSettings['renderButton'] = ( + props +) => { + const type = props?.type; + + if (type === 'delRule') { + return ( + + ); + } + + return <>; +};