mirror of
				https://github.com/open-metadata/OpenMetadata.git
				synced 2025-10-31 10:39:30 +00:00 
			
		
		
		
	add query builder widget ui improvements (#18389)
* add query builder improvements * fix alert flicker (cherry picked from commit c8e2ed0653ac8eaf44f3fbfc788a091aceeefc10)
This commit is contained in:
		
							parent
							
								
									4af077fbd6
								
							
						
					
					
						commit
						69dc6b4c38
					
				| @ -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 = | ||||
|   <P extends Record<string, unknown>>(Component: FC<P>) => | ||||
|   <P extends Record<string, unknown>>( | ||||
|     Component: FC<P>, | ||||
|     providerProps?: Omit<AdvanceSearchProviderProps, 'children'> | ||||
|   ) => | ||||
|   (props: P) => { | ||||
|     return ( | ||||
|       <AdvanceSearchProvider> | ||||
|       <AdvanceSearchProvider {...providerProps}> | ||||
|         <Component {...props} /> | ||||
|       </AdvanceSearchProvider> | ||||
|     ); | ||||
|  | ||||
| @ -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> = ({ | ||||
| }: WidgetProps) => { | ||||
|   const { config, treeInternal, onTreeUpdate, onChangeSearchIndex } = | ||||
|     useAdvanceSearch(); | ||||
|   const [searchResults, setSearchResults] = useState<number>(0); | ||||
|   const [searchResults, setSearchResults] = useState<number | undefined>(); | ||||
|   const [isCountLoading, setIsCountLoading] = useState<boolean>(false); | ||||
|   const entityType = | ||||
|     (props.formContext?.entityType ?? schema?.entityType) || EntityType.ALL; | ||||
|   const searchIndexMapping = searchClassBase.getEntityTypeSearchIndexMapping(); | ||||
| @ -54,6 +56,7 @@ const QueryBuilderWidget: FC<WidgetProps> = ({ | ||||
|   const fetchEntityCount = useCallback( | ||||
|     async (queryFilter: Record<string, unknown>) => { | ||||
|       try { | ||||
|         setIsCountLoading(true); | ||||
|         const res = await searchQuery({ | ||||
|           query: '', | ||||
|           pageNumber: 0, | ||||
| @ -67,6 +70,8 @@ const QueryBuilderWidget: FC<WidgetProps> = ({ | ||||
|         setSearchResults(res.hits.total.value ?? 0); | ||||
|       } catch (_) { | ||||
|         // silent fail
 | ||||
|       } finally { | ||||
|         setIsCountLoading(false); | ||||
|       } | ||||
|     }, | ||||
|     [] | ||||
| @ -85,11 +90,20 @@ const QueryBuilderWidget: FC<WidgetProps> = ({ | ||||
|     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,10 +123,8 @@ const QueryBuilderWidget: FC<WidgetProps> = ({ | ||||
|   }, [searchIndex]); | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     if ( | ||||
|       !isEmpty(value) && | ||||
|       outputType === QueryBuilderOutputType.ELASTICSEARCH | ||||
|     ) { | ||||
|     if (!isEmpty(value)) { | ||||
|       if (outputType === QueryBuilderOutputType.ELASTICSEARCH) { | ||||
|         const tree = QbUtils.checkTree( | ||||
|           QbUtils.loadTree( | ||||
|             getJsonTreeFromQueryFilter(JSON.parse(value || '')) as JsonTree | ||||
| @ -120,6 +132,12 @@ const QueryBuilderWidget: FC<WidgetProps> = ({ | ||||
|           config | ||||
|         ); | ||||
|         onTreeUpdate(tree, config); | ||||
|       } else { | ||||
|         const tree = QbUtils.loadFromJsonLogic(JSON.parse(value || ''), config); | ||||
|         if (tree) { | ||||
|           onTreeUpdate(tree, config); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   }, []); | ||||
| 
 | ||||
| @ -127,6 +145,9 @@ const QueryBuilderWidget: FC<WidgetProps> = ({ | ||||
|     <div | ||||
|       className="query-builder-form-field" | ||||
|       data-testid="query-builder-form-field"> | ||||
|       <Card className="query-builder-card"> | ||||
|         <Row gutter={[8, 8]}> | ||||
|           <Col className="p-t-sm" span={24}> | ||||
|             <Query | ||||
|               {...config} | ||||
|               renderBuilder={(props) => ( | ||||
| @ -137,11 +158,21 @@ const QueryBuilderWidget: FC<WidgetProps> = ({ | ||||
|               value={treeInternal} | ||||
|               onChange={handleChange} | ||||
|             /> | ||||
|       {outputType === QueryBuilderOutputType.ELASTICSEARCH && | ||||
|         !isUndefined(value) && ( | ||||
|           <Col span={24}> | ||||
| 
 | ||||
|             {isCountLoading && ( | ||||
|               <Skeleton | ||||
|                 active | ||||
|                 className="m-t-sm" | ||||
|                 loading={isCountLoading} | ||||
|                 paragraph={false} | ||||
|                 title={{ style: { height: '32px' } }} | ||||
|               /> | ||||
|             )} | ||||
| 
 | ||||
|             {showFilteredResourceCount && ( | ||||
|               <div className="m-t-sm"> | ||||
|                 <Button | ||||
|               className="w-full p-0 text-left" | ||||
|                   className="w-full p-0 text-left h-auto" | ||||
|                   data-testid="view-assets-banner-button" | ||||
|                   disabled={false} | ||||
|                   href={queryURL} | ||||
| @ -167,10 +198,13 @@ const QueryBuilderWidget: FC<WidgetProps> = ({ | ||||
|                     type="info" | ||||
|                   /> | ||||
|                 </Button> | ||||
|           </Col> | ||||
|               </div> | ||||
|             )} | ||||
|           </Col> | ||||
|         </Row> | ||||
|       </Card> | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
| 
 | ||||
| export default withAdvanceSearch(QueryBuilderWidget); | ||||
| export default withAdvanceSearch(QueryBuilderWidget, { isExplorePage: false }); | ||||
|  | ||||
| @ -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; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -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); | ||||
|  | ||||
| @ -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, | ||||
|       }, | ||||
|     }; | ||||
| 
 | ||||
|  | ||||
| @ -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 ( | ||||
|       <Button | ||||
|         className="action action--DELETE" | ||||
|         data-testid="delete-condition-button" | ||||
|         icon={<CloseOutlined />} | ||||
|         onClick={props?.onClick} | ||||
|       /> | ||||
|     ); | ||||
|   } else if (type === 'addRule') { | ||||
|     return ( | ||||
|       <Button | ||||
|         className="action action--ADD-RULE" | ||||
|         data-testid="add-condition-button" | ||||
|         type="primary" | ||||
|         onClick={props?.onClick}> | ||||
|         {t('label.add-entity', { | ||||
|           entity: t('label.condition'), | ||||
|         })} | ||||
|       </Button> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   return <></>; | ||||
| }; | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Karan Hotchandani
						Karan Hotchandani