Minor search issues (#14861)

* fix: minor search issues

* show dbt icon in lineage
This commit is contained in:
Karan Hotchandani 2024-01-25 19:08:02 +05:30 committed by GitHub
parent 6a1c13687e
commit 00838d6c73
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 140 additions and 56 deletions

View File

@ -237,6 +237,7 @@ const CustomControls: FC<ControlProps> = ({
</Button>
</Dropdown>
<ExploreQuickFilters
independent
aggregations={{}}
fields={selectedQuickFilters}
index={SearchIndex.ALL}

View File

@ -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();
});
});

View File

@ -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

View File

@ -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,

View File

@ -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 {

View File

@ -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}

View File

@ -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 {

View File

@ -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) => {

View File

@ -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

View File

@ -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;
}

View File

@ -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,
},
};
}