mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-30 20:06:19 +00:00
Minor search issues (#14861)
* fix: minor search issues * show dbt icon in lineage
This commit is contained in:
parent
6a1c13687e
commit
00838d6c73
@ -237,6 +237,7 @@ const CustomControls: FC<ControlProps> = ({
|
||||
</Button>
|
||||
</Dropdown>
|
||||
<ExploreQuickFilters
|
||||
independent
|
||||
aggregations={{}}
|
||||
fields={selectedQuickFilters}
|
||||
index={SearchIndex.ALL}
|
||||
|
@ -13,6 +13,7 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { ReactFlowProvider } from 'reactflow';
|
||||
import { ModelType } from '../../../generated/entity/data/table';
|
||||
import CustomNodeV1Component from './CustomNodeV1.component';
|
||||
|
||||
const mockNodeDataProps = {
|
||||
@ -39,6 +40,33 @@ const mockNodeDataProps = {
|
||||
zIndex: 0,
|
||||
};
|
||||
|
||||
const mockNodeDataProps2 = {
|
||||
id: 'node1',
|
||||
type: 'table',
|
||||
data: {
|
||||
node: {
|
||||
fullyQualifiedName: 'dim_customer',
|
||||
type: 'table',
|
||||
entityType: 'table',
|
||||
id: 'khjahjfja',
|
||||
columns: [
|
||||
{ fullyQualifiedName: 'col1', name: 'col1' },
|
||||
{ fullyQualifiedName: 'col2', name: 'col2' },
|
||||
{ fullyQualifiedName: 'col3', name: 'col3' },
|
||||
],
|
||||
dataModel: {
|
||||
modelType: ModelType.Dbt,
|
||||
},
|
||||
},
|
||||
},
|
||||
selected: false,
|
||||
isConnectable: false,
|
||||
xPos: 0,
|
||||
yPos: 0,
|
||||
dragging: true,
|
||||
zIndex: 0,
|
||||
};
|
||||
|
||||
const onMockColumnClick = jest.fn();
|
||||
|
||||
jest.mock('../../LineageProvider/LineageProvider', () => ({
|
||||
@ -74,4 +102,16 @@ describe('CustomNodeV1', () => {
|
||||
expect(screen.getByTestId('lineage-node-dim_customer')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('expand-cols-btn')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders node with dbt icon correctly', () => {
|
||||
render(
|
||||
<ReactFlowProvider>
|
||||
<CustomNodeV1Component {...mockNodeDataProps2} />
|
||||
</ReactFlowProvider>
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('lineage-node-dim_customer')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('expand-cols-btn')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('dbt-icon')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
@ -12,33 +12,27 @@
|
||||
*/
|
||||
|
||||
import { Col, Row, Space, Typography } from 'antd';
|
||||
import { get } from 'lodash';
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { EntityLineageNodeType } from '../../../enums/entity.enum';
|
||||
import { EntityReference } from '../../../generated/entity/type';
|
||||
import { ReactComponent as IconDBTModel } from '../../../assets/svg/dbt-model.svg';
|
||||
import { EntityType } from '../../../enums/entity.enum';
|
||||
import { ModelType, Table } from '../../../generated/entity/data/table';
|
||||
import { getBreadcrumbsFromFqn } from '../../../utils/EntityUtils';
|
||||
import { getServiceIcon } from '../../../utils/TableUtils';
|
||||
import { SourceType } from '../../SearchedData/SearchedData.interface';
|
||||
import './lineage-node-label.less';
|
||||
|
||||
interface LineageNodeLabelProps {
|
||||
node: EntityReference;
|
||||
node: SourceType;
|
||||
}
|
||||
|
||||
const EntityLabel = ({ node }: Pick<LineageNodeLabelProps, 'node'>) => {
|
||||
const { t } = useTranslation();
|
||||
if (node.type === EntityLineageNodeType.LOAD_MORE) {
|
||||
const showDbtIcon = useMemo(() => {
|
||||
return (
|
||||
<div>
|
||||
<span>{t('label.load-more')}</span>
|
||||
<span className="load-more-node-sizes p-x-xs">{`(${get(
|
||||
node,
|
||||
'pagination_data.childrenLength'
|
||||
)})`}</span>
|
||||
</div>
|
||||
(node as SourceType).entityType === EntityType.TABLE &&
|
||||
(node as Table)?.dataModel?.modelType === ModelType.Dbt
|
||||
);
|
||||
}
|
||||
}, [node]);
|
||||
|
||||
return (
|
||||
<Row className="items-center" wrap={false}>
|
||||
@ -48,29 +42,34 @@ const EntityLabel = ({ node }: Pick<LineageNodeLabelProps, 'node'>) => {
|
||||
</div>
|
||||
<Space align="start" direction="vertical" size={0}>
|
||||
<Typography.Text
|
||||
className="m-b-0 d-block text-left text-grey-muted w-56"
|
||||
className="m-b-0 d-block text-left text-grey-muted w-54"
|
||||
data-testid="entity-header-name"
|
||||
ellipsis={{ tooltip: true }}>
|
||||
{node.name}
|
||||
</Typography.Text>
|
||||
<Typography.Text
|
||||
className="m-b-0 d-block text-left entity-header-display-name text-md font-medium w-56"
|
||||
className="m-b-0 d-block text-left entity-header-display-name text-md font-medium w-54"
|
||||
data-testid="entity-header-display-name"
|
||||
ellipsis={{ tooltip: true }}>
|
||||
{node.displayName || node.name}
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
{showDbtIcon && (
|
||||
<div className="m-r-xs" data-testid="dbt-icon">
|
||||
<IconDBTModel />
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
const LineageNodeLabelV1 = ({ node }: { node: EntityReference }) => {
|
||||
const LineageNodeLabelV1 = ({ node }: Pick<LineageNodeLabelProps, 'node'>) => {
|
||||
const { t } = useTranslation();
|
||||
const breadcrumbs = getBreadcrumbsFromFqn(node.fullyQualifiedName ?? '');
|
||||
|
||||
return (
|
||||
<div className="w-72">
|
||||
<div className="w-76">
|
||||
<div className="m-0 p-x-md p-y-xs">
|
||||
<div className="d-flex gap-2 items-center m-b-xs">
|
||||
<Space
|
||||
|
@ -115,7 +115,7 @@ const NodeSuggestions: FC<EntitySuggestionProps> = ({
|
||||
autoFocus
|
||||
open
|
||||
showSearch
|
||||
className="w-72 lineage-node-searchbox"
|
||||
className="w-76 lineage-node-searchbox"
|
||||
data-testid="node-search-box"
|
||||
options={(data || []).map((entity) => ({
|
||||
value: entity.fullyQualifiedName,
|
||||
|
@ -23,6 +23,7 @@ export interface ExploreQuickFiltersProps {
|
||||
onAdvanceSearch?: () => void;
|
||||
showDeleted?: boolean;
|
||||
onChangeShowDeleted?: (showDeleted: boolean) => void;
|
||||
independent?: boolean; // flag to indicate if the filters are independent of aggregations
|
||||
}
|
||||
|
||||
export interface FilterFieldsMenuItem {
|
||||
|
@ -41,6 +41,7 @@ const ExploreQuickFilters: FC<ExploreQuickFiltersProps> = ({
|
||||
fields,
|
||||
index,
|
||||
aggregations,
|
||||
independent = false,
|
||||
onFieldValueSelect,
|
||||
}) => {
|
||||
const location = useLocation();
|
||||
@ -151,7 +152,7 @@ const ExploreQuickFilters: FC<ExploreQuickFiltersProps> = ({
|
||||
|
||||
return;
|
||||
}
|
||||
if (aggregations?.[key] && key !== TIER_FQN_KEY) {
|
||||
if (key !== TIER_FQN_KEY) {
|
||||
const res = await getAggregateFieldOptions(
|
||||
index,
|
||||
key,
|
||||
@ -197,6 +198,7 @@ const ExploreQuickFilters: FC<ExploreQuickFiltersProps> = ({
|
||||
<SearchDropdown
|
||||
highlight
|
||||
fixedOrderOptions={field.key === TIER_FQN_KEY}
|
||||
independent={independent}
|
||||
index={index as ExploreSearchIndex}
|
||||
isSuggestionsLoading={isOptionsLoading}
|
||||
key={field.key}
|
||||
|
@ -26,6 +26,7 @@ export interface SearchDropdownProps {
|
||||
onChange: (values: SearchDropdownOption[], searchKey: string) => void;
|
||||
onGetInitialOptions?: (searchKey: string) => void;
|
||||
onSearch: (searchText: string, searchKey: string) => void;
|
||||
independent?: boolean; // flag to indicate if the filters are independent of aggregations
|
||||
}
|
||||
|
||||
export interface SearchDropdownOption {
|
||||
|
@ -63,6 +63,7 @@ const SearchDropdown: FC<SearchDropdownProps> = ({
|
||||
onGetInitialOptions,
|
||||
onSearch,
|
||||
index,
|
||||
independent = false,
|
||||
}) => {
|
||||
const tabsInfo = searchClassBase.getTabsInfo();
|
||||
const { t } = useTranslation();
|
||||
@ -75,7 +76,9 @@ const SearchDropdown: FC<SearchDropdownProps> = ({
|
||||
// derive menu props from options and selected keys
|
||||
const menuOptions: MenuProps['items'] = useMemo(() => {
|
||||
// Filtering out selected options
|
||||
const selectedOptionsObj = options.filter((option) =>
|
||||
const selectedOptionsObj = independent
|
||||
? selectedOptions
|
||||
: options.filter((option) =>
|
||||
selectedOptions.find((selectedOpt) => option.key === selectedOpt.key)
|
||||
);
|
||||
|
||||
@ -116,7 +119,7 @@ const SearchDropdown: FC<SearchDropdownProps> = ({
|
||||
|
||||
return [...selectedOptionKeys, ...otherOptions];
|
||||
}
|
||||
}, [options, selectedOptions, fixedOrderOptions]);
|
||||
}, [options, selectedOptions, fixedOrderOptions, independent]);
|
||||
|
||||
// handle menu item click
|
||||
const handleMenuItemClick: MenuItemProps['onClick'] = (info) => {
|
||||
|
@ -53,7 +53,6 @@ import { findActiveSearchIndex } from '../../utils/Explore.utils';
|
||||
import { getCombinedQueryFilterObject } from '../../utils/ExplorePage/ExplorePageUtils';
|
||||
import searchClassBase from '../../utils/SearchClassBase';
|
||||
import { escapeESReservedCharacters } from '../../utils/StringsUtils';
|
||||
import { getEntityIcon } from '../../utils/TableUtils';
|
||||
import { showErrorToast } from '../../utils/ToastUtils';
|
||||
import {
|
||||
QueryFieldInterface,
|
||||
@ -229,7 +228,10 @@ const ExplorePageV1: FunctionComponent = () => {
|
||||
|
||||
const tabItems = useMemo(() => {
|
||||
const items = Object.entries(tabsInfo).map(
|
||||
([tabSearchIndex, tabDetail]) => ({
|
||||
([tabSearchIndex, tabDetail]) => {
|
||||
const Icon = tabDetail.icon;
|
||||
|
||||
return {
|
||||
key: tabSearchIndex,
|
||||
label: (
|
||||
<div
|
||||
@ -237,7 +239,7 @@ const ExplorePageV1: FunctionComponent = () => {
|
||||
data-testid={`${lowerCase(tabDetail.label)}-tab`}>
|
||||
<div className="d-flex items-center">
|
||||
<span className="explore-icon d-flex m-r-xs">
|
||||
{getEntityIcon(tabSearchIndex)}
|
||||
<Icon />
|
||||
</span>
|
||||
<Typography.Text
|
||||
className={
|
||||
@ -260,7 +262,8 @@ const ExplorePageV1: FunctionComponent = () => {
|
||||
count: searchHitCounts
|
||||
? searchHitCounts[tabSearchIndex as ExploreSearchIndex]
|
||||
: 0,
|
||||
})
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
return searchQueryParam
|
||||
|
@ -59,6 +59,9 @@
|
||||
.w-52 {
|
||||
width: 13rem; /* 208px */
|
||||
}
|
||||
.w-54 {
|
||||
width: 13.5rem;
|
||||
}
|
||||
.w-56 {
|
||||
width: 14rem;
|
||||
}
|
||||
@ -71,6 +74,9 @@
|
||||
.w-72 {
|
||||
width: 288px;
|
||||
}
|
||||
.w-76 {
|
||||
width: 19rem;
|
||||
}
|
||||
.w-400 {
|
||||
width: 400px;
|
||||
}
|
||||
|
@ -10,7 +10,21 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { SearchOutlined } from '@ant-design/icons';
|
||||
import i18next from 'i18next';
|
||||
import { ReactComponent as ClassificationIcon } from '../assets/svg/classification.svg';
|
||||
import { ReactComponent as IconDataModel } from '../assets/svg/data-model.svg';
|
||||
import { ReactComponent as GlossaryIcon } from '../assets/svg/glossary.svg';
|
||||
import { ReactComponent as DashboardIcon } from '../assets/svg/ic-dashboard.svg';
|
||||
import { ReactComponent as DataProductIcon } from '../assets/svg/ic-data-product.svg';
|
||||
import { ReactComponent as DatabaseIcon } from '../assets/svg/ic-database.svg';
|
||||
import { ReactComponent as MlModelIcon } from '../assets/svg/ic-ml-model.svg';
|
||||
import { ReactComponent as PipelineIcon } from '../assets/svg/ic-pipeline.svg';
|
||||
import { ReactComponent as SchemaIcon } from '../assets/svg/ic-schema.svg';
|
||||
import { ReactComponent as ContainerIcon } from '../assets/svg/ic-storage.svg';
|
||||
import { ReactComponent as IconStoredProcedure } from '../assets/svg/ic-stored-procedure.svg';
|
||||
import { ReactComponent as TableIcon } from '../assets/svg/ic-table.svg';
|
||||
import { ReactComponent as TopicIcon } from '../assets/svg/ic-topic.svg';
|
||||
import { ReactComponent as IconTable } from '../assets/svg/table-grey.svg';
|
||||
import {
|
||||
Option,
|
||||
@ -82,84 +96,98 @@ class SearchClassBase {
|
||||
sortingFields: tableSortingFields,
|
||||
sortField: INITIAL_SORT_FIELD,
|
||||
path: 'tables',
|
||||
icon: TableIcon,
|
||||
},
|
||||
[SearchIndex.STORED_PROCEDURE]: {
|
||||
label: i18n.t('label.stored-procedure-plural'),
|
||||
sortingFields: entitySortingFields,
|
||||
sortField: INITIAL_SORT_FIELD,
|
||||
path: 'storedProcedure',
|
||||
icon: IconStoredProcedure,
|
||||
},
|
||||
[SearchIndex.DATABASE]: {
|
||||
label: i18n.t('label.database-plural'),
|
||||
sortingFields: entitySortingFields,
|
||||
sortField: INITIAL_SORT_FIELD,
|
||||
path: 'databases',
|
||||
icon: DatabaseIcon,
|
||||
},
|
||||
[SearchIndex.DATABASE_SCHEMA]: {
|
||||
label: i18n.t('label.database-schema-plural'),
|
||||
sortingFields: entitySortingFields,
|
||||
sortField: INITIAL_SORT_FIELD,
|
||||
path: 'databaseSchemas',
|
||||
icon: SchemaIcon,
|
||||
},
|
||||
[SearchIndex.DASHBOARD]: {
|
||||
label: i18n.t('label.dashboard-plural'),
|
||||
sortingFields: entitySortingFields,
|
||||
sortField: INITIAL_SORT_FIELD,
|
||||
path: 'dashboards',
|
||||
icon: DashboardIcon,
|
||||
},
|
||||
[SearchIndex.DASHBOARD_DATA_MODEL]: {
|
||||
label: i18n.t('label.dashboard-data-model-plural'),
|
||||
sortingFields: entitySortingFields,
|
||||
sortField: INITIAL_SORT_FIELD,
|
||||
path: 'dashboardDataModel',
|
||||
icon: IconDataModel,
|
||||
},
|
||||
[SearchIndex.PIPELINE]: {
|
||||
label: i18n.t('label.pipeline-plural'),
|
||||
sortingFields: entitySortingFields,
|
||||
sortField: INITIAL_SORT_FIELD,
|
||||
path: 'pipelines',
|
||||
icon: PipelineIcon,
|
||||
},
|
||||
[SearchIndex.TOPIC]: {
|
||||
label: i18n.t('label.topic-plural'),
|
||||
sortingFields: entitySortingFields,
|
||||
sortField: INITIAL_SORT_FIELD,
|
||||
path: 'topics',
|
||||
icon: TopicIcon,
|
||||
},
|
||||
[SearchIndex.MLMODEL]: {
|
||||
label: i18n.t('label.ml-model-plural'),
|
||||
sortingFields: entitySortingFields,
|
||||
sortField: INITIAL_SORT_FIELD,
|
||||
path: 'mlmodels',
|
||||
icon: MlModelIcon,
|
||||
},
|
||||
[SearchIndex.CONTAINER]: {
|
||||
label: i18n.t('label.container-plural'),
|
||||
sortingFields: entitySortingFields,
|
||||
sortField: INITIAL_SORT_FIELD,
|
||||
path: 'containers',
|
||||
icon: ContainerIcon,
|
||||
},
|
||||
[SearchIndex.SEARCH_INDEX]: {
|
||||
label: i18n.t('label.search-index-plural'),
|
||||
sortingFields: entitySortingFields,
|
||||
sortField: INITIAL_SORT_FIELD,
|
||||
path: 'searchIndexes',
|
||||
icon: SearchOutlined,
|
||||
},
|
||||
[SearchIndex.GLOSSARY]: {
|
||||
label: i18n.t('label.glossary-plural'),
|
||||
sortingFields: entitySortingFields,
|
||||
sortField: INITIAL_SORT_FIELD,
|
||||
path: 'glossaries',
|
||||
icon: GlossaryIcon,
|
||||
},
|
||||
[SearchIndex.TAG]: {
|
||||
label: i18n.t('label.tag-plural'),
|
||||
sortingFields: entitySortingFields,
|
||||
sortField: INITIAL_SORT_FIELD,
|
||||
path: 'tags',
|
||||
icon: ClassificationIcon,
|
||||
},
|
||||
[SearchIndex.DATA_PRODUCT]: {
|
||||
label: i18n.t('label.data-product-plural'),
|
||||
sortingFields: tableSortingFields,
|
||||
sortField: INITIAL_SORT_FIELD,
|
||||
path: 'dataProducts',
|
||||
icon: DataProductIcon,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user