From 0c7bf13901c4236fe3e7acbcf3efa07ff3eb7b5f Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Wed, 14 Dec 2022 11:52:48 +0530 Subject: [PATCH] feat(ui): localization for utils and models (#9265) --- .../AddAnnouncementModal.tsx | 30 +++-- .../EditAnnouncementModal.tsx | 29 +++-- .../ui/src/locale/languages/en-us.json | 40 +++++- .../src/main/resources/ui/src/setupTests.js | 7 + .../ui/src/utils/AnnouncementsUtils.ts | 3 - .../resources/ui/src/utils/CommonUtils.tsx | 41 ++++-- .../ui/src/utils/EntityLineageUtils.tsx | 31 ++--- .../ui/src/utils/EventPublisherUtils.ts | 13 +- .../src/main/resources/ui/src/utils/Fqn.js | 5 +- .../main/resources/ui/src/utils/Fqn.test.js | 6 +- .../main/resources/ui/src/utils/TourUtils.tsx | 120 +++++++++++++----- 11 files changed, 220 insertions(+), 105 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Modals/AnnouncementModal/AddAnnouncementModal.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Modals/AnnouncementModal/AddAnnouncementModal.tsx index e415716c396..ab8152da74e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Modals/AnnouncementModal/AddAnnouncementModal.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Modals/AnnouncementModal/AddAnnouncementModal.tsx @@ -15,16 +15,14 @@ import { Form, Input, Modal, Space } from 'antd'; import { AxiosError } from 'axios'; import { observer } from 'mobx-react'; import React, { FC, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import AppState from '../../../AppState'; import { postThread } from '../../../axiosAPIs/feedsAPI'; import { CreateThread, ThreadType, } from '../../../generated/api/feed/createThread'; -import { - announcementInvalidStartTime, - validateMessages, -} from '../../../utils/AnnouncementsUtils'; +import { validateMessages } from '../../../utils/AnnouncementsUtils'; import { getEntityFeedLink } from '../../../utils/EntityUtils'; import { getTimeZone, getUTCDateTime } from '../../../utils/TimeUtils'; import { showErrorToast, showSuccessToast } from '../../../utils/ToastUtils'; @@ -57,11 +55,13 @@ const AddAnnouncementModal: FC = ({ const [isLoading, setIsLoading] = useState(false); + const { t } = useTranslation(); + const handleCreateAnnouncement = async () => { const startTime = Math.floor(getUTCDateTime(startDate) / 1000); const endTime = Math.floor(getUTCDateTime(endDate) / 1000); if (startTime >= endTime) { - showErrorToast(announcementInvalidStartTime); + showErrorToast(t('message.announcement-invalid-start-time')); } else { const announcementData: CreateThread = { from: currentUser?.name as string, @@ -78,7 +78,7 @@ const AddAnnouncementModal: FC = ({ setIsLoading(true); const data = await postThread(announcementData); if (data) { - showSuccessToast('Announcement created successfully!'); + showSuccessToast(t('message.announcement-created-successfully')); } onCancel(); } catch (error) { @@ -103,7 +103,7 @@ const AddAnnouncementModal: FC = ({ htmlType: 'submit', }} okText="Submit" - title="Make an announcement" + title={t('label.make-an-announcement')} visible={open} width={620} onCancel={onCancel}> @@ -114,7 +114,7 @@ const AddAnnouncementModal: FC = ({ validateMessages={validateMessages} onFinish={handleCreateAnnouncement}> = ({ }, ]}> setTitle(e.target.value)} @@ -133,7 +133,9 @@ const AddAnnouncementModal: FC = ({ = ({ /> = ({ /> - + setDescription(value)} /> diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Modals/AnnouncementModal/EditAnnouncementModal.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Modals/AnnouncementModal/EditAnnouncementModal.tsx index 835bb81a8e1..b6f4ba0a7cf 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Modals/AnnouncementModal/EditAnnouncementModal.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Modals/AnnouncementModal/EditAnnouncementModal.tsx @@ -14,11 +14,9 @@ import { Form, Input, Modal, Space } from 'antd'; import { observer } from 'mobx-react'; import React, { FC, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { AnnouncementDetails } from '../../../generated/entity/feed/thread'; -import { - announcementInvalidStartTime, - validateMessages, -} from '../../../utils/AnnouncementsUtils'; +import { validateMessages } from '../../../utils/AnnouncementsUtils'; import { getLocaleDateFromTimeStamp, getTimeZone, @@ -53,12 +51,13 @@ const EditAnnouncementModal: FC = ({ const [description, setDescription] = useState( announcement.description || '' ); + const { t } = useTranslation(); const handleConfirm = () => { const startTime = Math.floor(getUTCDateTime(startDate) / 1000); const endTime = Math.floor(getUTCDateTime(endDate) / 1000); if (startTime >= endTime) { - showErrorToast(announcementInvalidStartTime); + showErrorToast(t('message.announcement-invalid-start-time')); } else { const updatedAnnouncement = { ...announcement, @@ -82,8 +81,8 @@ const EditAnnouncementModal: FC = ({ type: 'primary', htmlType: 'submit', }} - okText="Save" - title="Edit an announcement" + okText={t('label.save')} + title={t('label.edit-an-announcement')} visible={open} width={620} onCancel={onCancel}> @@ -95,7 +94,7 @@ const EditAnnouncementModal: FC = ({ validateMessages={validateMessages} onFinish={handleConfirm}> = ({ }, ]}> setTitle(e.target.value)} @@ -114,7 +113,9 @@ const EditAnnouncementModal: FC = ({ = ({ /> = ({ /> - + setDescription(value)} /> diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json index 59fb1c6761d..0b2f46bea5b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json @@ -471,6 +471,27 @@ "retention-size": "retention-size", "clean-up-policies": "clean-up policies", "maximum-size": "maximum size", + "openmetadata": "OpenMetadata", + "activity-feeds": "Activity Feeds", + "search": "Search", + "enter": "Enter", + "profiler": "Profiler", + "data-profiler": "Data Profiler", + "starting": "Starting", + "idle": "Idle", + "completed": "Completed", + "active-with-error": "Active with error", + "retry": "Retry", + "claim-ownership-in-manage": "Claim ownership in Manage", + "deactivated": "Deactivated", + "username": "Username", + "make-an-announcement": "Make an announcement", + "announcement-title": "Announcement title", + "start-date-time-zone": "Start Date: ({{timeZone}})", + "end-date-time-zone": "End Date: ({{timeZone}})", + "write-your-announcement-lowercase": "write your announcement", + "title": "Title", + "edit-an-announcement": "Edit an announcement", "no-of-test": " No. of Test", "test-suite": "Test Suite", "enter-entity": "Enter {{entity}}", @@ -577,7 +598,24 @@ "entity-owned-by-name": "This Entity is Owned by {{entityOwner}}", "and-followed-owned-by-name": "and followed team owned by {{userName}}", "field-text-is-invalid": "{{fieldText}} is invalid.", - "delete-team-message": "Any teams under \"{{teamName}}\" will be {{deleteType}} deleted as well." + "delete-team-message": "Any teams under \"{{teamName}}\" will be {{deleteType}} deleted as well.", + "tour-step-discover-all-assets-at-one-place": "Discover all your data assets in a single place with <0>{{text}}, a centralized metadata store. Collaborate with your team and get a holistic picture of the data in your organization.", + "tour-step-activity-feed": "<0>{{text}} help you understand how the data is changing in your organization.", + "tour-step-search-for-matching-dataset": "Search for matching data assets by \"name\", \"description\", \"column name\", and so on from the <0>{{text}} box.", + "tour-step-type-search-term": "In the search box, type <0>\"{{text}}\". Hit <0>{{enterText}}.", + "tour-step-explore-summary-asset": "From the <0>\"{{text}}\" page, view a summary of each asset, including: title, description, owner, tier (importance), usage, and location.", + "tour-step-click-on-link-to-view-more": "Click on the <0>title of the asset to view more details.", + "tour-step-get-to-know-table-schema": "Get to know the table <0>Schema, including column names and data types as well as column descriptions and tags. You can even view metadata for complex types such as structs.", + "tour-step-click-on-entity-tab": "Click on the <0>\"{{text}}\" tab.", + "tour-step-look-at-sample-data": "Take a look at the <0>{{text}} to get a feel for what the table contains and how you might use it.", + "tour-step-trace-path-across-tables": " With <0>{{text}}, trace the path of data across tables, pipelines, & dashboards.", + "tour-step-discover-data-assets-with-data-profile": "Discover assets with the <0>{{text}}. Get to know the table usage stats, check for null values and duplicates, and understand the column data distributions.", + "lineage-data-is-not-available-for-deleted-entities": "Lineage data is not available for deleted entities.", + "remove-edge-between-source-and-target": "Are you sure you want to remove the edge between \"{{sourceDisplayName}} and {{targetDisplayName}}\"?.", + "announcement-invalid-start-time": "Announcement start time must be earlier than the end time", + "permanently-delete-metadata-and-dependents": "Permanently deleting this {{entityName}} will remove its metadata, as well as the metadata of {{dependents}} from OpenMetadata permanently.", + "permanently-delete-metadata": "Permanently deleting this {{entityName}} will remove its metadata from OpenMetadata permanently.", + "announcement-created-successfully": "Announcement created successfully!" }, "server": { "you-have-not-action-anything-yet": "You have not {{action}} anything yet.", diff --git a/openmetadata-ui/src/main/resources/ui/src/setupTests.js b/openmetadata-ui/src/main/resources/ui/src/setupTests.js index 425475b8dfc..848552e4083 100644 --- a/openmetadata-ui/src/main/resources/ui/src/setupTests.js +++ b/openmetadata-ui/src/main/resources/ui/src/setupTests.js @@ -82,3 +82,10 @@ jest.mock('react-i18next', () => ({ }), t: (key) => key, })); + +/** + * mock i18next + */ +jest.mock('i18next', () => ({ + t: jest.fn().mockImplementation((key) => key), +})); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/AnnouncementsUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/AnnouncementsUtils.ts index cd43d69c8f2..891b98737b2 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/AnnouncementsUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/AnnouncementsUtils.ts @@ -20,6 +20,3 @@ export const isActiveAnnouncement = (startTime: number, endTime: number) => { return currentTime > startTime && currentTime < endTime; }; - -export const announcementInvalidStartTime = - 'Announcement start time must be earlier than the end time'; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/CommonUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/CommonUtils.tsx index 468abd9b5db..4fe5db2e9f6 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/CommonUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/CommonUtils.tsx @@ -16,7 +16,7 @@ import { Popover, Space, Tag, Typography } from 'antd'; import { ColumnsType } from 'antd/lib/table'; import { AxiosError } from 'axios'; import classNames from 'classnames'; -import i18n from 'i18next'; +import { t } from 'i18next'; import { capitalize, isEmpty, @@ -37,6 +37,7 @@ import { RecentlyViewedData, } from 'Models'; import React from 'react'; +import { Trans } from 'react-i18next'; import { Link } from 'react-router-dom'; import { reactLocalStorage } from 'reactjs-localstorage'; import AppState from '../AppState'; @@ -536,7 +537,7 @@ export const prepareLabel = (type: string, fqn: string, withQuotes = true) => { */ export const getEntityPlaceHolder = (value: string, isDeleted?: boolean) => { if (isDeleted) { - return `${value} (Deactivated)`; + return `${value} (${t('label.deactivated')})`; } else { return value; } @@ -586,13 +587,14 @@ export const getEntityId = ( export const getEntityDeleteMessage = (entity: string, dependents: string) => { if (dependents) { - return `Permanently deleting this ${getTitleCase( - entity - )} will remove its metadata, as well as the metadata of ${dependents} from OpenMetadata permanently.`; + return t('message.permanently-delete-metadata-and-dependents', { + entityName: getTitleCase(entity), + dependents, + }); } else { - return `Permanently deleting this ${getTitleCase( - entity - )} will remove its metadata from OpenMetadata permanently.`; + return t('message.permanently-delete-metadata-and-dependents', { + entityName: getTitleCase(entity), + }); } }; @@ -731,7 +733,7 @@ export const getHostNameFromURL = (url: string) => { export const commonUserDetailColumns: ColumnsType = [ { - title: 'Username', + title: t('label.username'), dataIndex: 'username', key: 'username', render: (_, record) => ( @@ -744,7 +746,7 @@ export const commonUserDetailColumns: ColumnsType = [ ), }, { - title: 'Teams', + title: t('label.teams'), dataIndex: 'teams', key: 'teams', render: (_, record) => { @@ -792,7 +794,7 @@ export const commonUserDetailColumns: ColumnsType = [ }, }, { - title: 'Roles', + title: t('label.roles'), dataIndex: 'roles', key: 'roles', render: (_, record) => { @@ -872,7 +874,7 @@ export const getEmptyPlaceholder = () => { return ( - {i18n.t('label.no-data-available')} + {t('label.no-data-available')} ); @@ -952,6 +954,21 @@ export const sortTagsCaseInsensitive = (tags: TagLabel[]) => { ); }; +export const Transi18next = ({ + i18nKey, + values, + renderElement, + ...otherProps +}: { + i18nKey: string; + values?: {}; + renderElement: JSX.Element | HTMLElement; +}): JSX.Element => ( + + {renderElement} + +); + /** * It returns a link to the documentation for the given filter pattern type * @param {FilterPatternEnum} type - The type of filter pattern. diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.tsx index 51d2375fdb3..048d806fc2a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.tsx @@ -17,6 +17,7 @@ import { } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import dagre from 'dagre'; +import { t } from 'i18next'; import { isEmpty, isNil, isUndefined } from 'lodash'; import { LeafNodes, LineagePos, LoadingNodeState, LoadingState } from 'Models'; import React, { Fragment, MouseEvent as ReactMouseEvent } from 'react'; @@ -431,29 +432,12 @@ export const getDataLabel = ( } }; -export const getNoLineageDataPlaceholder = () => { - return ( -
- - Lineage is currently supported for Airflow. To enable lineage collection - from Airflow, please follow the documentation - - - here. - -
- ); -}; export const getDeletedLineagePlaceholder = () => { return (
- Lineage data is not available for deleted entities. + + {t('message.lineage-data-is-not-available-for-deleted-entities')} +
); }; @@ -527,9 +511,10 @@ export const getModalBodyText = (selectedEdge: SelectedEdge) => { targetEntity = getPartialNameFromFQN(targetFQN || '', ['database']); } - return `Are you sure you want to remove the edge between "${ - source.displayName ? source.displayName : sourceEntity - } and ${target.displayName ? target.displayName : targetEntity}"?`; + return t('message.remove-edge-between-source-and-target', { + sourceDisplayName: source.displayName ? source.displayName : sourceEntity, + targetDisplayName: target.displayName ? target.displayName : targetEntity, + }); }; export const getUniqueFlowElements = (elements: CustomFlow[]) => { diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/EventPublisherUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/EventPublisherUtils.ts index 24c757fb113..262b575fa28 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/EventPublisherUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/EventPublisherUtils.ts @@ -11,6 +11,7 @@ * limitations under the License. */ +import { t } from 'i18next'; import { Status } from '../generated/settings/eventPublisherJob'; import { Icons } from './SvgUtils'; @@ -37,19 +38,19 @@ export const getStatusResultBadgeIcon = (status: string) => { export const getEventPublisherStatusText = (status?: string) => { switch (status) { case Status.Starting: - return 'Starting'; + return t('label.starting'); case Status.Idle: - return 'Idle'; + return t('label.idle'); case Status.Completed: - return 'Completed'; + return t('label.completed'); case Status.Active: - return 'Active'; + return t('label.active'); case Status.ActiveWithError: - return 'Active with error'; + return t('label.active-with-error'); case Status.Retry: - return 'Retry'; + return t('label.retry'); default: return status || ''; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/Fqn.js b/openmetadata-ui/src/main/resources/ui/src/utils/Fqn.js index 2c1b93df8b2..45ce1071d77 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/Fqn.js +++ b/openmetadata-ui/src/main/resources/ui/src/utils/Fqn.js @@ -13,6 +13,7 @@ import antlr4 from 'antlr4'; import { ParseTreeWalker } from 'antlr4/src/antlr4/tree'; +import i18next from 'i18next'; import SplitListener from '../antlr/SplitListener'; import FqnLexer from '../generated/antlr/FqnLexer'; import FqnParser from '../generated/antlr/FqnParser'; @@ -45,7 +46,7 @@ export default class Fqn { static quoteName(name) { const matcher = /^(")([^"]+)(")$|^(.*)$/.exec(name); if (!matcher || matcher[0].length != name.length) { - throw 'Invalid name ' + name; + throw new Error(`${i18next.t('label.invalid-name')} ${name}`); } // Name matches quoted string "sss". @@ -63,6 +64,6 @@ export default class Fqn { return unquotedName.includes('.') ? '"' + name + '"' : unquotedName; } - throw 'Invalid name ' + name; + throw new Error(`${i18next.t('label.invalid-name')} ${name}`); } } diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/Fqn.test.js b/openmetadata-ui/src/main/resources/ui/src/utils/Fqn.test.js index c04e00cb2cb..856415623ee 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/Fqn.test.js +++ b/openmetadata-ui/src/main/resources/ui/src/utils/Fqn.test.js @@ -59,8 +59,8 @@ describe('Test FQN', () => { expect('"a.b"').toStrictEqual(Fqn.quoteName('"a.b"')); // Leave existing valid quotes expect('a').toStrictEqual(Fqn.quoteName('"a"')); // Remove quotes when not needed - expect(() => Fqn.quoteName('"a')).toThrow('Invalid name "a'); // Error when ending quote is missing - expect(() => Fqn.quoteName('a"')).toThrow('Invalid name a"'); // Error when beginning quote is missing - expect(() => Fqn.quoteName('a"b')).toThrow('Invalid name a"b'); // Error when invalid quote is present in the middle of the string + expect(() => Fqn.quoteName('"a')).toThrow('label.invalid-name "a'); // Error when ending quote is missing + expect(() => Fqn.quoteName('a"')).toThrow('label.invalid-name a"'); // Error when beginning quote is missing + expect(() => Fqn.quoteName('a"b')).toThrow('label.invalid-name a"b'); // Error when invalid quote is present in the middle of the string }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/TourUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/TourUtils.tsx index 861b288a9dc..feff4c9dcd2 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/TourUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/TourUtils.tsx @@ -11,9 +11,11 @@ * limitations under the License. */ +import i18next from 'i18next'; import React from 'react'; import AppState from '../AppState'; import { CurrentTourPageType } from '../enums/tour.enum'; +import { Transi18next } from './CommonUtils'; import { getCurrentDatasetTab } from './DatasetDetailsUtils'; export const getSteps = (value: string, clearSearchTerm: () => void) => { @@ -21,10 +23,13 @@ export const getSteps = (value: string, clearSearchTerm: () => void) => { { content: () => (

- Discover all your data assets in a single place with{' '} - OpenMetadata, a centralized metadata store. - Collaborate with your team and get a holistic picture of the data in - your organization. + } + values={{ + text: i18next.t('label.openmetadata'), + }} + />

), stepInteraction: false, @@ -33,8 +38,13 @@ export const getSteps = (value: string, clearSearchTerm: () => void) => { { content: () => (

- Activity Feeds help you understand how the data is - changing in your organization. + } + values={{ + text: i18next.t('label.activity-feeds'), + }} + />

), selector: '#feedData', @@ -43,9 +53,13 @@ export const getSteps = (value: string, clearSearchTerm: () => void) => { { content: () => (

- Search for matching data assets by "name", - "description", "column name", and so on from the{' '} - Search box. + } + values={{ + text: i18next.t('label.search'), + }} + />

), selector: '#searchBox', @@ -56,8 +70,14 @@ export const getSteps = (value: string, clearSearchTerm: () => void) => { beforePrev: clearSearchTerm, content: () => (

- In the search box, type "{value}". Hit{' '} - Enter. + } + values={{ + text: value, + enterText: i18next.t('label.enter'), + }} + />

), actionType: 'enter', @@ -74,9 +94,13 @@ export const getSteps = (value: string, clearSearchTerm: () => void) => { }, content: () => (

- From the "Explore" page, view a summary of - each asset, including: title, description, owner, tier (importance), - usage, and location. + } + values={{ + text: i18next.t('label.explore'), + }} + />

), selector: '#tabledatacard0', @@ -85,7 +109,10 @@ export const getSteps = (value: string, clearSearchTerm: () => void) => { { content: () => (

- Click on the title of the asset to view more details. + } + />

), actionType: 'click', @@ -100,10 +127,13 @@ export const getSteps = (value: string, clearSearchTerm: () => void) => { }, content: () => (

- {' '} - Get to know the table Schema, including column names - and data types as well as column descriptions and tags. You can even - view metadata for complex types such as structs. + } + values={{ + text: i18next.t('label.schema'), + }} + />

), stepInteraction: false, @@ -116,7 +146,13 @@ export const getSteps = (value: string, clearSearchTerm: () => void) => { actionType: 'click', content: () => (

- Click on the "Sample Data" tab. + } + values={{ + text: i18next.t('label.sample-data'), + }} + />

), selector: '#sampleData', @@ -128,8 +164,13 @@ export const getSteps = (value: string, clearSearchTerm: () => void) => { { content: () => (

- Take a look at the Sample Data to get a feel for what - the table contains and how you might use it. + } + values={{ + text: i18next.t('label.sample-data'), + }} + />

), selector: '#sampleDataDetails', @@ -145,7 +186,13 @@ export const getSteps = (value: string, clearSearchTerm: () => void) => { actionType: 'click', content: () => (

- Click on the "Profiler" tab. + } + values={{ + text: i18next.t('label.profiler'), + }} + />

), selector: '#profilerDataQuality', @@ -153,9 +200,13 @@ export const getSteps = (value: string, clearSearchTerm: () => void) => { { content: () => (

- Discover assets with the Data Profiler. Get to know - the table usage stats, check for null values and duplicates, and - understand the column data distributions. + } + values={{ + text: i18next.t('label.data-profiler'), + }} + />

), stepInteraction: false, @@ -171,7 +222,13 @@ export const getSteps = (value: string, clearSearchTerm: () => void) => { actionType: 'click', content: () => (

- Click on the "Lineage" tab + } + values={{ + text: i18next.t('label.lineage'), + }} + />

), selector: '#lineage', @@ -179,8 +236,13 @@ export const getSteps = (value: string, clearSearchTerm: () => void) => { { content: () => (

- With Lineage, trace the path of data across tables, - pipelines, & dashboards. + } + values={{ + text: i18next.t('label.lineage'), + }} + />

), stepInteraction: false,