From 016f585e14bb642b37da5e346002c7ff9a13a93f Mon Sep 17 00:00:00 2001 From: Shailesh Parmar Date: Thu, 25 Apr 2024 22:05:58 +0530 Subject: [PATCH] #14440 Enhance table details, and restore the test case states in column profiler tab (#16024) Co-authored-by: Teddy --- .../ColumnProfileTable/ColumnProfileTable.tsx | 53 ++++++++- .../QualityTab/QualityTab.component.tsx | 40 +------ .../TableProfiler/TableProfiler.interface.ts | 3 +- .../TableProfilerProvider.test.tsx | 16 +-- .../TableProfiler/TableProfilerProvider.tsx | 6 +- .../SchemaTab/SchemaTab.component.tsx | 14 +-- .../SchemaTab/SchemaTab.interfaces.ts | 13 +-- .../Database/SchemaTab/SchemaTab.test.tsx | 62 +--------- .../SchemaTable/SchemaTable.component.tsx | 35 +++++- .../SchemaTable/SchemaTable.interface.ts | 12 +- .../Database/SchemaTable/SchemaTable.test.tsx | 107 +++++++++--------- ...eStatusSummaryIndicator.component.test.tsx | 51 +++++++++ ...stCaseStatusSummaryIndicator.component.tsx | 38 +++++++ ...estCaseStatusSummaryIndicator.interface.ts | 17 +++ .../common/TestIndicator/test-indicator.less | 4 + .../ui/src/locale/languages/de-de.json | 1 + .../ui/src/locale/languages/en-us.json | 1 + .../ui/src/locale/languages/es-es.json | 1 + .../ui/src/locale/languages/fr-fr.json | 1 + .../ui/src/locale/languages/he-he.json | 1 + .../ui/src/locale/languages/ja-jp.json | 1 + .../ui/src/locale/languages/nl-nl.json | 1 + .../ui/src/locale/languages/pt-br.json | 1 + .../ui/src/locale/languages/ru-ru.json | 1 + .../ui/src/locale/languages/zh-cn.json | 1 + .../TableDetailsPageV1.test.tsx | 2 +- .../TableDetailsPageV1/TableDetailsPageV1.tsx | 24 ++-- .../resources/ui/src/utils/EntityLink.test.ts | 22 ++++ .../main/resources/ui/src/utils/EntityLink.ts | 11 ++ .../main/resources/ui/src/utils/FeedUtils.tsx | 3 + 30 files changed, 330 insertions(+), 213 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/common/TestCaseStatusSummaryIndicator/TestCaseStatusSummaryIndicator.component.test.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/common/TestCaseStatusSummaryIndicator/TestCaseStatusSummaryIndicator.component.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/common/TestCaseStatusSummaryIndicator/TestCaseStatusSummaryIndicator.interface.ts diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/TableProfiler/ColumnProfileTable/ColumnProfileTable.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/TableProfiler/ColumnProfileTable/ColumnProfileTable.tsx index 651032f1c3f..73f5c07e161 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/TableProfiler/ColumnProfileTable/ColumnProfileTable.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/TableProfiler/ColumnProfileTable/ColumnProfileTable.tsx @@ -29,7 +29,7 @@ import { DateRangeObject } from 'Models'; import Qs from 'qs'; import React, { useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { useHistory, useLocation } from 'react-router-dom'; +import { Link, useHistory, useLocation } from 'react-router-dom'; import { ReactComponent as DropDownIcon } from '../../../../../assets/svg/drop-down.svg'; import { ReactComponent as SettingIcon } from '../../../../../assets/svg/ic-settings-primery.svg'; import { PAGE_SIZE_LARGE } from '../../../../../constants/constants'; @@ -50,6 +50,7 @@ import { getEntityName, searchInColumns, } from '../../../../../utils/EntityUtils'; +import { getEntityColumnFQN } from '../../../../../utils/FeedUtils'; import { getAddCustomMetricPath, getAddDataQualityTableTestPath, @@ -65,6 +66,7 @@ import { SummaryCard } from '../../../../common/SummaryCard/SummaryCard.componen import { SummaryCardProps } from '../../../../common/SummaryCard/SummaryCard.interface'; import Table from '../../../../common/Table/Table'; import TabsLabel from '../../../../common/TabsLabel/TabsLabel.component'; +import TestCaseStatusSummaryIndicator from '../../../../common/TestCaseStatusSummaryIndicator/TestCaseStatusSummaryIndicator.component'; import PageHeader from '../../../../PageHeader/PageHeader.component'; import { TableProfilerTab } from '../../ProfilerDashboard/profilerDashboard.interface'; import ColumnPickerMenu from '../ColumnPickerMenu'; @@ -91,7 +93,12 @@ const ColumnProfileTable = () => { tableProfiler, dateRangeObject, onDateRangeChange, + table, } = useTableProfiler(); + const testCaseCounts = useMemo( + () => table?.testSuite?.summary?.columnTestSummary ?? [], + [table] + ); const isLoading = isTestsLoading || isProfilerDataLoading; const columns = tableProfiler?.columns ?? []; const [searchText, setSearchText] = useState(''); @@ -211,8 +218,50 @@ const ColumnProfileTable = () => { sorter: (col1, col2) => (col1.profile?.valuesCount || 0) - (col2.profile?.valuesCount || 0), }, + { + title: t('label.test-plural'), + dataIndex: 'testCount', + key: 'Tests', + render: (_, record) => { + const testCounts = testCaseCounts.find((column) => { + return isEqual( + getEntityColumnFQN(column.entityLink ?? ''), + record.fullyQualifiedName + ); + }); + + return ( + + {testCounts?.total ?? 0} + + ); + }, + }, + { + title: t('label.status'), + dataIndex: 'dataQualityTest', + key: 'dataQualityTest', + render: (_, record) => { + const testCounts = testCaseCounts.find((column) => { + return isEqual( + getEntityColumnFQN(column.entityLink ?? ''), + record.fullyQualifiedName + ); + }); + + return ( + + ); + }, + }, ]; - }, [columns]); + }, [columns, testCaseCounts]); const selectedColumn = useMemo(() => { return find( diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/TableProfiler/QualityTab/QualityTab.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/TableProfiler/QualityTab/QualityTab.component.tsx index e7e15380998..1a68c6291c7 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/TableProfiler/QualityTab/QualityTab.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/TableProfiler/QualityTab/QualityTab.component.tsx @@ -12,9 +12,8 @@ */ import { DownOutlined } from '@ant-design/icons'; import { Button, Col, Dropdown, Form, Row, Select, Space, Tabs } from 'antd'; -import { AxiosError } from 'axios'; -import { isEmpty, isUndefined } from 'lodash'; -import React, { useEffect, useMemo, useState } from 'react'; +import { isEmpty } from 'lodash'; +import React, { useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useHistory } from 'react-router-dom'; import { getEntityDetailsPath } from '../../../../../constants/constants'; @@ -24,23 +23,16 @@ import { TEST_CASE_TYPE_OPTION, } from '../../../../../constants/profiler.constant'; import { INITIAL_TEST_SUMMARY } from '../../../../../constants/TestSuite.constant'; -import { - EntityTabs, - EntityType, - TabSpecificField, -} from '../../../../../enums/entity.enum'; +import { EntityTabs, EntityType } from '../../../../../enums/entity.enum'; import { ProfilerDashboardType } from '../../../../../enums/table.enum'; -import { Table } from '../../../../../generated/entity/data/table'; import { TestCaseStatus } from '../../../../../generated/tests/testCase'; import { useFqn } from '../../../../../hooks/useFqn'; -import { getTableDetailsByFQN } from '../../../../../rest/tableAPI'; import { TestCaseType } from '../../../../../rest/testAPI'; import { getBreadcrumbForTable, getEntityName, } from '../../../../../utils/EntityUtils'; import { getAddDataQualityTableTestPath } from '../../../../../utils/RouterUtils'; -import { showErrorToast } from '../../../../../utils/ToastUtils'; import NextPrevious from '../../../../common/NextPrevious/NextPrevious'; import { NextPreviousProps } from '../../../../common/NextPrevious/NextPrevious.interface'; import TabsLabel from '../../../../common/TabsLabel/TabsLabel.component'; @@ -60,6 +52,7 @@ export const QualityTab = () => { isTestsLoading, isTableDeleted, testCasePaging, + table, } = useTableProfiler(); const { @@ -79,8 +72,6 @@ export const QualityTab = () => { const [selectedTestCaseStatus, setSelectedTestCaseStatus] = useState('' as TestCaseStatus); const [selectedTestType, setSelectedTestType] = useState(TestCaseType.all); - const [table, setTable] = useState(); - const [isTestSuiteLoading, setIsTestSuiteLoading] = useState(true); const testSuite = useMemo(() => table?.testSuite, [table]); const handleTestCasePageChange: NextPreviousProps['pagingHandler'] = ({ @@ -207,28 +198,6 @@ export const QualityTab = () => { [] ); - const fetchTestSuiteDetails = async () => { - setIsTestSuiteLoading(true); - try { - const details = await getTableDetailsByFQN(datasetFQN, { - fields: TabSpecificField.TESTSUITE, - }); - setTable(details); - } catch (error) { - showErrorToast(error as AxiosError); - } finally { - setIsTestSuiteLoading(false); - } - }; - - useEffect(() => { - if (isUndefined(testSuite)) { - fetchTestSuiteDetails(); - } else { - setIsTestSuiteLoading(false); - } - }, [testSuite]); - return ( @@ -280,7 +249,6 @@ export const QualityTab = () => { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/TableProfiler/TableProfiler.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/TableProfiler/TableProfiler.interface.ts index 48576d9746f..a9897884734 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/TableProfiler/TableProfiler.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/TableProfiler/TableProfiler.interface.ts @@ -29,8 +29,8 @@ import { UsePagingInterface } from '../../../../hooks/paging/usePaging'; import { ListTestCaseParams } from '../../../../rest/testAPI'; export interface TableProfilerProps { - isTableDeleted?: boolean; permissions: OperationPermission; + table?: Table; } export interface TableProfilerProviderProps extends TableProfilerProps { @@ -54,6 +54,7 @@ export interface TableProfilerContextInterface { dateRangeObject: DateRangeObject; onDateRangeChange: (dateRange: DateRangeObject) => void; testCasePaging: UsePagingInterface; + table?: Table; } export type TableTestsType = { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/TableProfiler/TableProfilerProvider.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/TableProfiler/TableProfilerProvider.test.tsx index ed1d0fc5c9e..8c08021ba5c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/TableProfiler/TableProfilerProvider.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/TableProfiler/TableProfilerProvider.test.tsx @@ -14,6 +14,7 @@ import { render, screen } from '@testing-library/react'; import React from 'react'; import { OperationPermission } from '../../../../context/PermissionProvider/PermissionProvider.interface'; +import { MOCK_TABLE } from '../../../../mocks/TableData.mock'; import { getListTestCase } from '../../../../rest/testAPI'; import { TableProfilerProvider } from './TableProfilerProvider'; @@ -69,27 +70,20 @@ const mockPermissions = { } as OperationPermission; describe('TableProfilerProvider', () => { - it('renders children without crashing', async () => { + beforeEach(() => { render( - +
Test Children
); + }); + it('renders children without crashing', async () => { expect(await screen.findByText('Test Children')).toBeInTheDocument(); }); it('test cases should be fetch on data quality tab', async () => { const mockGetListTestCase = getListTestCase as jest.Mock; - render( - -
Test Children
-
- ); expect(mockGetListTestCase).toHaveBeenCalledTimes(1); expect(mockGetListTestCase).toHaveBeenCalledWith({ diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/TableProfiler/TableProfilerProvider.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/TableProfiler/TableProfilerProvider.tsx index 06a7df8f639..209f0e900cb 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/TableProfiler/TableProfilerProvider.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/TableProfiler/TableProfilerProvider.tsx @@ -58,7 +58,7 @@ export const TableProfilerContext = export const TableProfilerProvider = ({ children, permissions, - isTableDeleted, + table, }: TableProfilerProviderProps) => { const { t } = useTranslation(); const { fqn: datasetFQN } = useFqn(); @@ -76,6 +76,8 @@ export const TableProfilerProvider = ({ const [dateRangeObject, setDateRangeObject] = useState(DEFAULT_RANGE_DATA); + const isTableDeleted = useMemo(() => table?.deleted, [table]); + const { activeTab = isTourOpen ? TableProfilerTab.COLUMN_PROFILE @@ -269,6 +271,7 @@ export const TableProfilerProvider = ({ onDateRangeChange: handleDateRangeChange, dateRangeObject, testCasePaging, + table, }; }, [ isTestsLoading, @@ -282,6 +285,7 @@ export const TableProfilerProvider = ({ customMetric, dateRangeObject, testCasePaging, + table, ]); return ( diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Database/SchemaTab/SchemaTab.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Database/SchemaTab/SchemaTab.component.tsx index 4371bc74302..01177261838 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Database/SchemaTab/SchemaTab.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Database/SchemaTab/SchemaTab.component.tsx @@ -19,16 +19,12 @@ import SchemaTable from '../SchemaTable/SchemaTable.component'; import { Props } from './SchemaTab.interfaces'; const SchemaTab: FunctionComponent = ({ - columns, - joins, + table, onUpdate, - columnName, hasDescriptionEditAccess, hasTagEditAccess, onThreadLinkSelect, isReadOnly = false, - entityFqn, - tableConstraints, }: Props) => { const [searchText, setSearchText] = useState(''); @@ -42,7 +38,7 @@ const SchemaTab: FunctionComponent = ({
= ({
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Database/SchemaTab/SchemaTab.interfaces.ts b/openmetadata-ui/src/main/resources/ui/src/components/Database/SchemaTab/SchemaTab.interfaces.ts index 5797911565b..e2eab6c4905 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Database/SchemaTab/SchemaTab.interfaces.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/Database/SchemaTab/SchemaTab.interfaces.ts @@ -12,22 +12,13 @@ */ import { ThreadType } from '../../../generated/api/feed/createThread'; -import { - ColumnJoins, - Table, - TableData, -} from '../../../generated/entity/data/table'; +import { Table } from '../../../generated/entity/data/table'; export type Props = { - columns: Table['columns']; - joins: Array; - columnName: string; - tableConstraints: Table['tableConstraints']; - sampleData?: TableData; + table?: Table; hasDescriptionEditAccess: boolean; hasTagEditAccess: boolean; isReadOnly?: boolean; - entityFqn: string; onThreadLinkSelect: (value: string, threadType?: ThreadType) => void; onUpdate: (columns: Table['columns']) => Promise; }; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Database/SchemaTab/SchemaTab.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Database/SchemaTab/SchemaTab.test.tsx index 9f3131afd3f..aab789455c0 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Database/SchemaTab/SchemaTab.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Database/SchemaTab/SchemaTab.test.tsx @@ -14,57 +14,11 @@ import { getByTestId, getByText, render } from '@testing-library/react'; import React from 'react'; import { MemoryRouter } from 'react-router-dom'; -import { - Column, - DataType, - LabelType, - State, - Table, - TagSource, -} from '../../../generated/entity/data/table'; +import { MOCK_TABLE } from '../../../mocks/TableData.mock'; import SchemaTab from './SchemaTab.component'; -const mockColumns: Column[] = [ - { - name: 'testId', - dataType: DataType.String, - description: 'string', - fullyQualifiedName: 'string', - tags: [ - { - tagFQN: 'string', - labelType: LabelType.Manual, - source: TagSource.Classification, - state: State.Confirmed, - }, - { - tagFQN: 'string2', - labelType: LabelType.Derived, - source: TagSource.Classification, - state: State.Confirmed, - }, - ], - ordinalPosition: 2, - }, -]; - -const mockjoins = [ - { - columnName: 'testId', - joinedWith: [{ fullyQualifiedName: 'joinedTable', joinCount: 1 }], - }, -]; const mockUpdate = jest.fn(); -const mockSampleData = { - columns: ['column1', 'column2', 'column3'], - rows: [ - ['row1', 'row2', 'row3'], - ['row1', 'row2', 'row3'], - ['row1', 'row2', 'row3'], - ], -}; - jest.mock('../SampleDataTable/SampleDataTable.component', () => { return jest.fn().mockReturnValue(

SampleDataTable

); }); @@ -73,26 +27,14 @@ jest.mock('../SchemaTable/SchemaTable.component', () => { return jest.fn().mockReturnValue(

SchemaTable

); }); -const mockTableConstraints = [ - { - constraintType: 'PRIMARY_KEY', - columns: ['address_id', 'shop_id'], - }, -] as Table['tableConstraints']; - describe('Test SchemaTab Component', () => { it('Renders all the parts of the schema tab', () => { const { queryByTestId, container } = render( , diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Database/SchemaTable/SchemaTable.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Database/SchemaTable/SchemaTable.component.tsx index 53c3e08ae29..54938f2de34 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Database/SchemaTable/SchemaTable.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Database/SchemaTable/SchemaTable.component.tsx @@ -19,6 +19,7 @@ import { cloneDeep, groupBy, isEmpty, + isEqual, isUndefined, set, sortBy, @@ -52,6 +53,7 @@ import { getFrequentlyJoinedColumns, searchInColumns, } from '../../../utils/EntityUtils'; +import { getEntityColumnFQN } from '../../../utils/FeedUtils'; import { getAllTags, searchTagInData, @@ -66,6 +68,7 @@ import { import { showErrorToast } from '../../../utils/ToastUtils'; import FilterTablePlaceHolder from '../../common/ErrorWithPlaceholder/FilterTablePlaceHolder'; import Table from '../../common/Table/Table'; +import TestCaseStatusSummaryIndicator from '../../common/TestCaseStatusSummaryIndicator/TestCaseStatusSummaryIndicator.component'; import EntityNameModal from '../../Modals/EntityNameModal/EntityNameModal.component'; import { EntityName } from '../../Modals/EntityNameModal/EntityNameModal.interface'; import { ModalWithMarkdownEditor } from '../../Modals/ModalWithMarkdownEditor/ModalWithMarkdownEditor'; @@ -75,18 +78,25 @@ import TableTags from '../TableTags/TableTags.component'; import { SchemaTableProps, TableCellRendered } from './SchemaTable.interface'; const SchemaTable = ({ - tableColumns, searchText, onUpdate, hasDescriptionEditAccess, hasTagEditAccess, - joins, isReadOnly = false, onThreadLinkSelect, - tableConstraints, + table, }: SchemaTableProps) => { const { theme } = useApplicationStore(); const { t } = useTranslation(); + const { testCaseCounts, tableColumns, joins, tableConstraints } = useMemo( + () => ({ + testCaseCounts: table?.testSuite?.summary?.columnTestSummary ?? [], + tableColumns: table?.columns ?? [], + joins: table?.joins?.columnJoins ?? [], + tableConstraints: table?.tableConstraints, + }), + [table] + ); const [searchedColumns, setSearchedColumns] = useState([]); const [expandedRowKeys, setExpandedRowKeys] = useState([]); @@ -455,6 +465,24 @@ const SchemaTable = ({ filterDropdown: ColumnFilter, onFilter: searchTagInData, }, + { + title: t('label.data-quality-test-plural'), + dataIndex: 'dataQualityTest', + key: 'dataQualityTest', + width: 170, + render: (_, record) => { + const testCounts = testCaseCounts.find((column) => { + return isEqual( + getEntityColumnFQN(column.entityLink ?? ''), + record.fullyQualifiedName + ); + }); + + return ( + + ); + }, + }, ], [ decodedEntityFqn, @@ -468,6 +496,7 @@ const SchemaTable = ({ handleTagSelection, onThreadLinkSelect, tagFilter, + testCaseCounts, ] ); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Database/SchemaTable/SchemaTable.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/Database/SchemaTable/SchemaTable.interface.ts index 1b7f643e901..186172bc68f 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Database/SchemaTable/SchemaTable.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/Database/SchemaTable/SchemaTable.interface.ts @@ -13,24 +13,16 @@ import { ReactNode } from 'react'; import { ThreadType } from '../../../generated/api/feed/createThread'; -import { - Column, - ColumnJoins, - Table, -} from '../../../generated/entity/data/table'; +import { Column, Table } from '../../../generated/entity/data/table'; export interface SchemaTableProps { - tableColumns: Column[]; - joins: Array; - columnName: string; hasDescriptionEditAccess: boolean; hasTagEditAccess: boolean; - tableConstraints: Table['tableConstraints']; searchText?: string; isReadOnly?: boolean; - entityFqn: string; onUpdate: (columns: Column[]) => Promise; onThreadLinkSelect: (value: string, threadType?: ThreadType) => void; + table?: Table; } export type TableCellRendered = ( diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Database/SchemaTable/SchemaTable.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Database/SchemaTable/SchemaTable.test.tsx index 95479d4551e..4625e2a293d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Database/SchemaTable/SchemaTable.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Database/SchemaTable/SchemaTable.test.tsx @@ -15,10 +15,11 @@ import { render, screen } from '@testing-library/react'; import React from 'react'; import { MemoryRouter } from 'react-router-dom'; import { Column } from '../../../generated/entity/data/container'; -import { Table, TablePartition } from '../../../generated/entity/data/table'; +import { Table } from '../../../generated/entity/data/table'; +import { MOCK_TABLE } from '../../../mocks/TableData.mock'; import EntityTableV1 from './SchemaTable.component'; +import { SchemaTableProps } from './SchemaTable.interface'; -const onEntityFieldSelect = jest.fn(); const onThreadLinkSelect = jest.fn(); const onUpdate = jest.fn(); @@ -28,60 +29,52 @@ const mockTableConstraints = [ columns: ['address_id', 'shop_id'], }, ] as Table['tableConstraints']; +const columns = [ + { + name: 'comments', + dataType: 'STRING', + dataLength: 1, + dataTypeDisplay: 'string', + fullyQualifiedName: + 'bigquery_gcp.ecommerce.shopify.raw_product_catalog.comments', + tags: [], + constraint: 'NULL', + ordinalPosition: 1, + }, + { + name: 'products', + dataType: 'ARRAY', + arrayDataType: 'STRUCT', + dataLength: 1, + dataTypeDisplay: + 'array>', + fullyQualifiedName: + 'bigquery_gcp.ecommerce.shopify.raw_product_catalog.products', + tags: [], + constraint: 'NULL', + ordinalPosition: 2, + }, + { + name: 'platform', + dataType: 'STRING', + dataLength: 1, + dataTypeDisplay: 'string', + fullyQualifiedName: + 'bigquery_gcp.ecommerce.shopify.raw_product_catalog.platform', + tags: [], + constraint: 'NULL', + ordinalPosition: 3, + }, +] as Column[]; -const mockEntityTableProp = { - tableColumns: [ - { - name: 'comments', - dataType: 'STRING', - dataLength: 1, - dataTypeDisplay: 'string', - fullyQualifiedName: - 'bigquery_gcp.ecommerce.shopify.raw_product_catalog.comments', - tags: [], - constraint: 'NULL', - ordinalPosition: 1, - }, - { - name: 'products', - dataType: 'ARRAY', - arrayDataType: 'STRUCT', - dataLength: 1, - dataTypeDisplay: - 'array>', - fullyQualifiedName: - 'bigquery_gcp.ecommerce.shopify.raw_product_catalog.products', - tags: [], - constraint: 'NULL', - ordinalPosition: 2, - }, - { - name: 'platform', - dataType: 'STRING', - dataLength: 1, - dataTypeDisplay: 'string', - fullyQualifiedName: - 'bigquery_gcp.ecommerce.shopify.raw_product_catalog.platform', - tags: [], - constraint: 'NULL', - ordinalPosition: 3, - }, - ] as Column[], +const mockEntityTableProp: SchemaTableProps = { searchText: '', - hasEditAccess: false, - joins: [], - entityFieldThreads: [], hasDescriptionEditAccess: true, isReadOnly: false, - entityFqn: 'bigquery_gcp.ecommerce.shopify.raw_product_catalog', - owner: {} as Table['owner'], - columnName: '', hasTagEditAccess: true, - tableConstraints: mockTableConstraints, - tablePartitioned: {} as TablePartition, - onEntityFieldSelect, onThreadLinkSelect, onUpdate, + table: { ...MOCK_TABLE, columns, tableConstraints: mockTableConstraints }, }; const columnsWithDisplayName = [ @@ -201,9 +194,15 @@ describe('Test EntityTable Component', () => { }); it('Table should load empty when no data present', async () => { - render(, { - wrapper: MemoryRouter, - }); + render( + , + { + wrapper: MemoryRouter, + } + ); const entityTable = await screen.findByTestId('entity-table'); @@ -232,7 +231,7 @@ describe('Test EntityTable Component', () => { render( , { wrapper: MemoryRouter, @@ -256,7 +255,7 @@ describe('Test EntityTable Component', () => { , { wrapper: MemoryRouter, diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/TestCaseStatusSummaryIndicator/TestCaseStatusSummaryIndicator.component.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/TestCaseStatusSummaryIndicator/TestCaseStatusSummaryIndicator.component.test.tsx new file mode 100644 index 00000000000..992d1edb246 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/TestCaseStatusSummaryIndicator/TestCaseStatusSummaryIndicator.component.test.tsx @@ -0,0 +1,51 @@ +/* + * Copyright 2024 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 { render, screen } from '@testing-library/react'; +import React from 'react'; +import TestCaseStatusSummaryIndicator from './TestCaseStatusSummaryIndicator.component'; + +jest.mock('../TestIndicator/TestIndicator', () => { + return jest + .fn() + .mockImplementation(({ type }) =>
{`test-indicator-${type}`}
); +}); + +describe('TestCaseStatusSummaryIndicator', () => { + it('should render test indicators for each test status', () => { + const testCaseStatusCounts = { + success: 5, + failed: 2, + aborted: 3, + total: 10, + queued: 0, + entityLink: 'test', + }; + + render( + + ); + + expect(screen.getByText('test-indicator-success')).toBeInTheDocument(); + expect(screen.getByText('test-indicator-failed')).toBeInTheDocument(); + expect(screen.getByText('test-indicator-aborted')).toBeInTheDocument(); + expect(screen.getByText('test-indicator-queued')).toBeInTheDocument(); + }); + + it('should render no data placeholder when testCaseStatusCounts is null', () => { + render(); + + expect(screen.getByTestId('no-data-placeholder')).toBeInTheDocument(); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/TestCaseStatusSummaryIndicator/TestCaseStatusSummaryIndicator.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/TestCaseStatusSummaryIndicator/TestCaseStatusSummaryIndicator.component.tsx new file mode 100644 index 00000000000..5e0448d9181 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/TestCaseStatusSummaryIndicator/TestCaseStatusSummaryIndicator.component.tsx @@ -0,0 +1,38 @@ +/* + * Copyright 2024 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 { Space, Typography } from 'antd'; +import { omit } from 'lodash'; +import React from 'react'; +import { NO_DATA_PLACEHOLDER } from '../../../constants/constants'; +import TestIndicator from '../TestIndicator/TestIndicator'; +import { TestCaseStatusSummaryIndicatorProps } from './TestCaseStatusSummaryIndicator.interface'; + +const TestCaseStatusSummaryIndicator = ({ + testCaseStatusCounts, +}: TestCaseStatusSummaryIndicatorProps) => { + return testCaseStatusCounts ? ( + + {Object.entries(omit(testCaseStatusCounts, ['entityLink', 'total'])).map( + (test) => ( + + ) + )} + + ) : ( + + {NO_DATA_PLACEHOLDER} + + ); +}; + +export default TestCaseStatusSummaryIndicator; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/TestCaseStatusSummaryIndicator/TestCaseStatusSummaryIndicator.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/common/TestCaseStatusSummaryIndicator/TestCaseStatusSummaryIndicator.interface.ts new file mode 100644 index 00000000000..2f3d00fef4e --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/TestCaseStatusSummaryIndicator/TestCaseStatusSummaryIndicator.interface.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2024 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 { ColumnTestSummaryDefinition } from '../../../generated/tests/testCase'; + +export interface TestCaseStatusSummaryIndicatorProps { + testCaseStatusCounts?: ColumnTestSummaryDefinition; +} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/TestIndicator/test-indicator.less b/openmetadata-ui/src/main/resources/ui/src/components/common/TestIndicator/test-indicator.less index 72b6482f5f2..cf1eacadff5 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/TestIndicator/test-indicator.less +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/TestIndicator/test-indicator.less @@ -41,4 +41,8 @@ background: @abortedColor; border: 1px solid @abort-border; } + &.queued { + background: @grey-1; + border: 1px solid @grey-3; + } } diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/de-de.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/de-de.json index 4db272d8a7d..022d069a57b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/de-de.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/de-de.json @@ -280,6 +280,7 @@ "data-proportion-plural": "Datenverhältnisse", "data-quality": "Datenqualität", "data-quality-test": "Datenqualitätstest", + "data-quality-test-plural": "Data Quality Tests", "data-quartile-plural": "Datenquartile", "data-range": "Datenbereich", "data-type": "Datentyp", 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 62c16063cd6..180b4779ec0 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 @@ -280,6 +280,7 @@ "data-proportion-plural": "Data Proportions", "data-quality": "Data Quality", "data-quality-test": "Data Quality Test", + "data-quality-test-plural": "Data Quality Tests", "data-quartile-plural": "Data Quartiles", "data-range": "Data Range", "data-type": "Data Type", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/es-es.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/es-es.json index 0b54e05c47a..e4be11f3136 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/es-es.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/es-es.json @@ -280,6 +280,7 @@ "data-proportion-plural": "Proporciones de datos", "data-quality": "Tests de calidad", "data-quality-test": "Test de calidad de datos", + "data-quality-test-plural": "Data Quality Tests", "data-quartile-plural": "cuartiles", "data-range": "Rango de datos", "data-type": "Tipo de datos", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json index 61faf205754..f0a86716fb2 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json @@ -280,6 +280,7 @@ "data-proportion-plural": "Proportions des Données", "data-quality": "Qualité des Données", "data-quality-test": "Test de Qualité des Données", + "data-quality-test-plural": "Data Quality Tests", "data-quartile-plural": "Quartiles des Données", "data-range": "Plage de Données", "data-type": "Type de Données", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/he-he.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/he-he.json index d679bbecf8b..bd0cb905460 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/he-he.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/he-he.json @@ -280,6 +280,7 @@ "data-proportion-plural": "יחסי נתונים", "data-quality": "איכות נתונים", "data-quality-test": "בקרת איכות נתונים", + "data-quality-test-plural": "Data Quality Tests", "data-quartile-plural": "רביעונים (Quaertiles)", "data-range": "טווח נתונים", "data-type": "סוג עמודה", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ja-jp.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ja-jp.json index 589dc0c5684..38f636054b8 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ja-jp.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ja-jp.json @@ -280,6 +280,7 @@ "data-proportion-plural": "Data Proportions", "data-quality": "Data Quality", "data-quality-test": "データ品質テスト", + "data-quality-test-plural": "Data Quality Tests", "data-quartile-plural": "Data Quartiles", "data-range": "Data Range", "data-type": "データ型", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/nl-nl.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/nl-nl.json index 9c39cb8a9a4..4c17487c1d6 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/nl-nl.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/nl-nl.json @@ -280,6 +280,7 @@ "data-proportion-plural": "Dataproporties", "data-quality": "Datakwaliteit", "data-quality-test": "Datakwaliteitstest", + "data-quality-test-plural": "Data Quality Tests", "data-quartile-plural": "Datakwartielen", "data-range": "Datasbereik", "data-type": "Datatype", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-br.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-br.json index f311e12bbff..abf87c6da86 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-br.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-br.json @@ -280,6 +280,7 @@ "data-proportion-plural": "Proporções de Dados", "data-quality": "Qualidade de Dados", "data-quality-test": "Teste de Qualidade de Dados", + "data-quality-test-plural": "Data Quality Tests", "data-quartile-plural": "Quartis de Dados", "data-range": "Intervalo de Dados", "data-type": "Tipo de Dados", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ru-ru.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ru-ru.json index 33e451780ef..a98d83f535e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ru-ru.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ru-ru.json @@ -280,6 +280,7 @@ "data-proportion-plural": "Распределение данных", "data-quality": "Качество данных", "data-quality-test": "Тест качества данных", + "data-quality-test-plural": "Data Quality Tests", "data-quartile-plural": "Качество данных", "data-range": "Временной интервал", "data-type": "Тип данных", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json index 65a228232e8..12c6fde37cb 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json @@ -280,6 +280,7 @@ "data-proportion-plural": "数据比例", "data-quality": "数据质控", "data-quality-test": "数据质控测试", + "data-quality-test-plural": "Data Quality Tests", "data-quartile-plural": "数据四分位数", "data-range": "数据范围", "data-type": "数据类型", diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/TableDetailsPageV1/TableDetailsPageV1.test.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/TableDetailsPageV1/TableDetailsPageV1.test.tsx index 80be9e4b90d..d5738f7c4f7 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/TableDetailsPageV1/TableDetailsPageV1.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/TableDetailsPageV1/TableDetailsPageV1.test.tsx @@ -244,7 +244,7 @@ describe('TestDetailsPageV1 component', () => { }); expect(getTableDetailsByFQN).toHaveBeenCalledWith('fqn', { - fields: `${COMMON_API_FIELDS},usageSummary`, + fields: `${COMMON_API_FIELDS},usageSummary,testSuite`, }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/TableDetailsPageV1/TableDetailsPageV1.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/TableDetailsPageV1/TableDetailsPageV1.tsx index e554f223669..8bdae5ed187 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/TableDetailsPageV1/TableDetailsPageV1.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/TableDetailsPageV1/TableDetailsPageV1.tsx @@ -132,8 +132,13 @@ const TableDetailsPageV1: React.FC = () => { datasetFQN ); - const viewUsagePermission = useMemo( - () => tablePermissions.ViewAll || tablePermissions.ViewUsage, + const { viewUsagePermission, viewTestCasePermission } = useMemo( + () => ({ + viewUsagePermission: + tablePermissions.ViewAll || tablePermissions.ViewUsage, + viewTestCasePermission: + tablePermissions.ViewAll || tablePermissions.ViewTests, + }), [tablePermissions] ); @@ -154,6 +159,9 @@ const TableDetailsPageV1: React.FC = () => { if (viewUsagePermission) { fields += `,${TabSpecificField.USAGE_SUMMARY}`; } + if (viewTestCasePermission) { + fields += `,${TabSpecificField.TESTSUITE}`; + } const details = await getTableDetailsByFQN(tableFqn, { fields }); @@ -528,18 +536,10 @@ const TableDetailsPageV1: React.FC = () => { onThreadLinkSelect={onThreadLinkSelect} /> @@ -679,8 +679,8 @@ const TableDetailsPageV1: React.FC = () => { ) : ( ), }, diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/EntityLink.test.ts b/openmetadata-ui/src/main/resources/ui/src/utils/EntityLink.test.ts index f939b1a94de..c0c088595cf 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/EntityLink.test.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/EntityLink.test.ts @@ -16,6 +16,8 @@ const entityLink = '<#E::table::sample_data.ecommerce_db.shopify.dim_address::description>'; const entityLinkWithColumn = '<#E::table::sample_data.ecommerce_db.shopify.dim_address::columns::address_id::tags>'; +const entityLinkWithNestedColumn = + '<#E::table::sample_data.ecommerce_db.shopify.dim_address::columns::"address_id.city"::tags>'; describe('Test EntityLink', () => { it('Should split the entityLink into parts', () => { @@ -81,4 +83,24 @@ describe('Test EntityLink', () => { '<#E::table::sample_data.ecommerce_db.shopify.dim_address>' ); }); + + it('Should return entityFqn from entityLink', () => { + expect(EntityLink.getEntityColumnFqn(entityLink)).toStrictEqual( + 'sample_data.ecommerce_db.shopify.dim_address' + ); + }); + + it('Should return entityColumnFqn from entityLink for column', () => { + expect(EntityLink.getEntityColumnFqn(entityLinkWithColumn)).toStrictEqual( + 'sample_data.ecommerce_db.shopify.dim_address.address_id' + ); + }); + + it('Should return entityColumnFqn from entityLink for nested column', () => { + expect( + EntityLink.getEntityColumnFqn(entityLinkWithNestedColumn) + ).toStrictEqual( + 'sample_data.ecommerce_db.shopify.dim_address."address_id.city"' + ); + }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/EntityLink.ts b/openmetadata-ui/src/main/resources/ui/src/utils/EntityLink.ts index 60d491f1809..5107d526456 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/EntityLink.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/EntityLink.ts @@ -57,6 +57,17 @@ export default class EntityLink { return this.split(entityLink)[1]; } + /** + * + * @param string entityLink column + * @returns entityColumn fqn + */ + static getEntityColumnFqn(entityLink: string) { + const parts = this.split(entityLink); + + return `${parts[1]}${parts[3] ? '.' + parts[3] : ''}`; + } + /** * * @param string entityLink diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/FeedUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/FeedUtils.tsx index ea78c62a104..badc4bff248 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/FeedUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/FeedUtils.tsx @@ -74,6 +74,9 @@ export const getEntityType = (entityLink: string) => { export const getEntityFQN = (entityLink: string) => { return EntityLink.getEntityFqn(entityLink); }; +export const getEntityColumnFQN = (entityLink: string) => { + return EntityLink.getEntityColumnFqn(entityLink); +}; export const getEntityField = (entityLink: string) => { const match = EntityRegEx.exec(entityLink);