mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-10-17 19:57:56 +00:00
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:
parent
0fdd58939c
commit
7d91ed9eb2
@ -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<{
|
||||
|
@ -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;
|
@ -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;
|
||||
}
|
@ -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();
|
||||
});
|
||||
});
|
@ -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;
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 && (
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 = {
|
||||
|
@ -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',
|
||||
},
|
||||
};
|
||||
|
@ -22,4 +22,5 @@ export enum ExplorePageTabs {
|
||||
DASHBOARDS = 'dashboards',
|
||||
PIPELINES = 'pipelines',
|
||||
MLMODELS = 'mlmodels',
|
||||
CONTAINERS = 'containers',
|
||||
}
|
||||
|
@ -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,
|
||||
];
|
||||
|
@ -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<
|
||||
|
@ -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}}"
|
||||
|
@ -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}}"
|
||||
|
@ -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}}"
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
});
|
||||
}
|
||||
),
|
||||
|
@ -52,6 +52,7 @@ const exploreCount = {
|
||||
[SearchIndex.DASHBOARD]: 0,
|
||||
[SearchIndex.PIPELINE]: 0,
|
||||
[SearchIndex.MLMODEL]: 0,
|
||||
[SearchIndex.CONTAINER]: 0,
|
||||
};
|
||||
|
||||
const TourPage = () => {
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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 [];
|
||||
}
|
||||
|
@ -247,6 +247,7 @@ export const getEntityLink = (
|
||||
return getMlModelPath(fullyQualifiedName);
|
||||
|
||||
case EntityType.CONTAINER:
|
||||
case SearchIndex.CONTAINER:
|
||||
return getContainerDetailPath(fullyQualifiedName);
|
||||
|
||||
case SearchIndex.TABLE:
|
||||
|
Loading…
x
Reference in New Issue
Block a user