feat: add container entity in explore page (#10632)

* feat: add container entity in explore page

* fix: adding missing localization keys

* fix: adding localization on other languages

* feat: add details for entity summary panel

* fix: add localization

* test: add unit tests for container summary
This commit is contained in:
karanh37 2023-03-20 12:16:47 +05:30 committed by GitHub
parent 0fdd58939c
commit 7d91ed9eb2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 388 additions and 62 deletions

View File

@ -11,7 +11,7 @@
* limitations under the License.
*/
import { EntityData } from 'components/common/PopOverCard/EntityPopOverCard';
import { EntityUnion } from 'components/Explore/explore.interface';
import { isEmpty, isNil, isUndefined } from 'lodash';
import { action, makeAutoObservable } from 'mobx';
import { ClientAuth, NewUser } from 'Models';
@ -38,7 +38,7 @@ class AppState {
nonSecureUserDetails: User = {} as User;
userDetails: User = {} as User;
userDataProfiles: Record<string, User> = {};
entityData: Record<string, EntityData> = {};
entityData: Record<string, EntityUnion> = {};
userTeams: Array<UserTeams> = [];
userPermissions: ResourcePermission[] = [];
userProfilePics: Array<{

View File

@ -0,0 +1,131 @@
/*
* Copyright 2022 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 { Col, Divider, Row, Typography } from 'antd';
import classNames from 'classnames';
import SummaryPanelSkeleton from 'components/Skeleton/SummaryPanelSkeleton/SummaryPanelSkeleton.component';
import { SummaryEntityType } from 'enums/EntitySummary.enum';
import { ExplorePageTabs } from 'enums/Explore.enum';
import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import { getFormattedEntityData } from 'utils/EntitySummaryPanelUtils';
import {
DRAWER_NAVIGATION_OPTIONS,
getEntityOverview,
} from 'utils/EntityUtils';
import SVGIcons from 'utils/SvgUtils';
import SummaryList from '../SummaryList/SummaryList.component';
import { BasicEntityInfo } from '../SummaryList/SummaryList.interface';
import { ContainerSummaryProps } from './ContainerSummary.interface';
function ContainerSummary({
entityDetails,
componentType = DRAWER_NAVIGATION_OPTIONS.explore,
isLoading,
}: ContainerSummaryProps) {
const { t } = useTranslation();
console.log(entityDetails);
const entityInfo = useMemo(
() => getEntityOverview(ExplorePageTabs.CONTAINERS, entityDetails),
[entityDetails]
);
const formattedColumnsData: BasicEntityInfo[] = useMemo(
() =>
getFormattedEntityData(
SummaryEntityType.COLUMN,
entityDetails.dataModel?.columns
),
[entityDetails]
);
return (
<SummaryPanelSkeleton loading={Boolean(isLoading)}>
<>
<Row className="m-md" gutter={[0, 4]}>
<Col span={24}>
<Row>
{entityInfo.map((info) => {
const isOwner = info.name === t('label.owner');
return info.visible?.includes(componentType) ? (
<Col key={info.name} span={24}>
<Row
className={classNames('', {
'p-b-md': isOwner,
})}
gutter={[16, 32]}>
{!isOwner ? (
<Col data-testid={`${info.name}-label`} span={8}>
<Typography.Text className="text-grey-muted">
{info.name}
</Typography.Text>
</Col>
) : null}
<Col data-testid={`${info.name}-value`} span={16}>
{info.isLink ? (
<Link
target={info.isExternal ? '_blank' : '_self'}
to={{ pathname: info.url }}>
{info.value}
{info.isExternal ? (
<SVGIcons
alt="external-link"
className="m-l-xs"
icon="external-link"
width="12px"
/>
) : null}
</Link>
) : (
<Typography.Text
className={classNames('text-grey-muted', {
'text-grey-body': !isOwner,
})}>
{info.value}
</Typography.Text>
)}
</Col>
</Row>
</Col>
) : null;
})}
</Row>
</Col>
</Row>
<Divider className="m-y-xs" />
<Row className="m-md" gutter={[0, 16]}>
<Col span={24}>
<Typography.Text
className="text-base text-grey-muted"
data-testid="schema-header">
{t('label.schema')}
</Typography.Text>
</Col>
<Col span={24}>
<SummaryList
entityType={SummaryEntityType.COLUMN}
formattedEntityData={formattedColumnsData}
/>
</Col>
</Row>
</>
</SummaryPanelSkeleton>
);
}
export default ContainerSummary;

View File

@ -0,0 +1,21 @@
/*
* Copyright 2023 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 { Container } from 'generated/entity/data/container';
import { TagLabel } from 'generated/type/tagLabel';
export interface ContainerSummaryProps {
entityDetails: Container;
componentType?: string;
tags?: TagLabel[];
isLoading?: boolean;
}

View File

@ -0,0 +1,112 @@
/*
* Copyright 2023 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 { act, render, screen } from '@testing-library/react';
import {
Constraint,
Container,
DataType,
FileFormat,
ObjectStoreServiceType,
} from 'generated/entity/data/container';
import React from 'react';
import ContainerSummary from './ContainerSummary.component';
jest.mock('../SummaryList/SummaryList.component', () =>
jest
.fn()
.mockImplementation(() => <div data-testid="SummaryList">SummaryList</div>)
);
const mockEntityDetails: Container = {
id: '63be99e5-8ebf-44b6-8247-0f4faed00798',
name: 'transactions',
fullyQualifiedName: 's3_object_store_sample.transactions',
displayName: 'Company Transactions',
description: "Bucket containing all the company's transactions",
version: 0.1,
updatedAt: 1678969800877,
updatedBy: 'admin',
href: 'http://openmetadata-server:8585/api/v1/containers/63be99e5-8ebf-44b6-8247-0f4faed00798',
service: {
id: '7ab99e67-b578-4361-bad2-9076a52b341d',
type: 'objectStoreService',
name: 's3_object_store_sample',
fullyQualifiedName: 's3_object_store_sample',
deleted: false,
href: 'http://openmetadata-server:8585/api/v1/services/objectstoreServices/7ab99e67-b578-4361-bad2-9076a52b341d',
},
dataModel: {
isPartitioned: true,
columns: [
{
name: 'transaction_id',
dataType: DataType.Numeric,
dataTypeDisplay: 'numeric',
description:
'The ID of the executed transaction. This column is the primary key for this table.',
fullyQualifiedName:
's3_object_store_sample.transactions.transaction_id',
tags: [],
constraint: Constraint.PrimaryKey,
ordinalPosition: 1,
},
{
name: 'merchant',
dataType: DataType.Varchar,
dataLength: 100,
dataTypeDisplay: 'varchar',
description: 'The merchant for this transaction.',
fullyQualifiedName: 's3_object_store_sample.transactions.merchant',
tags: [],
ordinalPosition: 2,
},
{
name: 'transaction_time',
dataType: DataType.Timestamp,
dataTypeDisplay: 'timestamp',
description: 'The time the transaction took place.',
fullyQualifiedName:
's3_object_store_sample.transactions.transaction_time',
tags: [],
ordinalPosition: 3,
},
],
},
prefix: '/transactions/',
numberOfObjects: 50,
size: 102400,
fileFormats: [FileFormat.Parquet],
serviceType: ObjectStoreServiceType.S3,
deleted: false,
tags: [],
followers: [],
};
describe('ContainerSummary component tests', () => {
it('Component should render properly, when loaded in the Explore page.', async () => {
await act(async () => {
render(<ContainerSummary entityDetails={mockEntityDetails} />);
});
const numberOfObjects = screen.getByTestId('label.number-of-object-value');
const serviceType = screen.getByTestId('label.service-type-value');
const colsLength = screen.getByTestId('label.column-plural-value');
const summaryList = screen.getByTestId('SummaryList');
expect(numberOfObjects).toBeInTheDocument();
expect(serviceType).toBeInTheDocument();
expect(colsLength).toBeInTheDocument();
expect(summaryList).toBeInTheDocument();
});
});

View File

@ -15,6 +15,7 @@ import { CloseOutlined } from '@ant-design/icons';
import { Col, Drawer, Row } from 'antd';
import TableDataCardTitle from 'components/common/table-data-card-v2/TableDataCardTitle.component';
import { EntityType } from 'enums/entity.enum';
import { Container } from 'generated/entity/data/container';
import React, { useMemo, useState } from 'react';
import { useParams } from 'react-router-dom';
import { ExplorePageTabs } from '../../../enums/Explore.enum';
@ -23,6 +24,7 @@ import { Mlmodel } from '../../../generated/entity/data/mlmodel';
import { Pipeline } from '../../../generated/entity/data/pipeline';
import { Table } from '../../../generated/entity/data/table';
import { Topic } from '../../../generated/entity/data/topic';
import ContainerSummary from './ContainerSummary/ContainerSummary.component';
import DashboardSummary from './DashboardSummary/DashboardSummary.component';
import { EntitySummaryPanelProps } from './EntitySummaryPanel.interface';
import './EntitySummaryPanel.style.less';
@ -73,6 +75,15 @@ export default function EntitySummaryPanel({
<MlModelSummary entityDetails={entityDetails.details as Mlmodel} />
);
case ExplorePageTabs.CONTAINERS:
setCurrentSearchIndex(EntityType.CONTAINER);
return (
<ContainerSummary
entityDetails={entityDetails.details as Container}
/>
);
default:
return null;
}

View File

@ -47,7 +47,7 @@ import AppliedFilterText from './AppliedFilterText/AppliedFilterText';
import EntitySummaryPanel from './EntitySummaryPanel/EntitySummaryPanel.component';
import {
EntityDetailsObjectInterface,
EntityDetailsType,
EntityUnion,
ExploreProps,
ExploreQuickFilterField,
ExploreSearchIndex,
@ -83,7 +83,7 @@ const Explore: React.FC<ExploreProps> = ({
>([] as ExploreQuickFilterField[]);
const [showSummaryPanel, setShowSummaryPanel] = useState(false);
const [entityDetails, setEntityDetails] =
useState<{ details: EntityDetailsType; entityType: string }>();
useState<{ details: EntityUnion; entityType: string }>();
const { toggleModal, sqlQuery } = useAdvanceSearch();
@ -160,7 +160,7 @@ const Explore: React.FC<ExploreProps> = ({
};
const handleSummaryPanelDisplay = useCallback(
(details: EntityDetailsType, entityType: string) => {
(details: EntityUnion, entityType: string) => {
setShowSummaryPanel(true);
setEntityDetails({ details, entityType });
},
@ -231,7 +231,7 @@ const Explore: React.FC<ExploreProps> = ({
searchResults?.hits?.hits[0]._index === searchIndex
) {
handleSummaryPanelDisplay(
searchResults?.hits?.hits[0]._source as EntityDetailsType,
searchResults?.hits?.hits[0]._source as EntityUnion,
tab
);
} else {

View File

@ -76,6 +76,7 @@ describe('Test Explore component', () => {
[SearchIndex.DASHBOARD]: 8,
[SearchIndex.PIPELINE]: 5,
[SearchIndex.MLMODEL]: 2,
[SearchIndex.CONTAINER]: 7,
}}
onChangeAdvancedSearchQueryFilter={mockFunction}
onChangePostFilter={mockFunction}
@ -96,6 +97,6 @@ describe('Test Explore component', () => {
expect(searchData).toBeInTheDocument();
expect(wrappedContent).toBeInTheDocument();
expect(tabs).toHaveLength(5);
expect(tabs).toHaveLength(6);
});
});

View File

@ -12,10 +12,7 @@
*/
import { SearchIndex } from '../../enums/search.enum';
import {
EntityDetailsType,
ExploreQuickFilterField,
} from './explore.interface';
import { EntityUnion, ExploreQuickFilterField } from './explore.interface';
export interface ExploreQuickFiltersProps {
index: SearchIndex;
@ -32,5 +29,5 @@ export interface FilterFieldsMenuItem {
export interface FormattedSuggestResponseObject {
text: string;
source: EntityDetailsType;
source: EntityUnion;
}

View File

@ -34,7 +34,7 @@ import {
import { showErrorToast } from '../../utils/ToastUtils';
import SearchDropdown from '../SearchDropdown/SearchDropdown';
import { SearchDropdownOption } from '../SearchDropdown/SearchDropdown.interface';
import { EntityDetailsType } from './explore.interface';
import { EntityUnion } from './explore.interface';
import { ExploreQuickFiltersProps } from './ExploreQuickFilters.interface';
const ExploreQuickFilters: FC<ExploreQuickFiltersProps> = ({
@ -100,7 +100,7 @@ const ExploreQuickFilters: FC<ExploreQuickFiltersProps> = ({
const formattedSuggestions = suggestOptions.map((op) => ({
text: op.text,
source: op._source as EntityDetailsType,
source: op._source as EntityUnion,
}));
const optionsArray = getOptionsObject(key, formattedSuggestions);

View File

@ -13,6 +13,9 @@
import { DefaultOptionType } from 'antd/lib/select';
import { SORT_ORDER } from 'enums/common.enum';
import { Container } from 'generated/entity/data/container';
import { Database } from 'generated/entity/data/database';
import { DatabaseSchema } from 'generated/entity/data/databaseSchema';
import { SearchIndex } from '../../enums/search.enum';
import { Dashboard } from '../../generated/entity/data/dashboard';
import { Mlmodel } from '../../generated/entity/data/mlmodel';
@ -33,7 +36,8 @@ export type ExploreSearchIndex =
| SearchIndex.PIPELINE
| SearchIndex.DASHBOARD
| SearchIndex.MLMODEL
| SearchIndex.TOPIC;
| SearchIndex.TOPIC
| SearchIndex.CONTAINER;
export type ExploreSearchIndexKey =
| 'TABLE'
@ -96,9 +100,18 @@ export interface SearchInputProps {
handleClear: () => void;
}
export type EntityDetailsType = Table | Topic | Dashboard | Pipeline | Mlmodel;
// Type for all the explore tab entities
export type EntityUnion =
| Table
| Topic
| Dashboard
| Pipeline
| Mlmodel
| Container
| DatabaseSchema
| Database;
export interface EntityDetailsObjectInterface {
details: EntityDetailsType;
details: EntityUnion;
entityType: string;
}

View File

@ -13,6 +13,7 @@
import { Button, Divider, Popover, Space, Typography } from 'antd';
import { AxiosError } from 'axios';
import { EntityUnion } from 'components/Explore/explore.interface';
import { uniqueId } from 'lodash';
import { EntityTags } from 'Models';
import React, { FC, HTMLAttributes, useEffect, useMemo, useState } from 'react';
@ -31,13 +32,7 @@ import { getEntityName } from 'utils/EntityUtils';
import AppState from '../../../AppState';
import { FQN_SEPARATOR_CHAR } from '../../../constants/char.constants';
import { EntityType } from '../../../enums/entity.enum';
import { Dashboard } from '../../../generated/entity/data/dashboard';
import { Database } from '../../../generated/entity/data/database';
import { DatabaseSchema } from '../../../generated/entity/data/databaseSchema';
import { Mlmodel } from '../../../generated/entity/data/mlmodel';
import { Pipeline } from '../../../generated/entity/data/pipeline';
import { Table } from '../../../generated/entity/data/table';
import { Topic } from '../../../generated/entity/data/topic';
import { TagSource } from '../../../generated/type/tagLabel';
import SVGIcons from '../../../utils/SvgUtils';
import {
@ -49,15 +44,6 @@ import { showErrorToast } from '../../../utils/ToastUtils';
import ProfilePicture from '../ProfilePicture/ProfilePicture';
import RichTextEditorPreviewer from '../rich-text-editor/RichTextEditorPreviewer';
export type EntityData =
| Table
| Topic
| Dashboard
| Pipeline
| Mlmodel
| Database
| DatabaseSchema;
interface Props extends HTMLAttributes<HTMLDivElement> {
entityType: string;
entityFQN: string;
@ -65,7 +51,7 @@ interface Props extends HTMLAttributes<HTMLDivElement> {
const EntityPopOverCard: FC<Props> = ({ children, entityType, entityFQN }) => {
const { t } = useTranslation();
const [entityData, setEntityData] = useState<EntityData>({} as EntityData);
const [entityData, setEntityData] = useState<EntityUnion>({} as EntityUnion);
const entityTier = useMemo(() => {
const tierFQN = getTierTags((entityData as Table).tags || [])?.tagFQN;
@ -83,13 +69,13 @@ const EntityPopOverCard: FC<Props> = ({ children, entityType, entityFQN }) => {
}, [(entityData as Table).tags]);
const getData = () => {
const setEntityDetails = (entityDetail: EntityData) => {
const setEntityDetails = (entityDetail: EntityUnion) => {
AppState.entityData[entityFQN] = entityDetail;
};
const fields = 'tags,owner';
let promise: Promise<EntityData> | null = null;
let promise: Promise<EntityUnion> | null = null;
switch (entityType) {
case EntityType.TABLE:

View File

@ -11,12 +11,12 @@
* limitations under the License.
*/
import { Col, Divider, Row, Typography } from 'antd';
import { EntityUnion } from 'components/Explore/explore.interface';
import TagsViewer from 'components/Tag/TagsViewer/tags-viewer';
import { TagLabel } from 'generated/type/tagLabel';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { ReactComponent as TagIcon } from '../../../assets/svg/tag-grey.svg';
import { EntityData } from '../PopOverCard/EntityPopOverCard';
import RichTextEditorPreviewer from '../rich-text-editor/RichTextEditorPreviewer';
const SummaryTagsDescription = ({
@ -24,7 +24,7 @@ const SummaryTagsDescription = ({
entityDetail,
}: {
tags: TagLabel[];
entityDetail: EntityData;
entityDetail: EntityUnion;
}) => {
const { t } = useTranslation();

View File

@ -25,14 +25,13 @@ import {
} from '../../../utils/CommonUtils';
import { stringToHTML } from '../../../utils/StringsUtils';
import { getEntityLink } from '../../../utils/TableUtils';
import { SourceType } from '../../searched-data/SearchedData.interface';
import './TableDataCardTitle.less';
interface TableDataCardTitleProps {
dataTestId?: string;
id?: string;
searchIndex: SearchIndex | EntityType;
source: SourceType;
source: { fullyQualifiedName?: string; displayName?: string };
isPanel?: boolean;
handleLinkClick?: (e: React.MouseEvent) => void;
}

View File

@ -13,6 +13,7 @@
import { ExclamationCircleOutlined } from '@ant-design/icons';
import classNames from 'classnames';
import { EntityUnion } from 'components/Explore/explore.interface';
import { isString, startCase, uniqueId } from 'lodash';
import { ExtraInfo } from 'Models';
import React, { useMemo } from 'react';
@ -33,7 +34,6 @@ import {
} from '../../../utils/CommonUtils';
import { serviceTypeLogo } from '../../../utils/ServiceUtils';
import { getUsagePercentile } from '../../../utils/TableUtils';
import { EntityDetailsType } from '../../Explore/explore.interface';
import { SearchedDataProps } from '../../searched-data/SearchedData.interface';
import '../table-data-card/TableDataCard.style.css';
import TableDataCardBody from '../table-data-card/TableDataCardBody';
@ -50,7 +50,7 @@ export interface TableDataCardPropsV2 {
}[];
searchIndex: SearchIndex | EntityType;
handleSummaryPanelDisplay?: (
details: EntityDetailsType,
details: EntityUnion,
entityType: string
) => void;
}
@ -133,7 +133,7 @@ const TableDataCardV2: React.FC<TableDataCardPropsV2> = ({
id={id}
onClick={() => {
handleSummaryPanelDisplay &&
handleSummaryPanelDisplay(source as EntityDetailsType, tab);
handleSummaryPanelDisplay(source as EntityUnion, tab);
}}>
<div>
{'databaseSchema' in source && 'database' in source && (

View File

@ -21,10 +21,7 @@ import {
SearchHitBody,
TableSearchSource,
} from '../../interface/search.interface';
import {
EntityDetailsType,
ExploreSearchIndex,
} from '../Explore/explore.interface';
import { EntityUnion, ExploreSearchIndex } from '../Explore/explore.interface';
type Fields =
| 'name'
@ -75,7 +72,7 @@ export interface SearchedDataProps {
showOnlyChildren?: boolean;
isFilterSelected: boolean;
handleSummaryPanelDisplay?: (
details: EntityDetailsType,
details: EntityUnion,
entityType: string
) => void;
}

View File

@ -119,7 +119,6 @@ export const INGESTION_NAME = ':ingestionName';
export const LOG_ENTITY_NAME = ':logEntityName';
export const KPI_NAME = ':kpiName';
export const PLACEHOLDER_ACTION = ':action';
export const PLACEHOLDER_CONTAINER_NAME = ':containerName';
export const pagingObject = { after: '', before: '', total: 0 };
@ -257,8 +256,8 @@ export const ROUTES = {
ADD_KPI: `/data-insights/kpi/add-kpi`,
EDIT_KPI: `/data-insights/kpi/edit-kpi/${KPI_NAME}`,
CONTAINER_DETAILS: `/container/${PLACEHOLDER_CONTAINER_NAME}`,
CONTAINER_DETAILS_WITH_TAB: `/container/${PLACEHOLDER_CONTAINER_NAME}/${PLACEHOLDER_ROUTE_TAB}`,
CONTAINER_DETAILS: `/container/${PLACEHOLDER_ROUTE_ENTITY_FQN}`,
CONTAINER_DETAILS_WITH_TAB: `/container/${PLACEHOLDER_ROUTE_ENTITY_FQN}/${PLACEHOLDER_ROUTE_TAB}`,
};
export const SOCKET_EVENTS = {

View File

@ -27,19 +27,19 @@ export const MAX_RESULT_HITS = 10000;
// as it is used only in unit tests it's not needed for translation
export const tableSortingFields: SortingField[] = [
{
name: 'Last Updated',
name: t('label.last-updated'),
value: 'updatedAt',
},
{ name: 'Weekly Usage', value: 'usageSummary.weeklyStats.count' },
{ name: 'Relevance', value: '_score' },
{ name: t('label.weekly-usage'), value: 'usageSummary.weeklyStats.count' },
{ name: t('label.relevance'), value: '_score' },
];
export const entitySortingFields = [
{
name: 'Last Updated',
name: t('label.last-updated'),
value: 'updatedAt',
},
{ name: 'Relevance', value: '_score' },
{ name: t('label.relevance'), value: '_score' },
];
export interface ExploreTabInfo {
@ -90,4 +90,10 @@ export const tabsInfo: { [K in ExploreSearchIndex]: ExploreTabInfo } = {
sortField: INITIAL_SORT_FIELD,
path: 'mlmodels',
},
[SearchIndex.CONTAINER]: {
label: t('label.container-plural'),
sortingFields: entitySortingFields,
sortField: INITIAL_SORT_FIELD,
path: 'containers',
},
};

View File

@ -22,4 +22,5 @@ export enum ExplorePageTabs {
DASHBOARDS = 'dashboards',
PIPELINES = 'pipelines',
MLMODELS = 'mlmodels',
CONTAINERS = 'containers',
}

View File

@ -21,6 +21,7 @@ export enum SearchIndex {
GLOSSARY = 'glossary_search_index',
MLMODEL = 'mlmodel_search_index',
TAG = 'tag_search_index',
CONTAINER = 'container_search_index',
}
export const GENERAL_SEARCH_INDEX = [
@ -29,4 +30,5 @@ export const GENERAL_SEARCH_INDEX = [
SearchIndex.TOPIC,
SearchIndex.PIPELINE,
SearchIndex.MLMODEL,
SearchIndex.CONTAINER,
];

View File

@ -11,6 +11,7 @@
* limitations under the License.
*/
import { Container } from 'generated/entity/data/container';
import { SearchIndex } from '../enums/search.enum';
import { Tag } from '../generated/entity/classification/tag';
import { Dashboard } from '../generated/entity/data/dashboard';
@ -67,6 +68,8 @@ export interface UserSearchSource extends SearchSourceBase, User {} // extends E
export interface TeamSearchSource extends SearchSourceBase, Team {} // extends EntityInterface
export interface ContainerSearchSource extends SearchSourceBase, Container {} // extends EntityInterface
export interface TagClassSearchSource extends SearchSourceBase, Tag {
id: string; // Tag is generated with the `id` field as optional, which is should not
} // extends EntityInterface
@ -78,7 +81,8 @@ export type ExploreSearchSource =
| DashboardSearchSource
| MlmodelSearchSource
| TopicSearchSource
| PipelineSearchSource;
| PipelineSearchSource
| ContainerSearchSource;
export type SearchIndexSearchSourceMapping = {
[SearchIndex.TABLE]: TableSearchSource;
@ -90,6 +94,7 @@ export type SearchIndexSearchSourceMapping = {
[SearchIndex.USER]: UserSearchSource;
[SearchIndex.TOPIC]: TopicSearchSource;
[SearchIndex.TAG]: TagClassSearchSource;
[SearchIndex.CONTAINER]: ContainerSearchSource;
};
export type SearchRequest<

View File

@ -492,6 +492,7 @@
"notification-plural": "Notifications",
"november": "November",
"null": "Null",
"number-of-object": "Number of objects",
"number-of-rows": "Number of rows",
"object-store": "Object Store",
"object-store-plural": "Object Stores",
@ -599,6 +600,7 @@
"region-name": "Region Name",
"registry": "Registry",
"related-term-plural": "Related Terms",
"relevance": "Relevance",
"remove": "Remove",
"remove-entity": "Remove {{entity}}",
"removed": "Removed",
@ -843,6 +845,7 @@
"webhook-display-text": "Webhook {{displayText}}",
"wednesday": "Wednesday",
"week": "Week",
"weekly-usage": "Weekly Usage",
"whats-new": "What's New",
"yes": "Yes",
"your-entity": "Your {{entity}}"

View File

@ -492,6 +492,7 @@
"notification-plural": "Notifications",
"november": "November",
"null": "Null",
"number-of-object": "Number of objects",
"number-of-rows": "Number of rows",
"object-store": "Object Store",
"object-store-plural": "Object Stores",
@ -599,6 +600,7 @@
"region-name": "Nom de Région",
"registry": "Registry",
"related-term-plural": "Related Terms",
"relevance": "Relevance",
"remove": "Enlever",
"remove-entity": "Supprimer {{entity}}",
"removed": "Removed",
@ -843,6 +845,7 @@
"webhook-display-text": "Webhook {{displayText}}",
"wednesday": "Wednesday",
"week": "Week",
"weekly-usage": "Weekly Usage",
"whats-new": "Nouveau",
"yes": "Yes",
"your-entity": "Your {{entity}}"

View File

@ -492,6 +492,7 @@
"notification-plural": "Notifications",
"november": "November",
"null": "Null",
"number-of-object": "Number of objects",
"number-of-rows": "Number of rows",
"object-store": "Object Store",
"object-store-plural": "Object Stores",
@ -599,6 +600,7 @@
"region-name": "Region Name",
"registry": "注册",
"related-term-plural": "Related Terms",
"relevance": "Relevance",
"remove": "删除",
"remove-entity": "删除 {{entity}}",
"removed": "Removed",
@ -843,6 +845,7 @@
"webhook-display-text": "Webhook {{displayText}}",
"wednesday": "Wednesday",
"week": "Week",
"weekly-usage": "Weekly Usage",
"whats-new": "What's new",
"yes": "是",
"your-entity": "Your {{entity}}"

View File

@ -88,8 +88,8 @@ const ContainerPage = () => {
const history = useHistory();
const { t } = useTranslation();
const { getEntityPermissionByFqn } = usePermissionProvider();
const { containerName, tab = CONTAINER_DETAILS_TABS.SCHEME } =
useParams<{ containerName: string; tab: CONTAINER_DETAILS_TABS }>();
const { entityFQN: containerName, tab = CONTAINER_DETAILS_TABS.SCHEME } =
useParams<{ entityFQN: string; tab: CONTAINER_DETAILS_TABS }>();
// Local states
const [isLoading, setIsLoading] = useState<boolean>(false);

View File

@ -202,6 +202,7 @@ const ExplorePage: FunctionComponent = () => {
SearchIndex.DASHBOARD,
SearchIndex.PIPELINE,
SearchIndex.MLMODEL,
SearchIndex.CONTAINER,
].map((index) =>
searchQuery({
query: searchQueryParam,
@ -221,6 +222,7 @@ const ExplorePage: FunctionComponent = () => {
dashboardResponse,
pipelineResponse,
mlmodelResponse,
containerResponse,
]) => {
setSearchHitCounts({
[SearchIndex.TABLE]: tableResponse.hits.total.value,
@ -228,6 +230,7 @@ const ExplorePage: FunctionComponent = () => {
[SearchIndex.DASHBOARD]: dashboardResponse.hits.total.value,
[SearchIndex.PIPELINE]: pipelineResponse.hits.total.value,
[SearchIndex.MLMODEL]: mlmodelResponse.hits.total.value,
[SearchIndex.CONTAINER]: containerResponse.hits.total.value,
});
}
),

View File

@ -52,6 +52,7 @@ const exploreCount = {
[SearchIndex.DASHBOARD]: 0,
[SearchIndex.PIPELINE]: 0,
[SearchIndex.MLMODEL]: 0,
[SearchIndex.CONTAINER]: 0,
};
const TourPage = () => {

View File

@ -25,6 +25,7 @@ export type SearchEntityHits = SearchResponse<
| SearchIndex.TABLE
| SearchIndex.MLMODEL
| SearchIndex.TOPIC
| SearchIndex.CONTAINER
>['hits']['hits'];
// if more value is added, also update its interface file at -> interface/types.d.ts

View File

@ -11,7 +11,7 @@
* limitations under the License.
*/
import {
PLACEHOLDER_CONTAINER_NAME,
PLACEHOLDER_ROUTE_ENTITY_FQN,
PLACEHOLDER_ROUTE_TAB,
ROUTES,
} from 'constants/constants';
@ -22,7 +22,7 @@ import { EntityTags, TagOption } from 'Models';
export const getContainerDetailPath = (containerFQN: string, tab?: string) => {
let path = tab ? ROUTES.CONTAINER_DETAILS_WITH_TAB : ROUTES.CONTAINER_DETAILS;
path = path.replace(PLACEHOLDER_CONTAINER_NAME, containerFQN);
path = path.replace(PLACEHOLDER_ROUTE_ENTITY_FQN, containerFQN);
if (tab) {
path = path.replace(PLACEHOLDER_ROUTE_TAB, tab);

View File

@ -12,14 +12,15 @@
*/
import { Popover } from 'antd';
import { EntityData } from 'components/common/PopOverCard/EntityPopOverCard';
import ProfilePicture from 'components/common/ProfilePicture/ProfilePicture';
import {
LeafNodes,
LineagePos,
} from 'components/EntityLineage/EntityLineage.interface';
import { EntityUnion } from 'components/Explore/explore.interface';
import { ResourceEntity } from 'components/PermissionProvider/PermissionProvider.interface';
import { ExplorePageTabs } from 'enums/Explore.enum';
import { Container } from 'generated/entity/data/container';
import { Mlmodel } from 'generated/entity/data/mlmodel';
import i18next from 'i18next';
import { isEmpty, isNil, isUndefined, lowerCase, startCase } from 'lodash';
@ -125,7 +126,7 @@ export const getOwnerNameWithProfilePic = (
export const getEntityOverview = (
type: string,
entityDetail: EntityData
entityDetail: EntityUnion
): Array<{
name: string;
value: string | number | React.ReactNode;
@ -428,6 +429,35 @@ export const getEntityOverview = (
return overview;
}
case ExplorePageTabs.CONTAINERS: {
const { numberOfObjects, serviceType, dataModel } =
entityDetail as Container;
const overview = [
{
name: i18next.t('label.number-of-object'),
value: numberOfObjects,
isLink: false,
visible: [DRAWER_NAVIGATION_OPTIONS.explore],
},
{
name: i18next.t('label.service-type'),
value: serviceType,
isLink: false,
visible: [DRAWER_NAVIGATION_OPTIONS.explore],
},
{
name: i18next.t('label.column-plural'),
value:
dataModel && dataModel.columns ? dataModel.columns.length : NO_DATA,
isLink: false,
visible: [DRAWER_NAVIGATION_OPTIONS.explore],
},
];
return overview;
}
default:
return [];
}

View File

@ -247,6 +247,7 @@ export const getEntityLink = (
return getMlModelPath(fullyQualifiedName);
case EntityType.CONTAINER:
case SearchIndex.CONTAINER:
return getContainerDetailPath(fullyQualifiedName);
case SearchIndex.TABLE: