chore(ui): feedback 1.2 part-1 (#13672)

* updated glossary term approval change message on version page
fixed widget heading stylings
aligned no data placeholders for all the widgets in the center
Fixed the schema styling as well as schema not updating properly for entities on custom property details page.

* localization changes for other languages
This commit is contained in:
Aniket Katkar 2023-10-20 23:11:37 +05:30 committed by GitHub
parent cacee460ed
commit 6c252b5722
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 515 additions and 356 deletions

View File

@ -11,6 +11,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { Typography } from 'antd'; import { Typography } from 'antd';
import { isEmpty } from 'lodash';
import React, { useEffect, useMemo, useState } from 'react'; import React, { useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { ReactComponent as ActivityFeedIcon } from '../../../assets/svg/activity-feed.svg'; import { ReactComponent as ActivityFeedIcon } from '../../../assets/svg/activity-feed.svg';
@ -67,34 +68,31 @@ const ActivityFeedListV1 = ({
return <Loader />; return <Loader />;
} }
return ( return isEmpty(entityThread) ? (
<div className="h-full p-x-md" data-testid="no-data-placeholder-container">
<ErrorPlaceHolder
icon={
isTaskTab ? (
<TaskIcon height={24} width={24} />
) : (
<ActivityFeedIcon height={SIZE.MEDIUM} width={SIZE.MEDIUM} />
)
}
type={ERROR_PLACEHOLDER_TYPE.CUSTOM}>
<Typography.Paragraph
className="tw-max-w-md"
style={{ marginBottom: '0' }}>
{isTaskTab && (
<Typography.Text strong>
{t('message.no-open-tasks')} <br />
</Typography.Text>
)}
{emptyPlaceholderText}
</Typography.Paragraph>
</ErrorPlaceHolder>
</div>
) : (
<div className="feed-list-container p-y-md" id="feedData"> <div className="feed-list-container p-y-md" id="feedData">
{entityThread.length === 0 && (
<div
className="h-full p-x-md"
data-testid="no-data-placeholder-container">
<ErrorPlaceHolder
icon={
isTaskTab ? (
<TaskIcon height={24} width={24} />
) : (
<ActivityFeedIcon height={SIZE.MEDIUM} width={SIZE.MEDIUM} />
)
}
type={ERROR_PLACEHOLDER_TYPE.CUSTOM}>
<Typography.Paragraph
className="tw-max-w-md"
style={{ marginBottom: '0' }}>
{isTaskTab && (
<Typography.Text strong>
{t('message.no-open-tasks')} <br />
</Typography.Text>
)}
{emptyPlaceholderText}
</Typography.Paragraph>
</ErrorPlaceHolder>
</div>
)}
{entityThread.map((feed) => ( {entityThread.map((feed) => (
<FeedPanelBodyV1 <FeedPanelBodyV1
feed={feed} feed={feed}

View File

@ -12,6 +12,7 @@
*/ */
import { Button, Col, Row, Typography } from 'antd'; import { Button, Col, Row, Typography } from 'antd';
import { isEmpty } from 'lodash';
import React, { FunctionComponent } from 'react'; import React, { FunctionComponent } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { EntityReference } from '../../../generated/entity/type'; import { EntityReference } from '../../../generated/entity/type';
@ -44,7 +45,7 @@ export const EntityListWithV1: FunctionComponent<AntdEntityListProp> = ({
<> <>
<Row className="m-b-xs" justify="space-between"> <Row className="m-b-xs" justify="space-between">
<Col> <Col>
<Typography.Text className="right-panel-label"> <Typography.Text className="font-medium">
{headerTextLabel} {headerTextLabel}
</Typography.Text> </Typography.Text>
</Col> </Col>
@ -52,47 +53,49 @@ export const EntityListWithV1: FunctionComponent<AntdEntityListProp> = ({
<Typography.Text>{headerText}</Typography.Text> <Typography.Text>{headerText}</Typography.Text>
</Col> </Col>
</Row> </Row>
<div className="entity-list-body"> {isEmpty(entityList) ? (
{entityList.length <div className="flex-center h-full">{noDataPlaceholder}</div>
? entityList.map((item) => { ) : (
return ( <div className="entity-list-body">
<div {entityList.map((item) => {
className="right-panel-list-item flex items-center justify-between" return (
data-testid={`${testIDText}-${getEntityName( <div
item as unknown as EntityReference className="right-panel-list-item flex items-center justify-between"
)}`} data-testid={`${testIDText}-${getEntityName(
key={item.id}> item as unknown as EntityReference
<div className="flex items-center"> )}`}
<Link key={item.id}>
className="font-medium" <div className="flex items-center">
to={getEntityLink( <Link
item.type || '', className="font-medium"
item.fullyQualifiedName ?? '' to={getEntityLink(
)}> item.type || '',
<Button item.fullyQualifiedName ?? ''
className="entity-button flex-center p-0 m--ml-1" )}>
icon={ <Button
<div className="entity-button-icon m-r-xs"> className="entity-button flex-center p-0 m--ml-1"
{getEntityIcon(item.type || '')} icon={
</div> <div className="entity-button-icon m-r-xs">
} {getEntityIcon(item.type || '')}
title={getEntityName( </div>
item as unknown as EntityReference }
)} title={getEntityName(
type="text"> item as unknown as EntityReference
<Typography.Text )}
className="w-72 text-left text-xs" type="text">
ellipsis={{ tooltip: true }}> <Typography.Text
{getEntityName(item as unknown as EntityReference)} className="w-72 text-left text-xs"
</Typography.Text> ellipsis={{ tooltip: true }}>
</Button> {getEntityName(item as unknown as EntityReference)}
</Link> </Typography.Text>
</div> </Button>
</Link>
</div> </div>
); </div>
}) );
: noDataPlaceholder} })}
</div> </div>
)}
</> </>
</EntityListSkeleton> </EntityListSkeleton>
); );

View File

@ -13,7 +13,7 @@
import { Col, Divider, Drawer, Row, Typography } from 'antd'; import { Col, Divider, Drawer, Row, Typography } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import { capitalize, toString } from 'lodash'; import { capitalize, isEmpty, toString } from 'lodash';
import React, { Fragment, useMemo, useState } from 'react'; import React, { Fragment, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { EntityHistory } from '../../../generated/type/entityHistory'; import { EntityHistory } from '../../../generated/type/entityHistory';
@ -136,7 +136,10 @@ const EntityVersionTimeLine: React.FC<Props> = ({
'diff-description': 'diff-description':
toString(currV?.version) === currentVersion, toString(currV?.version) === currentVersion,
})}> })}>
{getSummary(currV?.changeDescription)} {getSummary({
changeDescription: currV?.changeDescription,
isGlossaryTerm: !isEmpty(currV?.glossary),
})}
</div> </div>
<p className="text-xs font-italic"> <p className="text-xs font-italic">
<span className="font-medium">{currV?.updatedBy}</span> <span className="font-medium">{currV?.updatedBy}</span>

View File

@ -55,7 +55,7 @@ const EmptyPlaceholder = () => {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<div className="d-flex items-center flex-col p-t-sm"> <div className="flex-center flex-col h-full p-t-sm">
<KPIIcon width={80} /> <KPIIcon width={80} />
<div className="m-t-xs text-center"> <div className="m-t-xs text-center">
<Typography.Paragraph style={{ marginBottom: '0' }}> <Typography.Paragraph style={{ marginBottom: '0' }}>
@ -218,9 +218,9 @@ const KPIWidget = ({
data-testid="kpi-card" data-testid="kpi-card"
id="kpi-charts" id="kpi-charts"
loading={isKPIListLoading || isLoading}> loading={isKPIListLoading || isLoading}>
<Row justify="end"> {isEditView && (
<Col> <Row justify="end">
{isEditView && ( <Col>
<Space align="center"> <Space align="center">
<DragOutlined <DragOutlined
className="drag-widget-icon cursor-pointer" className="drag-widget-icon cursor-pointer"
@ -228,9 +228,9 @@ const KPIWidget = ({
/> />
<CloseOutlined size={14} onClick={handleCloseClick} /> <CloseOutlined size={14} onClick={handleCloseClick} />
</Space> </Space>
)} </Col>
</Col> </Row>
</Row> )}
<Row align="middle" justify="space-between"> <Row align="middle" justify="space-between">
<Col> <Col>
<Typography.Text className="font-medium"> <Typography.Text className="font-medium">
@ -238,55 +238,45 @@ const KPIWidget = ({
</Typography.Text> </Typography.Text>
</Col> </Col>
</Row> </Row>
{kpiList.length > 0 ? ( {isEmpty(kpiList) || isEmpty(graphData) ? (
<Row className="p-t-md">
{graphData.length ? (
<>
<Col span={isWidgetSizeMedium ? 14 : 24}>
<ResponsiveContainer debounce={1} height={250} width="100%">
<LineChart
data={graphData}
margin={{
top: 10,
right: isWidgetSizeMedium ? 50 : 20,
left: -30,
bottom: 0,
}}>
<CartesianGrid
stroke={GRAPH_BACKGROUND_COLOR}
vertical={false}
/>
<XAxis dataKey="timestamp" />
<YAxis />
{kpis.map((kpi, i) => (
<Line
dataKey={kpi}
key={kpi}
stroke={KPI_WIDGET_GRAPH_COLORS[i]}
type="monotone"
/>
))}
</LineChart>
</ResponsiveContainer>
</Col>
{!isUndefined(kpiLatestResults) &&
!isEmpty(kpiLatestResults) &&
isWidgetSizeMedium && (
<Col span={10}>
<KPILatestResultsV1
kpiLatestResultsRecord={kpiLatestResults}
/>
</Col>
)}
</>
) : (
<Col span={24}>
<EmptyPlaceholder />
</Col>
)}
</Row>
) : (
<EmptyPlaceholder /> <EmptyPlaceholder />
) : (
<Row className="p-t-md">
<Col span={isWidgetSizeMedium ? 14 : 24}>
<ResponsiveContainer debounce={1} height={250} width="100%">
<LineChart
data={graphData}
margin={{
top: 10,
right: isWidgetSizeMedium ? 50 : 20,
left: -30,
bottom: 0,
}}>
<CartesianGrid
stroke={GRAPH_BACKGROUND_COLOR}
vertical={false}
/>
<XAxis dataKey="timestamp" />
<YAxis />
{kpis.map((kpi, i) => (
<Line
dataKey={kpi}
key={kpi}
stroke={KPI_WIDGET_GRAPH_COLORS[i]}
type="monotone"
/>
))}
</LineChart>
</ResponsiveContainer>
</Col>
{!isUndefined(kpiLatestResults) &&
!isEmpty(kpiLatestResults) &&
isWidgetSizeMedium && (
<Col span={10}>
<KPILatestResultsV1 kpiLatestResultsRecord={kpiLatestResults} />
</Col>
)}
</Row>
)} )}
</Card> </Card>
); );

View File

@ -15,3 +15,11 @@
border-radius: 10px; border-radius: 10px;
box-shadow: none; box-shadow: none;
} }
.kpi-widget-card {
.ant-card-body {
height: 100%;
display: flex;
flex-direction: column;
}
}

View File

@ -12,7 +12,7 @@
*/ */
import { CloseOutlined, DragOutlined } from '@ant-design/icons'; import { CloseOutlined, DragOutlined } from '@ant-design/icons';
import { Button, Card, Col, Row, Space, Typography } from 'antd'; import { Button, Card, Col, Row, Space, Typography } from 'antd';
import { isUndefined } from 'lodash'; import { isEmpty, isUndefined } from 'lodash';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -27,6 +27,7 @@ import { Transi18next } from '../../../utils/CommonUtils';
import { getEntityName } from '../../../utils/EntityUtils'; import { getEntityName } from '../../../utils/EntityUtils';
import { getEntityIcon, getEntityLink } from '../../../utils/TableUtils'; import { getEntityIcon, getEntityLink } from '../../../utils/TableUtils';
import EntityListSkeleton from '../../Skeleton/MyData/EntityListSkeleton/EntityListSkeleton.component'; import EntityListSkeleton from '../../Skeleton/MyData/EntityListSkeleton/EntityListSkeleton.component';
import './MyDataWidget.less';
const MyDataWidgetInternal = ({ const MyDataWidgetInternal = ({
isEditView = false, isEditView = false,
@ -74,7 +75,7 @@ const MyDataWidgetInternal = ({
}, [currentUserDetails]); }, [currentUserDetails]);
return ( return (
<Card className="card-widget h-full" loading={isLoading}> <Card className="my-data-widget-container card-widget" loading={isLoading}>
<Row> <Row>
<Col span={24}> <Col span={24}>
<div className="d-flex justify-between m-b-xs"> <div className="d-flex justify-between m-b-xs">
@ -110,51 +111,51 @@ const MyDataWidgetInternal = ({
<EntityListSkeleton <EntityListSkeleton
dataLength={data.length !== 0 ? data.length : 5} dataLength={data.length !== 0 ? data.length : 5}
loading={Boolean(isLoading)}> loading={Boolean(isLoading)}>
<> {isEmpty(data) ? (
<div className="entity-list-body"> <div className="flex-center h-full">
{data.length ? ( <span className="text-center">
data.map((item) => { <Transi18next
return ( i18nKey="message.no-owned-data"
<div renderElement={<Link to={ROUTES.EXPLORE} />}
className="right-panel-list-item flex items-center justify-between" />
data-testid={`Recently Viewed-${getEntityName(item)}`} </span>
key={item.id}>
<div className="d-flex items-center">
<Link
className=""
to={getEntityLink(
item.type || '',
item.fullyQualifiedName as string
)}>
<Button
className="entity-button flex-center p-0 m--ml-1"
icon={
<div className="entity-button-icon m-r-xs">
{getEntityIcon(item.type || '')}
</div>
}
type="text">
<Typography.Text
className="text-left text-xs"
ellipsis={{ tooltip: true }}>
{getEntityName(item)}
</Typography.Text>
</Button>
</Link>
</div>
</div>
);
})
) : (
<span className="text-sm">
<Transi18next
i18nKey="message.no-owned-data"
renderElement={<Link to={ROUTES.EXPLORE} />}
/>
</span>
)}
</div> </div>
</> ) : (
<div className="entity-list-body">
{data.map((item) => {
return (
<div
className="right-panel-list-item flex items-center justify-between"
data-testid={`Recently Viewed-${getEntityName(item)}`}
key={item.id}>
<div className="d-flex items-center">
<Link
className=""
to={getEntityLink(
item.type || '',
item.fullyQualifiedName as string
)}>
<Button
className="entity-button flex-center p-0 m--ml-1"
icon={
<div className="entity-button-icon m-r-xs">
{getEntityIcon(item.type || '')}
</div>
}
type="text">
<Typography.Text
className="text-left text-xs"
ellipsis={{ tooltip: true }}>
{getEntityName(item)}
</Typography.Text>
</Button>
</Link>
</div>
</div>
);
})}
</div>
)}
</EntityListSkeleton> </EntityListSkeleton>
</Card> </Card>
); );

View File

@ -0,0 +1,19 @@
/*
* 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.
*/
.my-data-widget-container {
.ant-card-body {
display: flex;
flex-direction: column;
}
}

View File

@ -13,6 +13,8 @@
.announcement-container { .announcement-container {
.ant-card-body { .ant-card-body {
height: 100%; height: 100%;
display: flex;
flex-direction: column;
} }
.announcement-container-list { .announcement-container-list {
overflow-y: auto; overflow-y: auto;

View File

@ -13,7 +13,7 @@
import { CloseOutlined, DragOutlined } from '@ant-design/icons'; import { CloseOutlined, DragOutlined } from '@ant-design/icons';
import { Alert, Card, Col, Row, Space, Typography } from 'antd'; import { Alert, Card, Col, Row, Space, Typography } from 'antd';
import { isEmpty, isUndefined } from 'lodash'; import { isEmpty, isUndefined } from 'lodash';
import React, { useCallback } from 'react'; import React, { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { ReactComponent as AnnouncementIcon } from '../../../assets/svg/announcements-v1.svg'; import { ReactComponent as AnnouncementIcon } from '../../../assets/svg/announcements-v1.svg';
import { Thread } from '../../../generated/entity/feed/thread'; import { Thread } from '../../../generated/entity/feed/thread';
@ -41,11 +41,71 @@ function AnnouncementsWidget({
!isUndefined(handleRemoveWidget) && handleRemoveWidget(widgetKey); !isUndefined(handleRemoveWidget) && handleRemoveWidget(widgetKey);
}, [widgetKey]); }, [widgetKey]);
const announcement = useMemo(() => {
if (isAnnouncementLoading) {
return <Loader size="small" />;
}
if (isEmpty(announcements)) {
return (
<div className="flex-center h-full">
{t('message.no-entity-data-available', {
entity: t('label.announcement-lowercase'),
})}
</div>
);
}
return (
<div className="announcement-container-list">
<Row gutter={[8, 8]}>
{announcements.map((item) => {
return (
<Col key={item.id} span={24}>
<Alert
className="right-panel-announcement"
description={
<>
<FeedCardHeaderV1
about={item.about}
className="d-inline"
createdBy={item.createdBy}
showUserAvatar={false}
timeStamp={item.threadTs}
/>
<FeedCardBodyV1
isOpenInDrawer
announcement={item.announcement}
className="p-t-xs"
isEditPost={false}
message={item.message}
showSchedule={false}
/>
</>
}
message={
<div className="d-flex announcement-alert-heading">
<AnnouncementIcon width={20} />
<span className="text-sm p-l-xss">
{t('label.announcement')}
</span>
</div>
}
type="info"
/>
</Col>
);
})}
</Row>
</div>
);
}, [isAnnouncementLoading, announcements]);
return ( return (
<Card className="announcement-container card-widget h-full"> <Card className="announcement-container card-widget h-full">
<Row justify="space-between"> <Row justify="space-between">
<Col> <Col>
<Typography.Paragraph className="right-panel-label m-b-sm"> <Typography.Paragraph className="font-medium m-b-sm">
{t('label.recent-announcement-plural')} {t('label.recent-announcement-plural')}
</Typography.Paragraph> </Typography.Paragraph>
</Col> </Col>
@ -61,58 +121,7 @@ function AnnouncementsWidget({
</Col> </Col>
)} )}
</Row> </Row>
{isAnnouncementLoading ? ( {announcement}
<Loader size="small" />
) : (
<div className="announcement-container-list">
{isEmpty(announcements) && (
<Typography.Text className="text-xs">
{t('message.no-entity-data-available', {
entity: t('label.announcement-lowercase'),
})}
</Typography.Text>
)}
<Row gutter={[8, 8]}>
{announcements.map((item) => {
return (
<Col key={item.id} span={24}>
<Alert
className="right-panel-announcement"
description={
<>
<FeedCardHeaderV1
about={item.about}
className="d-inline"
createdBy={item.createdBy}
showUserAvatar={false}
timeStamp={item.threadTs}
/>
<FeedCardBodyV1
isOpenInDrawer
announcement={item.announcement}
className="p-t-xs"
isEditPost={false}
message={item.message}
showSchedule={false}
/>
</>
}
message={
<div className="d-flex announcement-alert-heading">
<AnnouncementIcon width={20} />
<span className="text-sm p-l-xss">
{t('label.announcement')}
</span>
</div>
}
type="info"
/>
</Col>
);
})}
</Row>
</div>
)}
</Card> </Card>
); );
} }

View File

@ -0,0 +1,19 @@
/*
* 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.
*/
.following-widget-container {
.ant-card-body {
display: flex;
flex-direction: column;
}
}

View File

@ -21,6 +21,7 @@ import { getUserPath } from '../../../constants/constants';
import { EntityReference } from '../../../generated/entity/type'; import { EntityReference } from '../../../generated/entity/type';
import { WidgetCommonProps } from '../../../pages/CustomizablePage/CustomizablePage.interface'; import { WidgetCommonProps } from '../../../pages/CustomizablePage/CustomizablePage.interface';
import { EntityListWithV1 } from '../../Entity/EntityList/EntityList'; import { EntityListWithV1 } from '../../Entity/EntityList/EntityList';
import './FollowingWidget.less';
export interface FollowingWidgetProps extends WidgetCommonProps { export interface FollowingWidgetProps extends WidgetCommonProps {
followedData: EntityReference[]; followedData: EntityReference[];
@ -44,7 +45,9 @@ function FollowingWidget({
}, [widgetKey]); }, [widgetKey]);
return ( return (
<Card className="card-widget h-full" data-testid="following-data-container"> <Card
className="following-widget-container card-widget h-full"
data-testid="following-data-container">
<EntityListWithV1 <EntityListWithV1
entityList={followedData} entityList={followedData}
headerText={ headerText={

View File

@ -13,7 +13,7 @@
import { CloseOutlined, DragOutlined } from '@ant-design/icons'; import { CloseOutlined, DragOutlined } from '@ant-design/icons';
import { Card, Col, Row, Typography } from 'antd'; import { Card, Col, Row, Typography } from 'antd';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import { isUndefined } from 'lodash'; import { isEmpty, isUndefined } from 'lodash';
import { import {
default as React, default as React,
useCallback, useCallback,
@ -126,7 +126,18 @@ const TotalDataAssetsWidget = ({
</Col> </Col>
</Row> </Row>
)} )}
{data.length ? ( {isEmpty(data) ? (
<Row className="h-full">
<Col span={14}>
<Typography.Text className="font-medium">
{t('label.data-insight-total-entity-summary')}
</Typography.Text>
</Col>
<Col className="h-95" span={24}>
<EmptyGraphPlaceholder />
</Col>
</Row>
) : (
<Row className="h-95"> <Row className="h-95">
<Col span={isWidgetSizeLarge ? 14 : 24}> <Col span={isWidgetSizeLarge ? 14 : 24}>
<Typography.Text className="font-medium"> <Typography.Text className="font-medium">
@ -171,17 +182,6 @@ const TotalDataAssetsWidget = ({
</Col> </Col>
)} )}
</Row> </Row>
) : (
<Row>
<Col span={14}>
<Typography.Text className="font-medium">
{t('label.data-insight-total-entity-summary')}
</Typography.Text>
</Col>
<Col span={24}>
<EmptyGraphPlaceholder />
</Col>
</Row>
)} )}
</Card> </Card>
); );

View File

@ -35,3 +35,11 @@
} }
} }
} }
.recently-viewed-widget-container {
.ant-card-body {
height: 100%;
display: flex;
flex-direction: column;
}
}

View File

@ -13,7 +13,7 @@
import { CloseOutlined, DragOutlined } from '@ant-design/icons'; import { CloseOutlined, DragOutlined } from '@ant-design/icons';
import { Button, Card, Col, Row, Space, Typography } from 'antd'; import { Button, Card, Col, Row, Space, Typography } from 'antd';
import { isUndefined } from 'lodash'; import { isEmpty, isUndefined } from 'lodash';
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
@ -67,7 +67,7 @@ const RecentlyViewed = ({
return ( return (
<Card <Card
className="card-widget h-full" className="recently-viewed-widget-container card-widget"
data-testid="recently-viewed-container"> data-testid="recently-viewed-container">
<EntityListSkeleton <EntityListSkeleton
dataLength={data.length !== 0 ? data.length : 5} dataLength={data.length !== 0 ? data.length : 5}
@ -75,7 +75,7 @@ const RecentlyViewed = ({
<> <>
<Row justify="space-between"> <Row justify="space-between">
<Col> <Col>
<Typography.Paragraph className="right-panel-label m-b-sm"> <Typography.Paragraph className="font-medium m-b-sm">
{t('label.recent-views')} {t('label.recent-views')}
</Typography.Paragraph> </Typography.Paragraph>
</Col> </Col>
@ -91,45 +91,49 @@ const RecentlyViewed = ({
</Col> </Col>
)} )}
</Row> </Row>
<div className="entity-list-body"> {isEmpty(data) ? (
{data.length <div className="flex-center h-full">
? data.map((item) => { {t('message.no-recently-viewed-date')}
return ( </div>
<div ) : (
className="right-panel-list-item flex items-center justify-between" <div className="entity-list-body">
data-testid={`Recently Viewed-${getEntityName(item)}`} {data.map((item) => {
key={item.id}> return (
<div className=" flex items-center"> <div
<Link className="right-panel-list-item flex items-center justify-between"
className="" data-testid={`Recently Viewed-${getEntityName(item)}`}
to={getEntityLink( key={item.id}>
item.type || '', <div className=" flex items-center">
item.fullyQualifiedName as string <Link
)}> className=""
<Button to={getEntityLink(
className="entity-button flex-center p-0 m--ml-1" item.type || '',
icon={ item.fullyQualifiedName as string
<div className="entity-button-icon m-r-xs"> )}>
{getEntityIcon(item.type || '')} <Button
</div> className="entity-button flex-center p-0 m--ml-1"
} icon={
title={getEntityName( <div className="entity-button-icon m-r-xs">
item as unknown as EntityReference {getEntityIcon(item.type || '')}
)} </div>
type="text"> }
<Typography.Text title={getEntityName(
className="w-72 text-left text-xs" item as unknown as EntityReference
ellipsis={{ tooltip: true }}> )}
{getEntityName(item)} type="text">
</Typography.Text> <Typography.Text
</Button> className="w-72 text-left text-xs"
</Link> ellipsis={{ tooltip: true }}>
</div> {getEntityName(item)}
</Typography.Text>
</Button>
</Link>
</div> </div>
); </div>
}) );
: t('message.no-recently-viewed-date')} })}
</div> </div>
)}
</> </>
</EntityListSkeleton> </EntityListSkeleton>
</Card> </Card>

View File

@ -21,6 +21,7 @@ import 'codemirror/addon/selection/active-line';
import 'codemirror/lib/codemirror.css'; import 'codemirror/lib/codemirror.css';
import 'codemirror/mode/javascript/javascript'; import 'codemirror/mode/javascript/javascript';
import 'codemirror/mode/sql/sql'; import 'codemirror/mode/sql/sql';
import { isUndefined } from 'lodash';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Controlled as CodeMirror } from 'react-codemirror2'; import { Controlled as CodeMirror } from 'react-codemirror2';
import { JSON_TAB_SIZE } from '../../constants/constants'; import { JSON_TAB_SIZE } from '../../constants/constants';
@ -83,7 +84,9 @@ const SchemaEditor = ({
_data: EditorChange, _data: EditorChange,
value: string value: string
): void => { ): void => {
onChange && onChange(getSchemaEditorValue(value)); if (!isUndefined(onChange)) {
onChange(getSchemaEditorValue(value));
}
}; };
useEffect(() => { useEffect(() => {

View File

@ -70,6 +70,7 @@
"applied-advanced-search": "Erweiterte Suche anwenden", "applied-advanced-search": "Erweiterte Suche anwenden",
"apply": "Anwenden", "apply": "Anwenden",
"approve": "Genehmigen", "approve": "Genehmigen",
"approved": "Approved",
"april": "April", "april": "April",
"as-lowercase": "als", "as-lowercase": "als",
"asset": "Asset", "asset": "Asset",
@ -798,6 +799,7 @@
"region-name": "Region Name", "region-name": "Region Name",
"registry": "Register", "registry": "Register",
"reject": "Ablehnen", "reject": "Ablehnen",
"rejected": "Rejected",
"related-term-plural": "Verwandte Begriffe", "related-term-plural": "Verwandte Begriffe",
"relevance": "Relevanz", "relevance": "Relevanz",
"remove": "Entfernen", "remove": "Entfernen",
@ -1320,6 +1322,7 @@
"fosters-collaboration-among-producers-and-consumers": "Setzen Sie Unternehmensziele und KPIs, um die Datenkultur Ihres Unternehmens proaktiv voranzutreiben. Fördern Sie eine Kultur kontinuierlicher Verbesserung mit rechtzeitigen Berichten zur Überwachung der Datenqualität.", "fosters-collaboration-among-producers-and-consumers": "Setzen Sie Unternehmensziele und KPIs, um die Datenkultur Ihres Unternehmens proaktiv voranzutreiben. Fördern Sie eine Kultur kontinuierlicher Verbesserung mit rechtzeitigen Berichten zur Überwachung der Datenqualität.",
"get-started-with-open-metadata": "Erste Schritte mit OpenMetadata", "get-started-with-open-metadata": "Erste Schritte mit OpenMetadata",
"glossary-term-description": "Jeder Begriff im Glossar hat eine eindeutige Definition. Neben der Definition des Standardbegriffs für ein Konzept können auch Synonyme sowie verwandte Begriffe (z. B. übergeordnete und untergeordnete Begriffe) angegeben werden. Es können Referenzen zu den Assets hinzugefügt werden, die sich auf die Begriffe beziehen. Neue Begriffe können dem Glossar hinzugefügt oder aktualisiert werden. Die Glossarbegriffe können von bestimmten Benutzern überprüft werden, die die Begriffe akzeptieren oder ablehnen können.", "glossary-term-description": "Jeder Begriff im Glossar hat eine eindeutige Definition. Neben der Definition des Standardbegriffs für ein Konzept können auch Synonyme sowie verwandte Begriffe (z. B. übergeordnete und untergeordnete Begriffe) angegeben werden. Es können Referenzen zu den Assets hinzugefügt werden, die sich auf die Begriffe beziehen. Neue Begriffe können dem Glossar hinzugefügt oder aktualisiert werden. Die Glossarbegriffe können von bestimmten Benutzern überprüft werden, die die Begriffe akzeptieren oder ablehnen können.",
"glossary-term-status": "Glossary Term was {{status}}.",
"go-back-to-login-page": "Zurück zur Anmeldeseite", "go-back-to-login-page": "Zurück zur Anmeldeseite",
"group-team-type-change-message": "Der Teamtyp 'Gruppe' kann nicht geändert werden. Erstellen Sie bitte ein neues Team mit dem gewünschten Typ.", "group-team-type-change-message": "Der Teamtyp 'Gruppe' kann nicht geändert werden. Erstellen Sie bitte ein neues Team mit dem gewünschten Typ.",
"group-type-team-not-allowed-to-have-sub-team": "Teams, die als Gruppen-Typ klassifiziert sind, dürfen keine Unterteams in ihrer Struktur haben.", "group-type-team-not-allowed-to-have-sub-team": "Teams, die als Gruppen-Typ klassifiziert sind, dürfen keine Unterteams in ihrer Struktur haben.",

View File

@ -70,6 +70,7 @@
"applied-advanced-search": "Applied Advanced Search", "applied-advanced-search": "Applied Advanced Search",
"apply": "Apply", "apply": "Apply",
"approve": "Approve", "approve": "Approve",
"approved": "Approved",
"april": "April", "april": "April",
"as-lowercase": "as", "as-lowercase": "as",
"asset": "Asset", "asset": "Asset",
@ -798,6 +799,7 @@
"region-name": "Region Name", "region-name": "Region Name",
"registry": "Registry", "registry": "Registry",
"reject": "Reject", "reject": "Reject",
"rejected": "Rejected",
"related-term-plural": "Related Terms", "related-term-plural": "Related Terms",
"relevance": "Relevance", "relevance": "Relevance",
"remove": "Remove", "remove": "Remove",
@ -1320,6 +1322,7 @@
"fosters-collaboration-among-producers-and-consumers": "Set organizational goals and KPIs to proactively drive the data culture of your company. Foster a culture of continuous improvement with timely reports to monitor data health.", "fosters-collaboration-among-producers-and-consumers": "Set organizational goals and KPIs to proactively drive the data culture of your company. Foster a culture of continuous improvement with timely reports to monitor data health.",
"get-started-with-open-metadata": "Get started with OpenMetadata", "get-started-with-open-metadata": "Get started with OpenMetadata",
"glossary-term-description": "Every term in the glossary has a unique definition. Along with defining the standard term for a concept, the synonyms as well as related terms (for e.g., parent and child terms) can be specified. References can be added to the assets related to the terms. New terms can be added or updated to the Glossary. The glossary terms can be reviewed by certain users, who can accept or reject the terms.", "glossary-term-description": "Every term in the glossary has a unique definition. Along with defining the standard term for a concept, the synonyms as well as related terms (for e.g., parent and child terms) can be specified. References can be added to the assets related to the terms. New terms can be added or updated to the Glossary. The glossary terms can be reviewed by certain users, who can accept or reject the terms.",
"glossary-term-status": "Glossary Term was {{status}}.",
"go-back-to-login-page": "Go back to Login page", "go-back-to-login-page": "Go back to Login page",
"group-team-type-change-message": "The team type 'Group' cannot be changed. Please create a new team with the preferred type.", "group-team-type-change-message": "The team type 'Group' cannot be changed. Please create a new team with the preferred type.",
"group-type-team-not-allowed-to-have-sub-team": "Teams classified as Group type are not permitted to have any sub-teams within their structure.", "group-type-team-not-allowed-to-have-sub-team": "Teams classified as Group type are not permitted to have any sub-teams within their structure.",

View File

@ -70,6 +70,7 @@
"applied-advanced-search": "Búsqueda avanzada aplicada", "applied-advanced-search": "Búsqueda avanzada aplicada",
"apply": "Aplicar", "apply": "Aplicar",
"approve": "Approve", "approve": "Approve",
"approved": "Approved",
"april": "Abril", "april": "Abril",
"as-lowercase": "como", "as-lowercase": "como",
"asset": "Activo", "asset": "Activo",
@ -798,6 +799,7 @@
"region-name": "Nombre de la región", "region-name": "Nombre de la región",
"registry": "Registro", "registry": "Registro",
"reject": "Reject", "reject": "Reject",
"rejected": "Rejected",
"related-term-plural": "Términos relacionados", "related-term-plural": "Términos relacionados",
"relevance": "Relevancia", "relevance": "Relevancia",
"remove": "Eliminar", "remove": "Eliminar",
@ -1320,6 +1322,7 @@
"fosters-collaboration-among-producers-and-consumers": "Fomenta la colaboración entre los productores y consumidores de datos.", "fosters-collaboration-among-producers-and-consumers": "Fomenta la colaboración entre los productores y consumidores de datos.",
"get-started-with-open-metadata": "Empezar con OpenMetadata", "get-started-with-open-metadata": "Empezar con OpenMetadata",
"glossary-term-description": "Cada término en el glosario tiene una definición única. Además de definir el término estándar para un concepto, se pueden especificar sinónimos y términos relacionados (por ejemplo, términos padre e hijo). Se pueden agregar referencias a los activos relacionados con los términos. Se pueden agregar o actualizar nuevos términos al glosario. Los términos del glosario pueden ser revisados por ciertos usuarios, quienes pueden aceptar o rechazar los términos.", "glossary-term-description": "Cada término en el glosario tiene una definición única. Además de definir el término estándar para un concepto, se pueden especificar sinónimos y términos relacionados (por ejemplo, términos padre e hijo). Se pueden agregar referencias a los activos relacionados con los términos. Se pueden agregar o actualizar nuevos términos al glosario. Los términos del glosario pueden ser revisados por ciertos usuarios, quienes pueden aceptar o rechazar los términos.",
"glossary-term-status": "Glossary Term was {{status}}.",
"go-back-to-login-page": "Volver a la página de inicio de sesión", "go-back-to-login-page": "Volver a la página de inicio de sesión",
"group-team-type-change-message": "El tipo de equipo 'Grupo' no se puede cambiar. Por favor, cree un nuevo equipo con el tipo preferido.", "group-team-type-change-message": "El tipo de equipo 'Grupo' no se puede cambiar. Por favor, cree un nuevo equipo con el tipo preferido.",
"group-type-team-not-allowed-to-have-sub-team": "Teams classified as Group type are not permitted to have any sub-teams within their structure.", "group-type-team-not-allowed-to-have-sub-team": "Teams classified as Group type are not permitted to have any sub-teams within their structure.",

View File

@ -70,6 +70,7 @@
"applied-advanced-search": "Appliquer la recherche avancée", "applied-advanced-search": "Appliquer la recherche avancée",
"apply": "Appliquer", "apply": "Appliquer",
"approve": "Approuver", "approve": "Approuver",
"approved": "Approved",
"april": "Avril", "april": "Avril",
"as-lowercase": "en tant que", "as-lowercase": "en tant que",
"asset": "Actif", "asset": "Actif",
@ -798,6 +799,7 @@
"region-name": "Nom de Région", "region-name": "Nom de Région",
"registry": "Registre", "registry": "Registre",
"reject": "Rejeter", "reject": "Rejeter",
"rejected": "Rejected",
"related-term-plural": "Termes Liés", "related-term-plural": "Termes Liés",
"relevance": "Pertinence", "relevance": "Pertinence",
"remove": "Retirer", "remove": "Retirer",
@ -1320,6 +1322,7 @@
"fosters-collaboration-among-producers-and-consumers": "Encouragez la collaborations entre les consommateurs et producteurs de données", "fosters-collaboration-among-producers-and-consumers": "Encouragez la collaborations entre les consommateurs et producteurs de données",
"get-started-with-open-metadata": "Commencez votre Journée avec OpenMetadata", "get-started-with-open-metadata": "Commencez votre Journée avec OpenMetadata",
"glossary-term-description": "Chaque terme du glossaire a une définition unique. En plus de définir le terme standard pour un concept, les synonymes ainsi que les termes associés (par exemple, les termes parent et enfant) peuvent être spécifiés. Des références peuvent être ajoutées aux actifs liés aux termes. De nouveaux termes peuvent être ajoutés ou mis à jour dans le glossaire. Les termes du glossaire peuvent être examinés par certains utilisateurs, qui peuvent accepter ou rejeter les termes.", "glossary-term-description": "Chaque terme du glossaire a une définition unique. En plus de définir le terme standard pour un concept, les synonymes ainsi que les termes associés (par exemple, les termes parent et enfant) peuvent être spécifiés. Des références peuvent être ajoutées aux actifs liés aux termes. De nouveaux termes peuvent être ajoutés ou mis à jour dans le glossaire. Les termes du glossaire peuvent être examinés par certains utilisateurs, qui peuvent accepter ou rejeter les termes.",
"glossary-term-status": "Glossary Term was {{status}}.",
"go-back-to-login-page": "Retour à la page d'accueil", "go-back-to-login-page": "Retour à la page d'accueil",
"group-team-type-change-message": "Le type 'Group' pour l'équipe ne peut pas être changé. Merci de créer une nouvelle équipe avec le type préférentiel.", "group-team-type-change-message": "Le type 'Group' pour l'équipe ne peut pas être changé. Merci de créer une nouvelle équipe avec le type préférentiel.",
"group-type-team-not-allowed-to-have-sub-team": "Les équipes classées comme de type 'Groupe' ne sont pas autorisées à avoir des sous-équipes dans leur structure.", "group-type-team-not-allowed-to-have-sub-team": "Les équipes classées comme de type 'Groupe' ne sont pas autorisées à avoir des sous-équipes dans leur structure.",

View File

@ -70,6 +70,7 @@
"applied-advanced-search": "高度な検索を適用", "applied-advanced-search": "高度な検索を適用",
"apply": "反映", "apply": "反映",
"approve": "Approve", "approve": "Approve",
"approved": "Approved",
"april": "4月", "april": "4月",
"as-lowercase": "as", "as-lowercase": "as",
"asset": "アセット", "asset": "アセット",
@ -798,6 +799,7 @@
"region-name": "リージョン名", "region-name": "リージョン名",
"registry": "レジストリ", "registry": "レジストリ",
"reject": "Reject", "reject": "Reject",
"rejected": "Rejected",
"related-term-plural": "関連する用語", "related-term-plural": "関連する用語",
"relevance": "Relevance", "relevance": "Relevance",
"remove": "除外", "remove": "除外",
@ -1320,6 +1322,7 @@
"fosters-collaboration-among-producers-and-consumers": "Fosters collaboration among the producers and consumers of data.", "fosters-collaboration-among-producers-and-consumers": "Fosters collaboration among the producers and consumers of data.",
"get-started-with-open-metadata": "Get started with OpenMetadata", "get-started-with-open-metadata": "Get started with OpenMetadata",
"glossary-term-description": "Every term in the glossary has a unique definition. Along with defining the standard term for a concept, the synonyms as well as related terms (for e.g., parent and child terms) can be specified. References can be added to the assets related to the terms. New terms can be added or updated to the Glossary. The glossary terms can be reviewed by certain users, who can accept or reject the terms.", "glossary-term-description": "Every term in the glossary has a unique definition. Along with defining the standard term for a concept, the synonyms as well as related terms (for e.g., parent and child terms) can be specified. References can be added to the assets related to the terms. New terms can be added or updated to the Glossary. The glossary terms can be reviewed by certain users, who can accept or reject the terms.",
"glossary-term-status": "Glossary Term was {{status}}.",
"go-back-to-login-page": "ログインページに戻る", "go-back-to-login-page": "ログインページに戻る",
"group-team-type-change-message": "The team type 'Group' cannot be changed. Please create a new team with the preferred type.", "group-team-type-change-message": "The team type 'Group' cannot be changed. Please create a new team with the preferred type.",
"group-type-team-not-allowed-to-have-sub-team": "Teams classified as Group type are not permitted to have any sub-teams within their structure.", "group-type-team-not-allowed-to-have-sub-team": "Teams classified as Group type are not permitted to have any sub-teams within their structure.",

View File

@ -70,6 +70,7 @@
"applied-advanced-search": "Busca avançada aplicada", "applied-advanced-search": "Busca avançada aplicada",
"apply": "Aplicar", "apply": "Aplicar",
"approve": "Approve", "approve": "Approve",
"approved": "Approved",
"april": "Abril", "april": "Abril",
"as-lowercase": "como", "as-lowercase": "como",
"asset": "Ativo", "asset": "Ativo",
@ -798,6 +799,7 @@
"region-name": "Nome da região", "region-name": "Nome da região",
"registry": "Registro", "registry": "Registro",
"reject": "Reject", "reject": "Reject",
"rejected": "Rejected",
"related-term-plural": "Termos relacionados", "related-term-plural": "Termos relacionados",
"relevance": "Relevância", "relevance": "Relevância",
"remove": "Remover", "remove": "Remover",
@ -1320,6 +1322,7 @@
"fosters-collaboration-among-producers-and-consumers": "Promove a colaboração entre produtores e consumidores de dados.", "fosters-collaboration-among-producers-and-consumers": "Promove a colaboração entre produtores e consumidores de dados.",
"get-started-with-open-metadata": "Comece com o OpenMetadata", "get-started-with-open-metadata": "Comece com o OpenMetadata",
"glossary-term-description": "Cada termo do glossário tem uma definição única. Além de definir o termo padrão para um conceito, podem ser especificados sinônimos, bem como termos relacionados (por exemplo, termos pai e filho). Referências podem ser adicionadas aos ativos relacionados aos termos. Novos termos podem ser adicionados ou atualizados no glossário. Os termos do glossário podem ser revisados por determinados usuários, que podem aceitar ou rejeitar os termos.", "glossary-term-description": "Cada termo do glossário tem uma definição única. Além de definir o termo padrão para um conceito, podem ser especificados sinônimos, bem como termos relacionados (por exemplo, termos pai e filho). Referências podem ser adicionadas aos ativos relacionados aos termos. Novos termos podem ser adicionados ou atualizados no glossário. Os termos do glossário podem ser revisados por determinados usuários, que podem aceitar ou rejeitar os termos.",
"glossary-term-status": "Glossary Term was {{status}}.",
"go-back-to-login-page": "Voltar para a página de login", "go-back-to-login-page": "Voltar para a página de login",
"group-team-type-change-message": "O tipo de equipe 'Grupo' não pode ser alterado. Por favor, crie uma nova equipe com o tipo preferido.", "group-team-type-change-message": "O tipo de equipe 'Grupo' não pode ser alterado. Por favor, crie uma nova equipe com o tipo preferido.",
"group-type-team-not-allowed-to-have-sub-team": "Teams classified as Group type are not permitted to have any sub-teams within their structure.", "group-type-team-not-allowed-to-have-sub-team": "Teams classified as Group type are not permitted to have any sub-teams within their structure.",

View File

@ -70,6 +70,7 @@
"applied-advanced-search": "Примененный расширенный поиск", "applied-advanced-search": "Примененный расширенный поиск",
"apply": "Применить", "apply": "Применить",
"approve": "Approve", "approve": "Approve",
"approved": "Approved",
"april": "Апрель", "april": "Апрель",
"as-lowercase": "как", "as-lowercase": "как",
"asset": "Объект", "asset": "Объект",
@ -798,6 +799,7 @@
"region-name": "Наименование региона", "region-name": "Наименование региона",
"registry": "Реестр", "registry": "Реестр",
"reject": "Reject", "reject": "Reject",
"rejected": "Rejected",
"related-term-plural": "Связанные термины", "related-term-plural": "Связанные термины",
"relevance": "Актуальность", "relevance": "Актуальность",
"remove": "Удалить", "remove": "Удалить",
@ -1320,6 +1322,7 @@
"fosters-collaboration-among-producers-and-consumers": "Способствует сотрудничеству между производителями и потребителями данных.", "fosters-collaboration-among-producers-and-consumers": "Способствует сотрудничеству между производителями и потребителями данных.",
"get-started-with-open-metadata": "Начните работу с OpenMetadata", "get-started-with-open-metadata": "Начните работу с OpenMetadata",
"glossary-term-description": "Каждый термин в глоссарии имеет уникальное определение. Наряду с определением стандартного термина для понятия можно указать синонимы, а также связанные термины (например, родительские и дочерние термины). Ссылки могут быть добавлены к объектам данных, связанным с терминами. Новые термины могут быть добавлены или обновлены в Глоссарий. Термины глоссария могут быть просмотрены определенными пользователями, которые могут принять или отклонить термины.", "glossary-term-description": "Каждый термин в глоссарии имеет уникальное определение. Наряду с определением стандартного термина для понятия можно указать синонимы, а также связанные термины (например, родительские и дочерние термины). Ссылки могут быть добавлены к объектам данных, связанным с терминами. Новые термины могут быть добавлены или обновлены в Глоссарий. Термины глоссария могут быть просмотрены определенными пользователями, которые могут принять или отклонить термины.",
"glossary-term-status": "Glossary Term was {{status}}.",
"go-back-to-login-page": "Вернуться на страницу входа", "go-back-to-login-page": "Вернуться на страницу входа",
"group-team-type-change-message": "Тип команды «Группа» изменить нельзя. Пожалуйста, создайте новую команду с предпочтительным типом.", "group-team-type-change-message": "Тип команды «Группа» изменить нельзя. Пожалуйста, создайте новую команду с предпочтительным типом.",
"group-type-team-not-allowed-to-have-sub-team": "Командам, относящимся к групповому типу, не разрешается иметь какие-либо подкоманды в своей структуре.", "group-type-team-not-allowed-to-have-sub-team": "Командам, относящимся к групповому типу, не разрешается иметь какие-либо подкоманды в своей структуре.",

View File

@ -70,6 +70,7 @@
"applied-advanced-search": "应用高级搜索", "applied-advanced-search": "应用高级搜索",
"apply": "应用", "apply": "应用",
"approve": "Approve", "approve": "Approve",
"approved": "Approved",
"april": "四月", "april": "四月",
"as-lowercase": "作为", "as-lowercase": "作为",
"asset": "资产", "asset": "资产",
@ -798,6 +799,7 @@
"region-name": "区域名称", "region-name": "区域名称",
"registry": "仓库", "registry": "仓库",
"reject": "Reject", "reject": "Reject",
"rejected": "Rejected",
"related-term-plural": "关联术语", "related-term-plural": "关联术语",
"relevance": "相关性", "relevance": "相关性",
"remove": "删除", "remove": "删除",
@ -1320,6 +1322,7 @@
"fosters-collaboration-among-producers-and-consumers": "促进数据生产者和使用者之间的合作", "fosters-collaboration-among-producers-and-consumers": "促进数据生产者和使用者之间的合作",
"get-started-with-open-metadata": "开始使用 OpenMetadata", "get-started-with-open-metadata": "开始使用 OpenMetadata",
"glossary-term-description": "术语库中的每个术语都有一个唯一的定义。除了为概念定义标准术语之外,还可以指定同义词以及相关术语(例如,父项和子项)。可以向与术语相关的资产添加引用。可以向术语库添加或更新新术语。某些用户可以审查术语,并接受或拒绝这些术语。", "glossary-term-description": "术语库中的每个术语都有一个唯一的定义。除了为概念定义标准术语之外,还可以指定同义词以及相关术语(例如,父项和子项)。可以向与术语相关的资产添加引用。可以向术语库添加或更新新术语。某些用户可以审查术语,并接受或拒绝这些术语。",
"glossary-term-status": "Glossary Term was {{status}}.",
"go-back-to-login-page": "返回登录页面", "go-back-to-login-page": "返回登录页面",
"group-team-type-change-message": "团队类型“组”无法更改,请创建一个具有所需类型的新团队", "group-team-type-change-message": "团队类型“组”无法更改,请创建一个具有所需类型的新团队",
"group-type-team-not-allowed-to-have-sub-team": "被设为“组”的团队类型,无法再拥有子团队", "group-type-team-not-allowed-to-have-sub-team": "被设为“组”的团队类型,无法再拥有子团队",

View File

@ -15,7 +15,13 @@ import { Button, Col, Row, Tabs } from 'antd';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import { compare } from 'fast-json-patch'; import { compare } from 'fast-json-patch';
import { isUndefined } from 'lodash'; import { isUndefined } from 'lodash';
import { default as React, useEffect, useMemo, useState } from 'react'; import {
default as React,
useCallback,
useEffect,
useMemo,
useState,
} from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useHistory, useParams } from 'react-router-dom'; import { useHistory, useParams } from 'react-router-dom';
import ErrorPlaceHolder from '../../components/common/error-with-placeholder/ErrorPlaceHolder'; import ErrorPlaceHolder from '../../components/common/error-with-placeholder/ErrorPlaceHolder';
@ -55,7 +61,7 @@ const CustomEntityDetailV1 = () => {
const [isButtonLoading, setIsButtonLoading] = useState<boolean>(false); const [isButtonLoading, setIsButtonLoading] = useState<boolean>(false);
const tabAttributePath = ENTITY_PATH[tab]; const tabAttributePath = useMemo(() => ENTITY_PATH[tab], [tab]);
const { getEntityPermission } = usePermissionProvider(); const { getEntityPermission } = usePermissionProvider();
@ -91,34 +97,37 @@ const CustomEntityDetailV1 = () => {
setIsLoading(false); setIsLoading(false);
}; };
const onTabChange = (activeKey: string) => { const onTabChange = useCallback((activeKey: string) => {
setActiveTab(activeKey as EntityTabs); setActiveTab(activeKey as EntityTabs);
}; }, []);
const handleAddProperty = () => { const handleAddProperty = useCallback(() => {
const path = getAddCustomPropertyPath(tabAttributePath); const path = getAddCustomPropertyPath(tabAttributePath);
history.push(path); history.push(path);
}; }, [tabAttributePath, history]);
const updateEntityType = async (properties: Type['customProperties']) => { const updateEntityType = useCallback(
setIsButtonLoading(true); async (properties: Type['customProperties']) => {
const patch = compare(selectedEntityTypeDetail, { setIsButtonLoading(true);
...selectedEntityTypeDetail, const patch = compare(selectedEntityTypeDetail, {
customProperties: properties, ...selectedEntityTypeDetail,
}); customProperties: properties,
});
try { try {
const data = await updateType(selectedEntityTypeDetail.id || '', patch); const data = await updateType(selectedEntityTypeDetail.id ?? '', patch);
setSelectedEntityTypeDetail((prev) => ({ setSelectedEntityTypeDetail((prev) => ({
...prev, ...prev,
customProperties: data.customProperties, customProperties: data.customProperties,
})); }));
} catch (error) { } catch (error) {
showErrorToast(error as AxiosError); showErrorToast(error as AxiosError);
} finally { } finally {
setIsButtonLoading(false); setIsButtonLoading(false);
} }
}; },
[selectedEntityTypeDetail.id]
);
const customPageHeader = useMemo(() => { const customPageHeader = useMemo(() => {
switch (tabAttributePath) { switch (tabAttributePath) {
@ -166,7 +175,7 @@ const CustomEntityDetailV1 = () => {
setIsError(false); setIsError(false);
fetchTypeDetail(tabAttributePath); fetchTypeDetail(tabAttributePath);
} }
}, [tab]); }, [tabAttributePath]);
useEffect(() => { useEffect(() => {
if (selectedEntityTypeDetail?.id) { if (selectedEntityTypeDetail?.id) {
@ -175,13 +184,13 @@ const CustomEntityDetailV1 = () => {
}, [selectedEntityTypeDetail]); }, [selectedEntityTypeDetail]);
const tabs = useMemo(() => { const tabs = useMemo(() => {
const { customProperties } = selectedEntityTypeDetail; const { customProperties, schema } = selectedEntityTypeDetail;
return [ return [
{ {
label: ( label: (
<TabsLabel <TabsLabel
count={(customProperties || []).length} count={(customProperties ?? []).length}
id={EntityTabs.CUSTOM_PROPERTIES} id={EntityTabs.CUSTOM_PROPERTIES}
isActive={activeTab === EntityTabs.CUSTOM_PROPERTIES} isActive={activeTab === EntityTabs.CUSTOM_PROPERTIES}
name={t('label.custom-property-plural')} name={t('label.custom-property-plural')}
@ -205,7 +214,7 @@ const CustomEntityDetailV1 = () => {
)} )}
</div> </div>
<CustomPropertyTable <CustomPropertyTable
customProperties={selectedEntityTypeDetail.customProperties ?? []} customProperties={customProperties ?? []}
hasAccess={editPermission} hasAccess={editPermission}
isButtonLoading={isButtonLoading} isButtonLoading={isButtonLoading}
isLoading={isLoading} isLoading={isLoading}
@ -222,18 +231,21 @@ const CustomEntityDetailV1 = () => {
<SchemaEditor <SchemaEditor
className="custom-properties-schemaEditor p-y-md" className="custom-properties-schemaEditor p-y-md"
editorClass="custom-entity-schema" editorClass="custom-entity-schema"
value={JSON.parse(selectedEntityTypeDetail.schema ?? '{}')} value={JSON.parse(schema ?? '{}')}
/> />
</div> </div>
), ),
}, },
]; ];
}, [ }, [
selectedEntityTypeDetail, selectedEntityTypeDetail.schema,
editPermission, editPermission,
isButtonLoading, isButtonLoading,
customPageHeader, customPageHeader,
isLoading, isLoading,
activeTab,
handleAddProperty,
updateEntityType,
]); ]);
if (isError) { if (isError) {
@ -249,7 +261,7 @@ const CustomEntityDetailV1 = () => {
<PageHeader data={customPageHeader} /> <PageHeader data={customPageHeader} />
</Col> </Col>
<Col className="global-settings-tabs" span={24}> <Col className="global-settings-tabs" span={24}>
<Tabs activeKey={activeTab} items={tabs} onChange={onTabChange} /> <Tabs items={tabs} key={tab} onChange={onTabChange} />
</Col> </Col>
</Row> </Row>
); );

View File

@ -39,4 +39,7 @@
box-shadow: none; box-shadow: none;
border-radius: 10px; border-radius: 10px;
height: 100%; height: 100%;
.ant-card-body {
height: 100%;
}
} }

View File

@ -95,9 +95,9 @@ export const getDiffByFieldName = (
changeDescription: ChangeDescription, changeDescription: ChangeDescription,
exactMatch?: boolean exactMatch?: boolean
): EntityDiffProps => { ): EntityDiffProps => {
const fieldsAdded = changeDescription?.fieldsAdded || []; const fieldsAdded = changeDescription?.fieldsAdded ?? [];
const fieldsDeleted = changeDescription?.fieldsDeleted || []; const fieldsDeleted = changeDescription?.fieldsDeleted ?? [];
const fieldsUpdated = changeDescription?.fieldsUpdated || []; const fieldsUpdated = changeDescription?.fieldsUpdated ?? [];
if (exactMatch) { if (exactMatch) {
return { return {
added: fieldsAdded.find((ch) => ch.name === name), added: fieldsAdded.find((ch) => ch.name === name),
@ -168,7 +168,7 @@ export const getTextDiff = (
latestText?: string latestText?: string
) => { ) => {
if (isEmpty(oldText) && isEmpty(newText)) { if (isEmpty(oldText) && isEmpty(newText)) {
return latestText || ''; return latestText ?? '';
} }
const diffArr = diffWords(toString(oldText), toString(newText)); const diffArr = diffWords(toString(oldText), toString(newText));
@ -276,38 +276,87 @@ export const summaryFormatter = (fieldChange: FieldChange) => {
} }
}; };
const getSummaryText = ( const getGlossaryTermApprovalText = (fieldsChanged: FieldChange[]) => {
isPrefix: boolean, const statusFieldDiff = fieldsChanged.find(
fieldsChanged: FieldChange[], (field) => field.name === 'status'
actionType: string, );
actionText: string let approvalText = '';
) => {
const prefix = isPrefix ? `+ ${actionType}` : '';
return `${prefix} ${fieldsChanged.map(summaryFormatter).join(', ')} ${ if (statusFieldDiff) {
!isPrefix approvalText = t('message.glossary-term-status', {
? t('label.has-been-action-type-lowercase', { status:
actionType: actionText, statusFieldDiff.newValue === 'Approved'
}) ? t('label.approved')
: '' : t('label.rejected'),
} `; });
}
return approvalText;
}; };
export const getSummary = ( const getSummaryText = ({
changeDescription: ChangeDescription, isPrefix,
isPrefix = false fieldsChanged,
) => { actionType,
const fieldsAdded = [...(changeDescription?.fieldsAdded || [])]; actionText,
const fieldsDeleted = [...(changeDescription?.fieldsDeleted || [])]; isGlossaryTerm,
}: {
isPrefix: boolean;
fieldsChanged: FieldChange[];
actionType: string;
actionText: string;
isGlossaryTerm?: boolean;
}) => {
const prefix = isPrefix ? `+ ${actionType}` : '';
const filteredFieldsChanged = isGlossaryTerm
? fieldsChanged.filter((field) => field.name !== 'status')
: fieldsChanged;
let summaryText = '';
if (!isEmpty(filteredFieldsChanged)) {
summaryText = `${prefix} ${filteredFieldsChanged
.map(summaryFormatter)
.join(', ')} ${
!isPrefix
? t('label.has-been-action-type-lowercase', {
actionType: actionText,
})
: ''
} `;
}
const isGlossaryTermStatusUpdated = fieldsChanged.some(
(field) => field.name === 'status'
);
const glossaryTermApprovalText = isGlossaryTermStatusUpdated
? getGlossaryTermApprovalText(fieldsChanged)
: '';
return `${glossaryTermApprovalText} ${summaryText}`;
};
export const getSummary = ({
changeDescription,
isPrefix = false,
isGlossaryTerm = false,
}: {
changeDescription: ChangeDescription;
isPrefix?: boolean;
isGlossaryTerm?: boolean;
}) => {
const fieldsAdded = [...(changeDescription?.fieldsAdded ?? [])];
const fieldsDeleted = [...(changeDescription?.fieldsDeleted ?? [])];
const fieldsUpdated = [ const fieldsUpdated = [
...(changeDescription?.fieldsUpdated?.filter( ...(changeDescription?.fieldsUpdated?.filter(
(field) => field.name !== 'deleted' (field) => field.name !== 'deleted'
) || []), ) ?? []),
]; ];
const isDeleteUpdated = [ const isDeleteUpdated = [
...(changeDescription?.fieldsUpdated?.filter( ...(changeDescription?.fieldsUpdated?.filter(
(field) => field.name === 'deleted' (field) => field.name === 'deleted'
) || []), ) ?? []),
]; ];
return ( return (
@ -329,32 +378,33 @@ export const getSummary = (
) : null} ) : null}
{fieldsAdded?.length > 0 ? ( {fieldsAdded?.length > 0 ? (
<Typography.Paragraph> <Typography.Paragraph>
{getSummaryText( {getSummaryText({
isPrefix, isPrefix,
fieldsAdded, fieldsChanged: fieldsAdded,
t('label.added'), actionType: t('label.added'),
t('label.added-lowercase') actionText: t('label.added-lowercase'),
)} })}
</Typography.Paragraph> </Typography.Paragraph>
) : null} ) : null}
{fieldsUpdated?.length ? ( {fieldsUpdated?.length ? (
<Typography.Paragraph> <Typography.Paragraph>
{getSummaryText( {getSummaryText({
isPrefix, isPrefix,
fieldsUpdated, fieldsChanged: fieldsUpdated,
t('label.edited'), actionType: t('label.edited'),
t('label.updated-lowercase') actionText: t('label.updated-lowercase'),
)} isGlossaryTerm,
})}
</Typography.Paragraph> </Typography.Paragraph>
) : null} ) : null}
{fieldsDeleted?.length ? ( {fieldsDeleted?.length ? (
<Typography.Paragraph> <Typography.Paragraph>
{getSummaryText( {getSummaryText({
isPrefix, isPrefix,
fieldsDeleted, fieldsChanged: fieldsDeleted,
t('label.removed'), actionType: t('label.removed'),
t('label.deleted-lowercase') actionText: t('label.deleted-lowercase'),
)} })}
</Typography.Paragraph> </Typography.Paragraph>
) : null} ) : null}
</Fragment> </Fragment>
@ -643,9 +693,9 @@ export const getAllDiffByFieldName = (
changeDescription: ChangeDescription, changeDescription: ChangeDescription,
exactMatch?: boolean exactMatch?: boolean
): EntityDiffWithMultiChanges => { ): EntityDiffWithMultiChanges => {
const fieldsAdded = changeDescription?.fieldsAdded || []; const fieldsAdded = changeDescription?.fieldsAdded ?? [];
const fieldsDeleted = changeDescription?.fieldsDeleted || []; const fieldsDeleted = changeDescription?.fieldsDeleted ?? [];
const fieldsUpdated = changeDescription?.fieldsUpdated || []; const fieldsUpdated = changeDescription?.fieldsUpdated ?? [];
if (exactMatch) { if (exactMatch) {
return { return {
added: fieldsAdded.filter((ch) => ch.name === name), added: fieldsAdded.filter((ch) => ch.name === name),