UI : Add localization (#9309)

* UI : Add localization

* address comments
This commit is contained in:
Sachin Chaurasiya 2022-12-15 19:24:47 +05:30 committed by GitHub
parent d2deb38766
commit c562be6f78
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 418 additions and 630 deletions

View File

@ -301,10 +301,6 @@ jest.mock(
) )
); );
jest.mock('../../utils/EntityVersionUtils', () => ({
getFeedSummary: jest.fn().mockImplementation(() => <p>EntityVersionUtils</p>),
}));
jest.mock('../../utils/ServiceUtils', () => ({ jest.mock('../../utils/ServiceUtils', () => ({
getAllServices: jest getAllServices: jest
.fn() .fn()

View File

@ -286,7 +286,7 @@ const TeamDetailsV1 = ({
disabled={!entityPermissions.EditAll} disabled={!entityPermissions.EditAll}
icon={ icon={
<SVGIcons <SVGIcons
alt="Remove" alt={t('label.remove')}
className="tw-w-4 tw-mb-2.5" className="tw-w-4 tw-mb-2.5"
icon={Icons.ICON_REMOVE} icon={Icons.ICON_REMOVE}
/> />
@ -554,7 +554,7 @@ const TeamDetailsV1 = ({
showErrorToast( showErrorToast(
error as AxiosError, error as AxiosError,
t('server.entity-fetch-error', { t('server.entity-fetch-error', {
entity: 'User Permissions', entity: t('label.user-permissions'),
}) })
); );
} finally { } finally {
@ -639,7 +639,9 @@ const TeamDetailsV1 = ({
); );
const restoreIcon = useMemo( const restoreIcon = useMemo(
() => <SVGIcons alt="Restore" icon={Icons.RESTORE} width="16px" />, () => (
<SVGIcons alt={t('label.restore')} icon={Icons.RESTORE} width="16px" />
),
[currentTeam.isJoinable] [currentTeam.isJoinable]
); );
@ -1366,7 +1368,7 @@ const TeamDetailsV1 = ({
okText={t('label.confirm')} okText={t('label.confirm')}
title={`${t('label.remove-entity', { title={`${t('label.remove-entity', {
entity: getEntityName(selectedEntity?.record), entity: getEntityName(selectedEntity?.record),
})} ${t('label.from')} ${getEntityName(currentTeam)}`} })} ${t('label.from-lowercase')} ${getEntityName(currentTeam)}`}
visible={!isUndefined(selectedEntity.record)} visible={!isUndefined(selectedEntity.record)}
onCancel={() => setEntity(undefined)} onCancel={() => setEntity(undefined)}
onOk={async () => { onOk={async () => {
@ -1380,7 +1382,7 @@ const TeamDetailsV1 = ({
{t('label.sure-to-remove')}{' '} {t('label.sure-to-remove')}{' '}
{`${getEntityName( {`${getEntityName(
selectedEntity.record selectedEntity.record
)} t('label.from') ${getEntityName(currentTeam)}?`} )} t('label.from-lowercase') ${getEntityName(currentTeam)}?`}
</Typography.Text> </Typography.Text>
</Modal> </Modal>
)} )}

View File

@ -81,7 +81,7 @@
"sure-to-remove": "Are you sure you want to remove the", "sure-to-remove": "Are you sure you want to remove the",
"confirm": "Confirm", "confirm": "Confirm",
"remove-entity": "Remove {{entity}}", "remove-entity": "Remove {{entity}}",
"from": "from", "from-lowercase": "from",
"add": "Add", "add": "Add",
"add-policy": "Add Policy", "add-policy": "Add Policy",
"go-back": "Go Back", "go-back": "Go Back",
@ -473,6 +473,25 @@
"retention-size": "retention-size", "retention-size": "retention-size",
"clean-up-policies": "clean-up policies", "clean-up-policies": "clean-up policies",
"maximum-size": "maximum size", "maximum-size": "maximum size",
"add-term": "Add Term",
"accept-suggestion": "Accept Suggestion",
"edit-amp-accept-suggestion": "Edit & Accept Suggestion",
"to-lowercase": "to",
"of-lowercase": "of",
"description-lowercase": "description",
"started-following": "Started following",
"unfollowed": "Unfollowed",
"data-asset-has-been-action-type": "Data asset has been {{actionType}}",
"deleted-lowercase": "deleted",
"restored-lowercase": "restored",
"edited": "Edited",
"added-lowercase": "added",
"updated-lowercase": "updated",
"has-been-action-type-lowercase": "has been {{actionType}}",
"rules": "Rules",
"manage-rule": "Manage Rule",
"users": "Users",
"user-permissions": "User Permissions",
"openmetadata": "OpenMetadata", "openmetadata": "OpenMetadata",
"activity-feeds": "Activity Feeds", "activity-feeds": "Activity Feeds",
"search": "Search", "search": "Search",
@ -504,13 +523,20 @@
"search-entity": "Search {{entity}}", "search-entity": "Search {{entity}}",
"more": "More", "more": "More",
"update": "Update", "update": "Update",
"reply-in-conversation": "Reply in conversation",
"older-reply-lowercase": "older reply",
"older-replies-lowercase": "older replies",
"created-a-task-lowercase": "created a task",
"posted-on-lowercase": "posted on",
"announcement": "Announcement",
"conversation": "Conversation",
"url-lowercase": "url",
"schemas": "Schemas",
"deleted": "Deleted {{entity}}", "deleted": "Deleted {{entity}}",
"test-plural": "Tests", "test-plural": "Tests",
"test": "Test", "test": "Test",
"profiler": "Profiler",
"here-lowercase": "here", "here-lowercase": "here",
"insert": "Insert", "insert": "Insert",
"update": "Update",
"table-profile": "Table Profile", "table-profile": "Table Profile",
"column-profile": "Column Profile", "column-profile": "Column Profile",
"applied-advanced-search": "Applied advanced search" "applied-advanced-search": "Applied advanced search"

View File

@ -81,7 +81,7 @@
"sure-to-remove": "Etes-vous sûr de vouloir supprimer le", "sure-to-remove": "Etes-vous sûr de vouloir supprimer le",
"confirm": "Confirmer", "confirm": "Confirmer",
"remove-entity": "Supprimer {{entity}}", "remove-entity": "Supprimer {{entity}}",
"from": "de", "from-lowercase": "de",
"add": "Ajouter", "add": "Ajouter",
"add-policy": "Ajouter une Police", "add-policy": "Ajouter une Police",
"go-back": "Retour en arrière", "go-back": "Retour en arrière",

View File

@ -108,6 +108,12 @@ jest.mock('../../../utils/ToastUtils', () => ({
showErrorToast: jest.fn().mockReturnValue(''), showErrorToast: jest.fn().mockReturnValue(''),
})); }));
jest.mock('react-i18next', () => ({
useTranslation: jest.fn().mockReturnValue({
t: (label: string) => label,
}),
}));
describe('Test Policy details page', () => { describe('Test Policy details page', () => {
it('Should render the policy details page component', async () => { it('Should render the policy details page component', async () => {
render(<PoliciesDetailPage />); render(<PoliciesDetailPage />);
@ -118,11 +124,11 @@ describe('Test Policy details page', () => {
const description = await screen.findByTestId('description-data'); const description = await screen.findByTestId('description-data');
const rulesTab = await screen.findByText('Rules'); const rulesTab = await screen.findByText('label.rules');
const rolesTab = await screen.findByText('Roles'); const rolesTab = await screen.findByText('label.roles');
const teamsTab = await screen.findByText('Teams'); const teamsTab = await screen.findByText('label.teams');
expect(container).toBeInTheDocument(); expect(container).toBeInTheDocument();

View File

@ -21,18 +21,16 @@ import {
Modal, Modal,
Row, Row,
Space, Space,
Table,
Tabs, Tabs,
Tooltip, Tooltip,
Typography, Typography,
} from 'antd'; } from 'antd';
import { ColumnsType } from 'antd/lib/table';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import { compare } from 'fast-json-patch'; import { compare } from 'fast-json-patch';
import { isEmpty, isUndefined, startCase } from 'lodash'; import { isEmpty, isUndefined, startCase } from 'lodash';
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Link, useHistory, useParams } from 'react-router-dom'; import { useHistory, useParams } from 'react-router-dom';
import { import {
getPolicyByName, getPolicyByName,
getRoleByName, getRoleByName,
@ -54,10 +52,6 @@ import {
GlobalSettingOptions, GlobalSettingOptions,
GlobalSettingsMenuCategory, GlobalSettingsMenuCategory,
} from '../../../constants/GlobalSettings.constants'; } from '../../../constants/GlobalSettings.constants';
import {
NO_PERMISSION_FOR_ACTION,
NO_PERMISSION_TO_VIEW,
} from '../../../constants/HelperTextUtil';
import { EntityType } from '../../../enums/entity.enum'; import { EntityType } from '../../../enums/entity.enum';
import { Rule } from '../../../generated/api/policies/createPolicy'; import { Rule } from '../../../generated/api/policies/createPolicy';
import { Policy } from '../../../generated/entity/policies/policy'; import { Policy } from '../../../generated/entity/policies/policy';
@ -67,105 +61,17 @@ import { DEFAULT_ENTITY_PERMISSION } from '../../../utils/PermissionsUtils';
import { import {
getAddPolicyRulePath, getAddPolicyRulePath,
getEditPolicyRulePath, getEditPolicyRulePath,
getRoleWithFqnPath,
getSettingPath, getSettingPath,
getTeamsWithFqnPath,
} from '../../../utils/RouterUtils'; } from '../../../utils/RouterUtils';
import SVGIcons, { Icons } from '../../../utils/SvgUtils'; import SVGIcons, { Icons } from '../../../utils/SvgUtils';
import { showErrorToast } from '../../../utils/ToastUtils'; import { showErrorToast } from '../../../utils/ToastUtils';
import './PoliciesDetail.less'; import './PoliciesDetail.less';
import PoliciesDetailsList from './PoliciesDetailsList.component';
const { TabPane } = Tabs; const { TabPane } = Tabs;
type Attribute = 'roles' | 'teams'; type Attribute = 'roles' | 'teams';
const List = ({
list,
type,
onDelete,
hasAccess,
}: {
list: EntityReference[];
type: 'role' | 'team';
onDelete: (record: EntityReference) => void;
hasAccess: boolean;
}) => {
const columns: ColumnsType<EntityReference> = useMemo(() => {
return [
{
title: 'Name',
dataIndex: 'name',
width: '200px',
key: 'name',
render: (_, record) => {
let link = '';
switch (type) {
case 'role':
link = getRoleWithFqnPath(record.fullyQualifiedName || '');
break;
case 'team':
link = getTeamsWithFqnPath(record.fullyQualifiedName || '');
break;
default:
break;
}
return (
<Link className="hover:tw-underline tw-cursor-pointer" to={link}>
{getEntityName(record)}
</Link>
);
},
},
{
title: 'Description',
dataIndex: 'description',
key: 'description',
render: (_, record) => (
<RichTextEditorPreviewer markdown={record?.description || ''} />
),
},
{
title: 'Actions',
dataIndex: 'actions',
width: '80px',
key: 'actions',
render: (_, record) => {
return (
<Tooltip title={hasAccess ? 'Remove' : NO_PERMISSION_FOR_ACTION}>
<Button
data-testid={`remove-action-${getEntityName(record)}`}
disabled={!hasAccess}
type="text"
onClick={() => onDelete(record)}>
<SVGIcons
alt="remove"
icon={Icons.ICON_REMOVE}
title="Remove"
/>
</Button>
</Tooltip>
);
},
},
];
}, []);
return (
<Table
bordered
className="list-table"
columns={columns}
dataSource={list}
pagination={false}
size="small"
/>
);
};
const PoliciesDetailPage = () => { const PoliciesDetailPage = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const history = useHistory(); const history = useHistory();
@ -191,7 +97,7 @@ const PoliciesDetailPage = () => {
const breadcrumb = useMemo( const breadcrumb = useMemo(
() => [ () => [
{ {
name: 'Policies', name: t('label.policies'),
url: policiesPath, url: policiesPath,
}, },
{ {
@ -402,8 +308,8 @@ const PoliciesDetailPage = () => {
<Tooltip <Tooltip
title={ title={
policyPermission.EditAll policyPermission.EditAll
? 'Manage Rule' ? t('label.manage-rule')
: NO_PERMISSION_FOR_ACTION : t('message.no-permission-for-action')
}> }>
<Button <Button
data-testid={`manage-button-${rule.name}`} data-testid={`manage-button-${rule.name}`}
@ -473,7 +379,7 @@ const PoliciesDetailPage = () => {
/> />
<Tabs defaultActiveKey="rules"> <Tabs defaultActiveKey="rules">
<TabPane key="rules" tab="Rules"> <TabPane key="rules" tab={t('label.rules')}>
{isEmpty(policy.rules) ? ( {isEmpty(policy.rules) ? (
<ErrorPlaceHolder> <ErrorPlaceHolder>
<p>{t('label.no-rule-found')}</p> <p>{t('label.no-rule-found')}</p>
@ -486,7 +392,7 @@ const PoliciesDetailPage = () => {
title={ title={
policyPermission.EditAll policyPermission.EditAll
? t('label.add-rule') ? t('label.add-rule')
: NO_PERMISSION_FOR_ACTION : t('message.no-permission-for-action')
}> }>
<Button <Button
data-testid="add-rule" data-testid="add-rule"
@ -596,8 +502,8 @@ const PoliciesDetailPage = () => {
</Space> </Space>
)} )}
</TabPane> </TabPane>
<TabPane key="roles" tab="Roles"> <TabPane key="roles" tab={t('label.roles')}>
<List <PoliciesDetailsList
hasAccess={policyPermission.EditAll} hasAccess={policyPermission.EditAll}
list={policy.roles ?? []} list={policy.roles ?? []}
type="role" type="role"
@ -606,8 +512,8 @@ const PoliciesDetailPage = () => {
} }
/> />
</TabPane> </TabPane>
<TabPane key="teams" tab="Teams"> <TabPane key="teams" tab={t('label.teams')}>
<List <PoliciesDetailsList
hasAccess={policyPermission.EditAll} hasAccess={policyPermission.EditAll}
list={policy.teams ?? []} list={policy.teams ?? []}
type="team" type="team"
@ -621,7 +527,9 @@ const PoliciesDetailPage = () => {
)} )}
</> </>
) : ( ) : (
<ErrorPlaceHolder>{NO_PERMISSION_TO_VIEW}</ErrorPlaceHolder> <ErrorPlaceHolder>
{t('message.no-permission-to-view')}
</ErrorPlaceHolder>
)} )}
{selectedEntity && ( {selectedEntity && (
<Modal <Modal
@ -631,7 +539,7 @@ const PoliciesDetailPage = () => {
okText={t('label.confirm')} okText={t('label.confirm')}
title={`${t('label.remove-entity', { title={`${t('label.remove-entity', {
entity: getEntityName(selectedEntity.record), entity: getEntityName(selectedEntity.record),
})} ${t('label.from')} ${getEntityName(policy)}`} })} ${t('label.from-lowercase')} ${getEntityName(policy)}`}
visible={!isUndefined(selectedEntity.record)} visible={!isUndefined(selectedEntity.record)}
onCancel={() => setEntity(undefined)} onCancel={() => setEntity(undefined)}
onOk={async () => { onOk={async () => {
@ -641,7 +549,7 @@ const PoliciesDetailPage = () => {
<Typography.Text> <Typography.Text>
{` ${t('label.sure-to-remove')} ${getEntityName( {` ${t('label.sure-to-remove')} ${getEntityName(
selectedEntity.record selectedEntity.record
)} ${t('label.from')} ${getEntityName(policy)}?`} )} ${t('label.from-lowercase')} ${getEntityName(policy)}?`}
</Typography.Text> </Typography.Text>
</Modal> </Modal>
)} )}

View File

@ -0,0 +1,121 @@
/*
* 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 { Button, Tooltip } from 'antd';
import Table, { ColumnsType } from 'antd/lib/table';
import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import RichTextEditorPreviewer from '../../../components/common/rich-text-editor/RichTextEditorPreviewer';
import { EntityReference } from '../../../generated/type/entityReference';
import { getEntityName } from '../../../utils/CommonUtils';
import {
getRoleWithFqnPath,
getTeamsWithFqnPath,
} from '../../../utils/RouterUtils';
import SVGIcons, { Icons } from '../../../utils/SvgUtils';
const PoliciesDetailsList = ({
list,
type,
onDelete,
hasAccess,
}: {
list: EntityReference[];
type: 'role' | 'team';
onDelete: (record: EntityReference) => void;
hasAccess: boolean;
}) => {
const { t } = useTranslation();
const columns: ColumnsType<EntityReference> = useMemo(() => {
return [
{
title: t('label.name'),
dataIndex: 'name',
width: '200px',
key: 'name',
render: (_, record) => {
let link = '';
switch (type) {
case 'role':
link = getRoleWithFqnPath(record.fullyQualifiedName || '');
break;
case 'team':
link = getTeamsWithFqnPath(record.fullyQualifiedName || '');
break;
default:
break;
}
return (
<Link className="hover:tw-underline tw-cursor-pointer" to={link}>
{getEntityName(record)}
</Link>
);
},
},
{
title: t('label.description'),
dataIndex: 'description',
key: 'description',
render: (_, record) => (
<RichTextEditorPreviewer markdown={record?.description || ''} />
),
},
{
title: t('label.actions'),
dataIndex: 'actions',
width: '80px',
key: 'actions',
render: (_, record) => {
return (
<Tooltip
title={
hasAccess
? t('label.remove')
: t('message.no-permission-for-action')
}>
<Button
data-testid={`remove-action-${getEntityName(record)}`}
disabled={!hasAccess}
type="text"
onClick={() => onDelete(record)}>
<SVGIcons
alt="remove"
icon={Icons.ICON_REMOVE}
title={t('label.remove')}
/>
</Button>
</Tooltip>
);
},
},
];
}, []);
return (
<Table
bordered
className="list-table"
columns={columns}
dataSource={list}
pagination={false}
size="small"
/>
);
};
export default PoliciesDetailsList;

View File

@ -88,9 +88,9 @@ describe('Test Roles Details Page', () => {
const tabs = await screen.findByTestId('tabs'); const tabs = await screen.findByTestId('tabs');
const policiesTab = await screen.findByText('Policies'); const policiesTab = await screen.findByText('label.policies');
const teamsTab = await screen.findByText('Teams'); const teamsTab = await screen.findByText('label.teams');
const usersTab = await screen.findByText('Users'); const usersTab = await screen.findByText('label.users');
expect(container).toBeInTheDocument(); expect(container).toBeInTheDocument();

View File

@ -11,20 +11,18 @@
* limitations under the License. * limitations under the License.
*/ */
import { Button, Modal, Space, Table, Tabs, Tooltip, Typography } from 'antd'; import { Button, Modal, Space, Tabs, Tooltip, Typography } from 'antd';
import { ColumnsType } from 'antd/lib/table';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import { compare } from 'fast-json-patch'; import { compare } from 'fast-json-patch';
import { isEmpty, isUndefined } from 'lodash'; import { isEmpty, isUndefined } 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 { Link, useHistory, useParams } from 'react-router-dom'; import { useHistory, useParams } from 'react-router-dom';
import { getRoleByName, patchRole } from '../../../axiosAPIs/rolesAPIV1'; import { getRoleByName, patchRole } from '../../../axiosAPIs/rolesAPIV1';
import { getTeamByName, patchTeamDetail } from '../../../axiosAPIs/teamsAPI'; import { getTeamByName, patchTeamDetail } from '../../../axiosAPIs/teamsAPI';
import { getUserByName, updateUserDetail } from '../../../axiosAPIs/userAPI'; import { getUserByName, updateUserDetail } from '../../../axiosAPIs/userAPI';
import Description from '../../../components/common/description/Description'; import Description from '../../../components/common/description/Description';
import ErrorPlaceHolder from '../../../components/common/error-with-placeholder/ErrorPlaceHolder'; import ErrorPlaceHolder from '../../../components/common/error-with-placeholder/ErrorPlaceHolder';
import RichTextEditorPreviewer from '../../../components/common/rich-text-editor/RichTextEditorPreviewer';
import TitleBreadcrumb from '../../../components/common/title-breadcrumb/title-breadcrumb.component'; import TitleBreadcrumb from '../../../components/common/title-breadcrumb/title-breadcrumb.component';
import Loader from '../../../components/Loader/Loader'; import Loader from '../../../components/Loader/Loader';
import { usePermissionProvider } from '../../../components/PermissionProvider/PermissionProvider'; import { usePermissionProvider } from '../../../components/PermissionProvider/PermissionProvider';
@ -32,29 +30,20 @@ import {
OperationPermission, OperationPermission,
ResourceEntity, ResourceEntity,
} from '../../../components/PermissionProvider/PermissionProvider.interface'; } from '../../../components/PermissionProvider/PermissionProvider.interface';
import { getUserPath } from '../../../constants/constants';
import { import {
GlobalSettingOptions, GlobalSettingOptions,
GlobalSettingsMenuCategory, GlobalSettingsMenuCategory,
} from '../../../constants/GlobalSettings.constants'; } from '../../../constants/GlobalSettings.constants';
import {
NO_PERMISSION_FOR_ACTION,
NO_PERMISSION_TO_VIEW,
} from '../../../constants/HelperTextUtil';
import { EntityType } from '../../../enums/entity.enum'; import { EntityType } from '../../../enums/entity.enum';
import { Role } from '../../../generated/entity/teams/role'; import { Role } from '../../../generated/entity/teams/role';
import { EntityReference } from '../../../generated/type/entityReference'; import { EntityReference } from '../../../generated/type/entityReference';
import { getEntityName } from '../../../utils/CommonUtils'; import { getEntityName } from '../../../utils/CommonUtils';
import { DEFAULT_ENTITY_PERMISSION } from '../../../utils/PermissionsUtils'; import { DEFAULT_ENTITY_PERMISSION } from '../../../utils/PermissionsUtils';
import { import { getSettingPath } from '../../../utils/RouterUtils';
getPolicyWithFqnPath,
getSettingPath,
getTeamsWithFqnPath,
} from '../../../utils/RouterUtils';
import SVGIcons, { Icons } from '../../../utils/SvgUtils';
import { showErrorToast } from '../../../utils/ToastUtils'; import { showErrorToast } from '../../../utils/ToastUtils';
import AddAttributeModal from '../AddAttributeModal/AddAttributeModal'; import AddAttributeModal from '../AddAttributeModal/AddAttributeModal';
import './RolesDetail.less'; import './RolesDetail.less';
import RolesDetailPageList from './RolesDetailPageList.component';
const { TabPane } = Tabs; const { TabPane } = Tabs;
@ -65,101 +54,6 @@ interface AddAttribute {
selectedData: EntityReference[]; selectedData: EntityReference[];
} }
const List = ({
list,
type,
onDelete,
hasAccess,
}: {
list: EntityReference[];
type: 'policy' | 'team' | 'user';
onDelete: (record: EntityReference) => void;
hasAccess: boolean;
}) => {
const columns: ColumnsType<EntityReference> = useMemo(() => {
return [
{
title: 'Name',
dataIndex: 'name',
width: '200px',
key: 'name',
render: (_, record) => {
let link = '';
switch (type) {
case 'policy':
link = getPolicyWithFqnPath(record.fullyQualifiedName || '');
break;
case 'team':
link = getTeamsWithFqnPath(record.fullyQualifiedName || '');
break;
case 'user':
link = getUserPath(record.fullyQualifiedName || '');
break;
default:
break;
}
return (
<Link
className="hover:tw-underline tw-cursor-pointer"
data-testid="entity-name"
to={link}>
{getEntityName(record)}
</Link>
);
},
},
{
title: 'Description',
dataIndex: 'description',
key: 'description',
render: (_, record) => (
<RichTextEditorPreviewer markdown={record?.description || ''} />
),
},
{
title: 'Actions',
dataIndex: 'actions',
width: '80px',
key: 'actions',
render: (_, record) => {
return (
<Tooltip title={hasAccess ? 'Remove' : NO_PERMISSION_FOR_ACTION}>
<Button
data-testid={`remove-action-${getEntityName(record)}`}
disabled={!hasAccess}
type="text"
onClick={() => onDelete(record)}>
<SVGIcons
alt="remove"
icon={Icons.ICON_REMOVE}
title="Remove"
/>
</Button>
</Tooltip>
);
},
},
];
}, []);
return (
<Table
bordered
className="list-table"
columns={columns}
dataSource={list}
pagination={false}
rowKey="id"
size="small"
/>
);
};
const RolesDetailPage = () => { const RolesDetailPage = () => {
const history = useHistory(); const history = useHistory();
const { t } = useTranslation(); const { t } = useTranslation();
@ -187,7 +81,7 @@ const RolesDetailPage = () => {
const breadcrumb = useMemo( const breadcrumb = useMemo(
() => [ () => [
{ {
name: 'Roles', name: t('label.roles'),
url: rolesPath, url: rolesPath,
}, },
{ {
@ -393,13 +287,13 @@ const RolesDetailPage = () => {
/> />
<Tabs data-testid="tabs" defaultActiveKey="policies"> <Tabs data-testid="tabs" defaultActiveKey="policies">
<TabPane key="policies" tab="Policies"> <TabPane key="policies" tab={t('label.policies')}>
<Space className="tw-w-full" direction="vertical"> <Space className="tw-w-full" direction="vertical">
<Tooltip <Tooltip
title={ title={
rolePermission.EditAll rolePermission.EditAll
? t('label.add-policy') ? t('label.add-policy')
: NO_PERMISSION_FOR_ACTION : t('message.no-permission-for-action')
}> }>
<Button <Button
data-testid="add-policy" data-testid="add-policy"
@ -414,7 +308,7 @@ const RolesDetailPage = () => {
{t('label.add-policy')} {t('label.add-policy')}
</Button> </Button>
</Tooltip> </Tooltip>
<List <RolesDetailPageList
hasAccess={rolePermission.EditAll} hasAccess={rolePermission.EditAll}
list={role.policies ?? []} list={role.policies ?? []}
type="policy" type="policy"
@ -424,8 +318,8 @@ const RolesDetailPage = () => {
/> />
</Space> </Space>
</TabPane> </TabPane>
<TabPane key="teams" tab="Teams"> <TabPane key="teams" tab={t('label.teams')}>
<List <RolesDetailPageList
hasAccess={rolePermission.EditAll} hasAccess={rolePermission.EditAll}
list={role.teams ?? []} list={role.teams ?? []}
type="team" type="team"
@ -434,8 +328,8 @@ const RolesDetailPage = () => {
} }
/> />
</TabPane> </TabPane>
<TabPane key="users" tab="Users"> <TabPane key="users" tab={t('label.users')}>
<List <RolesDetailPageList
hasAccess={rolePermission.EditAll} hasAccess={rolePermission.EditAll}
list={role.users ?? []} list={role.users ?? []}
type="user" type="user"
@ -449,7 +343,9 @@ const RolesDetailPage = () => {
)} )}
</> </>
) : ( ) : (
<ErrorPlaceHolder>{NO_PERMISSION_TO_VIEW}</ErrorPlaceHolder> <ErrorPlaceHolder>
{t('message.no-permission-to-view')}
</ErrorPlaceHolder>
)} )}
{selectedEntity && ( {selectedEntity && (
<Modal <Modal
@ -459,7 +355,7 @@ const RolesDetailPage = () => {
okText={t('label.confirm')} okText={t('label.confirm')}
title={`${t('label.remove-entity', { title={`${t('label.remove-entity', {
entity: getEntityName(selectedEntity.record), entity: getEntityName(selectedEntity.record),
})} ${t('label.from')} ${getEntityName(role)}`} })} ${t('label.from-lowercase')} ${getEntityName(role)}`}
visible={!isUndefined(selectedEntity.record)} visible={!isUndefined(selectedEntity.record)}
onCancel={() => setEntity(undefined)} onCancel={() => setEntity(undefined)}
onOk={async () => { onOk={async () => {
@ -469,7 +365,7 @@ const RolesDetailPage = () => {
<Typography.Text> <Typography.Text>
{t('label.sure-to-remove')}{' '} {t('label.sure-to-remove')}{' '}
{`${getEntityName(selectedEntity.record)} ${t( {`${getEntityName(selectedEntity.record)} ${t(
'label.from' 'label.from-lowercase'
)} ${getEntityName(role)}?`} )} ${getEntityName(role)}?`}
</Typography.Text> </Typography.Text>
</Modal> </Modal>

View File

@ -0,0 +1,129 @@
/*
* 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 { Button, Table, Tooltip } from 'antd';
import { ColumnsType } from 'antd/lib/table';
import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import RichTextEditorPreviewer from '../../../components/common/rich-text-editor/RichTextEditorPreviewer';
import { getUserPath } from '../../../constants/constants';
import { EntityReference } from '../../../generated/type/entityReference';
import { getEntityName } from '../../../utils/CommonUtils';
import {
getPolicyWithFqnPath,
getTeamsWithFqnPath,
} from '../../../utils/RouterUtils';
import SVGIcons, { Icons } from '../../../utils/SvgUtils';
const RolesDetailPageList = ({
list,
type,
onDelete,
hasAccess,
}: {
list: EntityReference[];
type: 'policy' | 'team' | 'user';
onDelete: (record: EntityReference) => void;
hasAccess: boolean;
}) => {
const { t } = useTranslation();
const columns: ColumnsType<EntityReference> = useMemo(() => {
return [
{
title: t('label.name'),
dataIndex: 'name',
width: '200px',
key: 'name',
render: (_, record) => {
let link = '';
switch (type) {
case 'policy':
link = getPolicyWithFqnPath(record.fullyQualifiedName || '');
break;
case 'team':
link = getTeamsWithFqnPath(record.fullyQualifiedName || '');
break;
case 'user':
link = getUserPath(record.fullyQualifiedName || '');
break;
default:
break;
}
return (
<Link
className="hover:tw-underline tw-cursor-pointer"
data-testid="entity-name"
to={link}>
{getEntityName(record)}
</Link>
);
},
},
{
title: t('label.description'),
dataIndex: 'description',
key: 'description',
render: (_, record) => (
<RichTextEditorPreviewer markdown={record?.description || ''} />
),
},
{
title: t('label.actions'),
dataIndex: 'actions',
width: '80px',
key: 'actions',
render: (_, record) => {
return (
<Tooltip
title={
hasAccess
? t('label.remove')
: t('message.no-permission-for-action')
}>
<Button
data-testid={`remove-action-${getEntityName(record)}`}
disabled={!hasAccess}
type="text"
onClick={() => onDelete(record)}>
<SVGIcons
alt="remove"
icon={Icons.ICON_REMOVE}
title={t('label.remove')}
/>
</Button>
</Tooltip>
);
},
},
];
}, []);
return (
<Table
bordered
className="list-table"
columns={columns}
dataSource={list}
pagination={false}
rowKey="id"
size="small"
/>
);
};
export default RolesDetailPageList;

View File

@ -11,6 +11,7 @@
* limitations under the License. * limitations under the License.
*/ */
import i18next from 'i18next';
import { TabSpecificField } from '../enums/entity.enum'; import { TabSpecificField } from '../enums/entity.enum';
import { ChartType } from '../pages/DashboardDetailsPage/DashboardDetailsPage.component'; import { ChartType } from '../pages/DashboardDetailsPage/DashboardDetailsPage.component';
import { sortTagsCaseInsensitive } from './CommonUtils'; import { sortTagsCaseInsensitive } from './CommonUtils';
@ -20,21 +21,21 @@ ${TabSpecificField.USAGE_SUMMARY}, ${TabSpecificField.CHARTS},${TabSpecificField
export const dashboardDetailsTabs = [ export const dashboardDetailsTabs = [
{ {
name: 'Details', name: i18next.t('label.details'),
path: 'details', path: 'details',
}, },
{ {
name: 'Activity Feeds & Tasks', name: i18next.t('label.activity-feed-and-task-plural'),
path: 'activity_feed', path: 'activity_feed',
field: TabSpecificField.ACTIVITY_FEED, field: TabSpecificField.ACTIVITY_FEED,
}, },
{ {
name: 'Lineage', name: i18next.t('label.lineage'),
path: 'lineage', path: 'lineage',
field: TabSpecificField.LINEAGE, field: TabSpecificField.LINEAGE,
}, },
{ {
name: 'Custom Properties', name: i18next.t('label.custom-properties'),
path: 'custom_properties', path: 'custom_properties',
}, },
]; ];

View File

@ -1,12 +1,13 @@
import i18next from 'i18next';
import { TabSpecificField } from '../enums/entity.enum'; import { TabSpecificField } from '../enums/entity.enum';
export const databaseDetailsTabs = [ export const databaseDetailsTabs = [
{ {
name: 'Schemas', name: i18next.t('label.schemas'),
path: 'schemas', path: 'schemas',
}, },
{ {
name: 'Activity Feeds', name: i18next.t('label.activity-feeds'),
path: 'activity_feed', path: 'activity_feed',
field: TabSpecificField.ACTIVITY_FEED, field: TabSpecificField.ACTIVITY_FEED,
}, },

View File

@ -11,6 +11,7 @@
* limitations under the License. * limitations under the License.
*/ */
import i18next from 'i18next';
import { TabSpecificField } from '../enums/entity.enum'; import { TabSpecificField } from '../enums/entity.enum';
export const defaultFields = `${TabSpecificField.COLUMNS}, ${TabSpecificField.USAGE_SUMMARY}, export const defaultFields = `${TabSpecificField.COLUMNS}, ${TabSpecificField.USAGE_SUMMARY},
@ -19,41 +20,41 @@ ${TabSpecificField.DATAMODEL},${TabSpecificField.TABLE_CONSTRAINTS},${TabSpecifi
export const datasetTableTabs = [ export const datasetTableTabs = [
{ {
name: 'Schema', name: i18next.t('label.schema'),
path: 'schema', path: 'schema',
}, },
{ {
name: 'Activity Feeds & Tasks', name: i18next.t('label.activity-feed-and-task-plural'),
path: 'activity_feed', path: 'activity_feed',
field: TabSpecificField.ACTIVITY_FEED, field: TabSpecificField.ACTIVITY_FEED,
}, },
{ {
name: 'Sample Data', name: i18next.t('label.sample-data'),
path: 'sample_data', path: 'sample_data',
}, },
{ {
name: 'Queries', name: i18next.t('label.query-plural'),
path: 'table_queries', path: 'table_queries',
}, },
{ {
name: 'Profiler', name: i18next.t('label.profiler'),
path: 'profiler', path: 'profiler',
}, },
{ {
name: 'Data Quality', name: i18next.t('label.data-quality'),
path: 'data-quality', path: 'data-quality',
}, },
{ {
name: 'Lineage', name: i18next.t('label.lineage'),
path: 'lineage', path: 'lineage',
field: TabSpecificField.LINEAGE, field: TabSpecificField.LINEAGE,
}, },
{ {
name: 'DBT', name: i18next.t('label.dbt-uppercase'),
path: 'dbt', path: 'dbt',
}, },
{ {
name: 'Custom Properties', name: i18next.t('label.custom-properties'),
path: 'custom_properties', path: 'custom_properties',
}, },
]; ];

View File

@ -11,6 +11,7 @@
* limitations under the License. * limitations under the License.
*/ */
import i18next from 'i18next';
import { isEmpty, isNil, isUndefined, lowerCase, startCase } from 'lodash'; import { isEmpty, isNil, isUndefined, lowerCase, startCase } from 'lodash';
import { Bucket, LeafNodes, LineagePos } from 'Models'; import { Bucket, LeafNodes, LineagePos } from 'Models';
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
@ -113,7 +114,7 @@ export const getEntityOverview = (
const overview = [ const overview = [
{ {
name: 'Service', name: i18next.t('label.service'),
value: service, value: service,
url: getServiceDetailsPath( url: getServiceDetailsPath(
service, service,
@ -122,7 +123,7 @@ export const getEntityOverview = (
isLink: true, isLink: true,
}, },
{ {
name: 'Database', name: i18next.t('label.database'),
value: database, value: database,
url: getDatabaseDetailsPath( url: getDatabaseDetailsPath(
getPartialNameFromTableFQN( getPartialNameFromTableFQN(
@ -134,7 +135,7 @@ export const getEntityOverview = (
isLink: true, isLink: true,
}, },
{ {
name: 'Schema', name: i18next.t('label.schema'),
value: schema, value: schema,
url: getDatabaseSchemaDetailsPath( url: getDatabaseSchemaDetailsPath(
getPartialNameFromTableFQN( getPartialNameFromTableFQN(
@ -146,33 +147,33 @@ export const getEntityOverview = (
isLink: true, isLink: true,
}, },
{ {
name: 'Owner', name: i18next.t('label.owner'),
value: getEntityName(owner) || '--', value: getEntityName(owner) || '--',
url: getTeamAndUserDetailsPath(owner?.name || ''), url: getTeamAndUserDetailsPath(owner?.name || ''),
isLink: owner ? owner.type === 'team' : false, isLink: owner ? owner.type === 'team' : false,
}, },
{ {
name: 'Tier', name: i18next.t('label.tier'),
value: tier ? tier.split(FQN_SEPARATOR_CHAR)[1] : '--', value: tier ? tier.split(FQN_SEPARATOR_CHAR)[1] : '--',
isLink: false, isLink: false,
}, },
{ {
name: 'Usage', name: i18next.t('label.usage'),
value: usage, value: usage,
isLink: false, isLink: false,
}, },
{ {
name: 'Queries', name: i18next.t('label.query-plural'),
value: `${queries} past week`, value: `${queries} past week`,
isLink: false, isLink: false,
}, },
{ {
name: 'Columns', name: i18next.t('label.columns-plural'),
value: columns ? columns.length : '--', value: columns ? columns.length : '--',
isLink: false, isLink: false,
}, },
{ {
name: 'Rows', name: i18next.t('label.row-plural'),
value: profile && profile?.rowCount ? profile.rowCount : '--', value: profile && profile?.rowCount ? profile.rowCount : '--',
isLink: false, isLink: false,
}, },
@ -188,7 +189,7 @@ export const getEntityOverview = (
const overview = [ const overview = [
{ {
name: 'Service', name: i18next.t('label.service'),
value: service?.name as string, value: service?.name as string,
url: getServiceDetailsPath( url: getServiceDetailsPath(
service?.name as string, service?.name as string,
@ -197,18 +198,18 @@ export const getEntityOverview = (
isLink: true, isLink: true,
}, },
{ {
name: 'Owner', name: i18next.t('label.owner'),
value: getEntityName(owner) || '--', value: getEntityName(owner) || '--',
url: getTeamAndUserDetailsPath(owner?.name || ''), url: getTeamAndUserDetailsPath(owner?.name || ''),
isLink: owner ? owner.type === 'team' : false, isLink: owner ? owner.type === 'team' : false,
}, },
{ {
name: 'Tier', name: i18next.t('label.tier'),
value: tier ? tier.split(FQN_SEPARATOR_CHAR)[1] : '--', value: tier ? tier.split(FQN_SEPARATOR_CHAR)[1] : '--',
isLink: false, isLink: false,
}, },
{ {
name: `${serviceType} url`, name: `${serviceType} ${i18next.t('label.url-lowercase')}`,
value: fullyQualifiedName?.split(FQN_SEPARATOR_CHAR)[1] as string, value: fullyQualifiedName?.split(FQN_SEPARATOR_CHAR)[1] as string,
url: pipelineUrl as string, url: pipelineUrl as string,
isLink: true, isLink: true,
@ -231,7 +232,7 @@ export const getEntityOverview = (
const overview = [ const overview = [
{ {
name: 'Service', name: i18next.t('label.service'),
value: service?.name as string, value: service?.name as string,
url: getServiceDetailsPath( url: getServiceDetailsPath(
service?.name as string, service?.name as string,
@ -240,18 +241,18 @@ export const getEntityOverview = (
isLink: true, isLink: true,
}, },
{ {
name: 'Owner', name: i18next.t('label.owner'),
value: getEntityName(owner) || '--', value: getEntityName(owner) || '--',
url: getTeamAndUserDetailsPath(owner?.name || ''), url: getTeamAndUserDetailsPath(owner?.name || ''),
isLink: owner ? owner.type === 'team' : false, isLink: owner ? owner.type === 'team' : false,
}, },
{ {
name: 'Tier', name: i18next.t('label.tier'),
value: tier ? tier.split(FQN_SEPARATOR_CHAR)[1] : '--', value: tier ? tier.split(FQN_SEPARATOR_CHAR)[1] : '--',
isLink: false, isLink: false,
}, },
{ {
name: `${serviceType} url`, name: `${serviceType} ${i18next.t('label.url-lowercase')}`,
value: value:
displayName || displayName ||
(fullyQualifiedName?.split(FQN_SEPARATOR_CHAR)[1] as string), (fullyQualifiedName?.split(FQN_SEPARATOR_CHAR)[1] as string),

View File

@ -19,27 +19,19 @@ import {
diffWords, diffWords,
diffWordsWithSpace, diffWordsWithSpace,
} from 'diff'; } from 'diff';
import { isEmpty, isUndefined, uniqueId } from 'lodash'; import { t } from 'i18next';
import { isUndefined, uniqueId } from 'lodash';
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import ReactDOMServer from 'react-dom/server'; import ReactDOMServer from 'react-dom/server';
import { Link } from 'react-router-dom';
import { FQN_SEPARATOR_CHAR } from '../constants/char.constants';
import {
DESCRIPTIONLENGTH,
getTeamAndUserDetailsPath,
} from '../constants/constants';
import { EntityField } from '../constants/Feeds.constants'; import { EntityField } from '../constants/Feeds.constants';
import { ChangeType } from '../enums/entity.enum';
import { Column } from '../generated/entity/data/table'; import { Column } from '../generated/entity/data/table';
import { import {
ChangeDescription, ChangeDescription,
FieldChange, FieldChange,
} from '../generated/entity/services/databaseService'; } from '../generated/entity/services/databaseService';
import { TagLabel } from '../generated/type/tagLabel'; import { TagLabel } from '../generated/type/tagLabel';
import { getEntityName } from './CommonUtils';
import { TagLabelWithStatus } from './EntityVersionUtils.interface'; import { TagLabelWithStatus } from './EntityVersionUtils.interface';
import { isValidJSONString } from './StringsUtils'; import { isValidJSONString } from './StringsUtils';
import { getEntityLink } from './TableUtils';
export const getDiffByFieldName = ( export const getDiffByFieldName = (
name: string, name: string,
@ -143,328 +135,6 @@ export const getTagsDiff = (
return result; return result;
}; };
export const getPreposition = (type: ChangeType) => {
switch (type) {
case 'Added':
return 'to';
case 'Removed':
return 'from';
case 'Updated':
return 'of';
default:
return '';
}
};
const getColumnName = (column: string) => {
const name = column.split(FQN_SEPARATOR_CHAR);
const length = name.length;
return name
.slice(length > 1 ? 1 : 0, length > 1 ? length - 1 : length)
.join(FQN_SEPARATOR_CHAR);
};
const getLinkWithColumn = (column: string, eFqn: string, eType: string) => {
return (
<Link
className="tw-pl-1"
to={`${getEntityLink(eType, eFqn)}.${getColumnName(column)}`}>
{getColumnName(column)}
</Link>
);
};
const getDescriptionText = (value: string) => {
const length = value.length;
return `${value.slice(0, DESCRIPTIONLENGTH)}${
length > DESCRIPTIONLENGTH ? '...' : ''
}`;
};
const getDescriptionElement = (fieldChange: FieldChange) => {
return fieldChange?.newValue && fieldChange?.oldValue ? (
<Fragment>
&nbsp;
<span className="tw-italic feed-change-description">{`${getDescriptionText(
fieldChange?.newValue
)}`}</span>
</Fragment>
) : fieldChange?.newValue ? (
<Fragment>
&nbsp;
<span className="tw-italic feed-change-description">
{`${getDescriptionText(fieldChange?.newValue)}`}
</span>
</Fragment>
) : (
<Fragment>
&nbsp;
<span className="tw-italic feed-change-description">
{`${getDescriptionText(fieldChange?.oldValue)}`}
</span>
</Fragment>
);
};
export const feedSummaryFromatter = (
fieldChange: FieldChange,
type: ChangeType,
_entityName: string,
entityType: string,
entityFQN: string
) => {
const value = JSON.parse(
isValidJSONString(fieldChange?.newValue)
? fieldChange?.newValue
: isValidJSONString(fieldChange?.oldValue)
? fieldChange?.oldValue
: '{}'
);
const oldValue = JSON.parse(
isValidJSONString(fieldChange?.oldValue) ? fieldChange?.oldValue : '{}'
);
const newValue = JSON.parse(
isValidJSONString(fieldChange?.newValue) ? fieldChange?.newValue : '{}'
);
let summary: JSX.Element;
switch (true) {
case fieldChange?.name?.startsWith('column'): {
if (fieldChange?.name?.endsWith('tags')) {
summary = (
<p key={uniqueId()}>
{`${type} tags ${value
?.map((val: TagLabel) => val?.tagFQN)
?.join(', ')} ${getPreposition(type)} column`}
{getLinkWithColumn(
fieldChange?.name as string,
entityFQN,
entityType
)}
</p>
);
break;
} else if (fieldChange?.name?.endsWith(EntityField.DESCRIPTION)) {
summary = (
<p key={uniqueId()}>
{`${
fieldChange?.newValue && fieldChange?.oldValue
? type
: fieldChange?.newValue
? 'Added'
: 'Removed'
} column description for`}
{getLinkWithColumn(
fieldChange?.name as string,
entityFQN,
entityType
)}
{isEmpty(value) ? getDescriptionElement(fieldChange) : ''}
</p>
);
break;
} else if (fieldChange?.name === EntityField.COLUMNS) {
const length = value?.length ?? 0;
summary = (
<p key={uniqueId()}>
{`${type} ${fieldChange?.name}`}{' '}
{value?.map((column: Column, i: number) => (
<span key={uniqueId()}>
{getLinkWithColumn(column.name, entityFQN, entityType)}{' '}
{i !== length - 1 ? ', ' : ''}
</span>
))}
</p>
);
break;
} else {
summary = (
<p key={uniqueId()}>
{`${type}`}
{getLinkWithColumn(
fieldChange?.name as string,
entityFQN,
entityType
)}
</p>
);
break;
}
}
case fieldChange?.name === 'tags': {
const tier = value?.find((t: TagLabel) => t?.tagFQN?.startsWith('Tier'));
const tags = value?.filter(
(t: TagLabel) => !t?.tagFQN?.startsWith('Tier')
);
summary = (
<div>
{tags?.length > 0 ? (
<p key={uniqueId()}>{`${type} tags ${tags
?.map((val: TagLabel) => val?.tagFQN)
?.join(', ')}`}</p>
) : null}
{tier ? (
<p key={uniqueId()}>{`${type} tier ${
tier?.tagFQN?.split(FQN_SEPARATOR_CHAR)[1]
}`}</p>
) : null}
</div>
);
break;
}
case fieldChange?.name === 'owner': {
const ownerName = getEntityName(newValue) || getEntityName(value);
const ownerText =
!isEmpty(oldValue) && !isEmpty(newValue) ? (
<Fragment>
{newValue?.type === 'team' ? (
<Link
className="tw-pl-1"
to={getTeamAndUserDetailsPath(newValue?.name || '')}>
<span title={ownerName}>{ownerName}</span>
</Link>
) : (
<span className="tw-pl-1" title={ownerName}>
{ownerName}
</span>
)}
</Fragment>
) : (
<Fragment>
{value?.type === 'team' ? (
<Link
className="tw-pl-1"
to={getTeamAndUserDetailsPath(value?.name || '')}>
<span title={ownerName}>{ownerName}</span>
</Link>
) : (
<span className="tw-pl-1" title={ownerName}>
{ownerName}
</span>
)}
</Fragment>
);
summary = (
<p
className={classNames('tw-truncate', {
'tw-w-52': ownerName.length > 32,
})}
key={uniqueId()}>
{`Assigned ownership to ${ownerText}`}
</p>
);
break;
}
case fieldChange?.name === EntityField.DESCRIPTION: {
summary = (
<p key={uniqueId()}>
{`${
fieldChange?.newValue && fieldChange?.oldValue
? type
: fieldChange?.newValue
? 'Added'
: 'Removed'
} description`}
{getDescriptionElement(fieldChange)}
</p>
);
break;
}
case fieldChange?.name === 'followers': {
summary = (
<p key={uniqueId()}>{`${
fieldChange?.newValue ? 'Started following' : 'Unfollowed'
} ${_entityName}`}</p>
);
break;
}
default:
summary = <p key={uniqueId()}>{`${type} ${fieldChange?.name}`}</p>;
break;
}
return summary;
};
export const getFeedSummary = (
changeDescription: ChangeDescription,
entityName: string,
entityType: string,
entityFQN: string
) => {
const fieldsAdded = [...(changeDescription?.fieldsAdded || [])];
const fieldsDeleted = [...(changeDescription?.fieldsDeleted || [])];
const fieldsUpdated = [...(changeDescription?.fieldsUpdated || [])];
return (
<Fragment>
{fieldsDeleted?.length ? (
<div className="tw-mb-2">
{fieldsDeleted?.map((d) => (
<Fragment key={uniqueId()}>
{feedSummaryFromatter(
d,
ChangeType.REMOVED,
entityName,
entityType,
entityFQN
)}
</Fragment>
))}
</div>
) : null}
{fieldsAdded?.length > 0 ? (
<div className="tw-mb-2">
{fieldsAdded?.map((a) => (
<Fragment key={uniqueId()}>
{feedSummaryFromatter(
a,
ChangeType.ADDED,
entityName,
entityType,
entityFQN
)}
</Fragment>
))}
</div>
) : null}
{fieldsUpdated?.length ? (
<div className="tw-mb-2">
{fieldsUpdated?.map((u) => (
<Fragment key={uniqueId()}>
{feedSummaryFromatter(
u,
ChangeType.UPDATED,
entityName,
entityType,
entityFQN
)}
</Fragment>
))}
</div>
) : null}
</Fragment>
);
};
export const summaryFormatter = (fieldChange: FieldChange) => { export const summaryFormatter = (fieldChange: FieldChange) => {
const value = JSON.parse( const value = JSON.parse(
isValidJSONString(fieldChange?.newValue) isValidJSONString(fieldChange?.newValue)
@ -511,31 +181,53 @@ export const getSummary = (
{isDeleteUpdated {isDeleteUpdated
.map((field) => { .map((field) => {
return field.newValue return field.newValue
? 'Data asset has been deleted' ? t('label.data-asset-has-been-action-type', {
: 'Data asset has been restored'; actionType: t('label.deleted-lowercase'),
})
: t('label.data-asset-has-been-action-type', {
actionType: t('label.restored-lowercase'),
});
}) })
.join(', ')} .join(', ')}
</p> </p>
) : null} ) : null}
{fieldsAdded?.length > 0 ? ( {fieldsAdded?.length > 0 ? (
<p className="tw-mb-2"> <p className="tw-mb-2">
{`${isPrefix ? '+ Added' : ''} ${fieldsAdded {`${isPrefix ? `+ ${t('label.added')}` : ''} ${fieldsAdded
.map(summaryFormatter) .map(summaryFormatter)
.join(', ')} ${!isPrefix ? `has been added` : ''}`}{' '} .join(', ')} ${
!isPrefix
? t('label.has-been-action-type-lowercase', {
actionType: t('label.added-lowercase'),
})
: ''
}`}{' '}
</p> </p>
) : null} ) : null}
{fieldsUpdated?.length ? ( {fieldsUpdated?.length ? (
<p className="tw-mb-2"> <p className="tw-mb-2">
{`${isPrefix ? 'Edited' : ''} ${fieldsUpdated {`${isPrefix ? t('label.edited') : ''} ${fieldsUpdated
.map(summaryFormatter) .map(summaryFormatter)
.join(', ')} ${!isPrefix ? `has been updated` : ''}`}{' '} .join(', ')} ${
!isPrefix
? t('label.has-been-action-type-lowercase', {
actionType: t('label.updated-lowercase'),
})
: ''
}`}{' '}
</p> </p>
) : null} ) : null}
{fieldsDeleted?.length ? ( {fieldsDeleted?.length ? (
<p className="tw-mb-2"> <p className="tw-mb-2">
{`${isPrefix ? '- Removed' : ''} ${fieldsDeleted {`${isPrefix ? '- Removed' : ''} ${fieldsDeleted
.map(summaryFormatter) .map(summaryFormatter)
.join(', ')} ${!isPrefix ? `has been Deleted` : ''}`}{' '} .join(', ')} ${
!isPrefix
? t('label.has-been-action-type-lowercase', {
actionType: t('label.deleted-lowercase'),
})
: ''
}`}{' '}
</p> </p>
) : null} ) : null}
</Fragment> </Fragment>

View File

@ -15,6 +15,7 @@ import { faAngleRight } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import { Operation } from 'fast-json-patch'; import { Operation } from 'fast-json-patch';
import i18next from 'i18next';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash';
import { import {
EntityFieldThreadCount, EntityFieldThreadCount,
@ -112,10 +113,15 @@ export const getReplyText = (
singular?: string, singular?: string,
plural?: string plural?: string
) => { ) => {
if (count === 0) return 'Reply in conversation'; if (count === 0) return i18next.t('label.reply-in-conversation');
if (count === 1) return `${count} ${singular ? singular : 'older reply'}`; if (count === 1)
return `${count} ${
singular ? singular : i18next.t('label.older-reply-lowercase')
}`;
return `${count} ${plural ? plural : 'older replies'}`; return `${count} ${
plural ? plural : i18next.t('label.older-replies-lowercase')
}`;
}; };
export const getEntityFieldThreadCounts = ( export const getEntityFieldThreadCounts = (
@ -481,10 +487,10 @@ export const updateThreadData = (
export const getFeedAction = (type: ThreadType) => { export const getFeedAction = (type: ThreadType) => {
if (type === ThreadType.Task) { if (type === ThreadType.Task) {
return 'created a task'; return i18next.t('label.created-a-task-lowercase');
} }
return 'posted on'; return i18next.t('label.posted-on-lowercase');
}; };
export const prepareFeedLink = (entityType: string, entityFQN: string) => { export const prepareFeedLink = (entityType: string, entityFQN: string) => {
@ -553,11 +559,11 @@ export const getFeedPanelHeaderText = (
) => { ) => {
switch (threadType) { switch (threadType) {
case ThreadType.Announcement: case ThreadType.Announcement:
return 'Announcement'; return i18next.t('label.announcement');
case ThreadType.Task: case ThreadType.Task:
return 'Task'; return i18next.t('label.task');
case ThreadType.Conversation: case ThreadType.Conversation:
default: default:
return 'Conversation'; return i18next.t('label.Conversation');
} }
}; };

View File

@ -12,6 +12,7 @@
*/ */
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import { t } from 'i18next';
import { cloneDeep, isEmpty } from 'lodash'; import { cloneDeep, isEmpty } from 'lodash';
import { FormattedGlossarySuggestion } from 'Models'; import { FormattedGlossarySuggestion } from 'Models';
import { DataNode } from 'rc-tree/lib/interface'; import { DataNode } from 'rc-tree/lib/interface';
@ -230,7 +231,7 @@ export const updateGlossaryListBySearchedTerms = (
export const getActionsList = () => { export const getActionsList = () => {
return [ return [
{ {
name: 'Add Term', name: t('label.add-term'),
value: 'add_term', value: 'add_term',
}, },
]; ];

View File

@ -13,6 +13,7 @@
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import { Change, diffWordsWithSpace } from 'diff'; import { Change, diffWordsWithSpace } from 'diff';
import { t } from 'i18next';
import { isEqual, isUndefined } from 'lodash'; import { isEqual, isUndefined } from 'lodash';
import { getDashboardByFqn } from '../axiosAPIs/dashboardAPI'; import { getDashboardByFqn } from '../axiosAPIs/dashboardAPI';
import { getUserSuggestions } from '../axiosAPIs/miscAPI'; import { getUserSuggestions } from '../axiosAPIs/miscAPI';
@ -311,11 +312,11 @@ export const fetchEntityDetail = (
export const TASK_ACTION_LIST = [ export const TASK_ACTION_LIST = [
{ {
label: 'Accept Suggestion', label: t('label.accept-suggestion'),
key: TaskActionMode.VIEW, key: TaskActionMode.VIEW,
}, },
{ {
label: 'Edit & Accept Suggestion', label: t('label.edit-amp-accept-suggestion'),
key: TaskActionMode.EDIT, key: TaskActionMode.EDIT,
}, },
]; ];