mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-28 02:46:09 +00:00
fix(ui)#9846: row count not populating on table details page (#9896)
* fixed rows not populating in table info added skeleton loaders for rowCount and columnCount styling and localization fixes * added unit tests for checking rowCount and columnCount
This commit is contained in:
parent
74d2132883
commit
d776224bcf
@ -11,7 +11,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Col, Row } from 'antd';
|
||||
import { Col, Row, Skeleton, Space, Typography } from 'antd';
|
||||
import { AxiosError } from 'axios';
|
||||
import classNames from 'classnames';
|
||||
import { isEqual, isNil, isUndefined } from 'lodash';
|
||||
@ -31,6 +31,7 @@ import {
|
||||
JoinedWith,
|
||||
Table,
|
||||
TableJoins,
|
||||
TableProfile,
|
||||
TypeUsedToReturnUsageDetailsOfAnEntity,
|
||||
} from '../../generated/entity/data/table';
|
||||
import { ThreadType } from '../../generated/entity/feed/thread';
|
||||
@ -127,6 +128,7 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
|
||||
handleExtensionUpdate,
|
||||
updateThreadHandler,
|
||||
entityFieldTaskCount,
|
||||
isTableProfileLoading,
|
||||
}: DatasetDetailsProps) => {
|
||||
const { t } = useTranslation();
|
||||
const history = useHistory();
|
||||
@ -338,37 +340,62 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
|
||||
.sort((a, b) => b.joinCount - a.joinCount);
|
||||
};
|
||||
|
||||
const prepareTableRowInfo = () => {
|
||||
const rowData =
|
||||
([
|
||||
{
|
||||
date: new Date(tableProfile?.timestamp || 0),
|
||||
value: tableProfile?.rowCount ?? 0,
|
||||
},
|
||||
] as Array<{
|
||||
date: Date;
|
||||
value: number;
|
||||
}>) ?? [];
|
||||
|
||||
if (!isUndefined(tableProfile)) {
|
||||
const prepareExtraInfoValues = (
|
||||
key: EntityInfo,
|
||||
isTableProfileLoading?: boolean,
|
||||
tableProfile?: TableProfile,
|
||||
numberOfColumns?: number
|
||||
) => {
|
||||
if (isTableProfileLoading) {
|
||||
return (
|
||||
<div className="tw-flex">
|
||||
{rowData.length > 1 && (
|
||||
<TableProfilerGraph
|
||||
className="tw--mt-4"
|
||||
data={rowData}
|
||||
height={38}
|
||||
toolTipPos={{ x: 20, y: -30 }}
|
||||
/>
|
||||
)}
|
||||
<span
|
||||
className={classNames({
|
||||
'tw--ml-6': rowData.length > 1,
|
||||
})}>{`${tableProfile?.rowCount?.toLocaleString() || 0} rows`}</span>
|
||||
</div>
|
||||
<Skeleton active paragraph={{ rows: 1, width: 50 }} title={false} />
|
||||
);
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
switch (key) {
|
||||
case EntityInfo.COLUMNS: {
|
||||
const columnCount =
|
||||
tableProfile && tableProfile?.columnCount
|
||||
? tableProfile?.columnCount
|
||||
: numberOfColumns
|
||||
? numberOfColumns
|
||||
: undefined;
|
||||
|
||||
return columnCount
|
||||
? `${columns.length} ${t('label.column-plural')}`
|
||||
: null;
|
||||
}
|
||||
|
||||
case EntityInfo.ROWS: {
|
||||
const rowData =
|
||||
([
|
||||
{
|
||||
date: new Date(tableProfile?.timestamp || 0),
|
||||
value: tableProfile?.rowCount ?? 0,
|
||||
},
|
||||
] as Array<{
|
||||
date: Date;
|
||||
value: number;
|
||||
}>) ?? [];
|
||||
|
||||
return isUndefined(tableProfile) ? null : (
|
||||
<Space align="center">
|
||||
{rowData.length > 1 && (
|
||||
<TableProfilerGraph
|
||||
data={rowData}
|
||||
height={32}
|
||||
margin={{ top: 0, right: 0, left: 0, bottom: 0 }}
|
||||
toolTipPos={{ x: 20, y: 30 }}
|
||||
width={120}
|
||||
/>
|
||||
)}
|
||||
<Typography.Paragraph className="m-0">{`${
|
||||
tableProfile?.rowCount?.toLocaleString() || 0
|
||||
} rows`}</Typography.Paragraph>
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
@ -395,16 +422,21 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
|
||||
{ value: `${weeklyUsageCount} ${t('label.query-plural')}` },
|
||||
{
|
||||
key: EntityInfo.COLUMNS,
|
||||
value:
|
||||
tableProfile && tableProfile?.columnCount
|
||||
? `${tableProfile.columnCount} ${t('label.columns-plural')}`
|
||||
: columns.length
|
||||
? `${columns.length} ${t('label.columns-plural')}`
|
||||
: '',
|
||||
localizationKey: 'column-plural',
|
||||
value: prepareExtraInfoValues(
|
||||
EntityInfo.COLUMNS,
|
||||
isTableProfileLoading,
|
||||
tableProfile,
|
||||
columns.length
|
||||
),
|
||||
},
|
||||
{
|
||||
key: EntityInfo.ROWS,
|
||||
value: prepareTableRowInfo(),
|
||||
value: prepareExtraInfoValues(
|
||||
EntityInfo.ROWS,
|
||||
isTableProfileLoading,
|
||||
tableProfile
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -66,6 +66,7 @@ export interface DatasetDetailsProps {
|
||||
slashedTableName: TitleBreadcrumbProps['titleLinks'];
|
||||
entityThread: Thread[];
|
||||
deleted?: boolean;
|
||||
isTableProfileLoading?: boolean;
|
||||
isLineageLoading?: boolean;
|
||||
isSampleDataLoading?: boolean;
|
||||
isQueriesLoading?: boolean;
|
||||
|
@ -203,7 +203,11 @@ const EntitySummaryDetails = ({
|
||||
? `${t(`label.${toLower(data.key)}`)} - `
|
||||
: null
|
||||
: `${t('label.no-entity', {
|
||||
entity: t(`label.${toLower(data.key)}`),
|
||||
entity: t(
|
||||
`label.${toLower(
|
||||
data.localizationKey ? data.localizationKey : data.key
|
||||
)}`
|
||||
),
|
||||
})}`
|
||||
: null}
|
||||
</>
|
||||
|
@ -175,6 +175,7 @@ declare module 'Models' {
|
||||
key?: string;
|
||||
value: string | number | React.ReactNode;
|
||||
id?: string;
|
||||
localizationKey?: string;
|
||||
isLink?: boolean;
|
||||
placeholderText?: string;
|
||||
openInNewTab?: boolean;
|
||||
|
@ -84,7 +84,7 @@
|
||||
"collapse-all": "Collapse All",
|
||||
"column": "Column",
|
||||
"column-entity": "Column {{entity}}",
|
||||
"columns-plural": "Columns",
|
||||
"column-plural": "Columns",
|
||||
"comment-lowercase": "comment",
|
||||
"completed": "Completed",
|
||||
"completed-entity": "Completed {{entity}}",
|
||||
|
@ -33,12 +33,14 @@ import { isEmpty, isUndefined } from 'lodash';
|
||||
import { observer } from 'mobx-react';
|
||||
import { EntityTags } from 'Models';
|
||||
import React, { FunctionComponent, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
import { getAllFeeds, postFeedById, postThread } from 'rest/feedsAPI';
|
||||
import { getLineageByFQN } from 'rest/lineageAPI';
|
||||
import { addLineage, deleteLineageEdge } from 'rest/miscAPI';
|
||||
import {
|
||||
addFollower,
|
||||
getLatestTableProfileByFqn,
|
||||
getTableDetailsByFQN,
|
||||
patchTableDetails,
|
||||
removeFollower,
|
||||
@ -95,6 +97,7 @@ import { showErrorToast } from '../../utils/ToastUtils';
|
||||
|
||||
const DatasetDetailsPage: FunctionComponent = () => {
|
||||
const history = useHistory();
|
||||
const { t } = useTranslation();
|
||||
const { getEntityPermissionByFqn } = usePermissionProvider();
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
const [isLineageLoading, setIsLineageLoading] = useState<boolean>(false);
|
||||
@ -104,6 +107,8 @@ const DatasetDetailsPage: FunctionComponent = () => {
|
||||
useState<boolean>(false);
|
||||
const [isentityThreadLoading, setIsentityThreadLoading] =
|
||||
useState<boolean>(false);
|
||||
const [isTableProfileLoading, setIsTableProfileLoading] =
|
||||
useState<boolean>(false);
|
||||
const USERId = getCurrentUserId();
|
||||
const [tableId, setTableId] = useState('');
|
||||
const [tier, setTier] = useState<TagLabel>();
|
||||
@ -289,7 +294,6 @@ const DatasetDetailsPage: FunctionComponent = () => {
|
||||
joins,
|
||||
tags,
|
||||
sampleData,
|
||||
profile,
|
||||
tableType,
|
||||
version,
|
||||
service,
|
||||
@ -354,7 +358,6 @@ const DatasetDetailsPage: FunctionComponent = () => {
|
||||
setDescription(description ?? '');
|
||||
setColumns(columns || []);
|
||||
setSampleData(sampleData as TableData);
|
||||
setTableProfile(profile);
|
||||
setTableTags(getTagsWithoutTier(tags || []));
|
||||
setUsageSummary(
|
||||
usageSummary as TypeUsedToReturnUsageDetailsOfAnEntity
|
||||
@ -382,6 +385,29 @@ const DatasetDetailsPage: FunctionComponent = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const fetchTableProfileDetails = async () => {
|
||||
if (!isEmpty(tableDetails)) {
|
||||
setIsTableProfileLoading(true);
|
||||
try {
|
||||
const { profile } = await getLatestTableProfileByFqn(
|
||||
tableDetails.fullyQualifiedName ?? ''
|
||||
);
|
||||
|
||||
setTableProfile(profile);
|
||||
} catch (err) {
|
||||
showErrorToast(
|
||||
err as AxiosError,
|
||||
t('server.entity-details-fetch-error', {
|
||||
entityType: t('label.table'),
|
||||
entityName: tableDetails.displayName ?? tableDetails.name,
|
||||
})
|
||||
);
|
||||
} finally {
|
||||
setIsTableProfileLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const fetchTabSpecificData = (tabField = '') => {
|
||||
switch (tabField) {
|
||||
case TabSpecificField.SAMPLE_DATA: {
|
||||
@ -798,6 +824,10 @@ const DatasetDetailsPage: FunctionComponent = () => {
|
||||
}
|
||||
}, [tablePermissions]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchTableProfileDetails();
|
||||
}, [tableDetails]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchResourcePermission(tableFQN);
|
||||
}, [tableFQN]);
|
||||
@ -851,6 +881,7 @@ const DatasetDetailsPage: FunctionComponent = () => {
|
||||
isNodeLoading={isNodeLoading}
|
||||
isQueriesLoading={isTableQueriesLoading}
|
||||
isSampleDataLoading={isSampleDataLoading}
|
||||
isTableProfileLoading={isTableProfileLoading}
|
||||
isentityThreadLoading={isentityThreadLoading}
|
||||
joins={joins}
|
||||
lineageLeafNodes={leafNodes}
|
||||
|
@ -17,6 +17,7 @@ import {
|
||||
findByText,
|
||||
fireEvent,
|
||||
render,
|
||||
screen,
|
||||
} from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { MemoryRouter } from 'react-router';
|
||||
@ -30,6 +31,7 @@ import { getLineageByFQN } from 'rest/lineageAPI';
|
||||
import { addLineage, deleteLineageEdge } from 'rest/miscAPI';
|
||||
import {
|
||||
addFollower,
|
||||
getLatestTableProfileByFqn,
|
||||
getTableDetailsByFQN,
|
||||
patchTableDetails,
|
||||
removeFollower,
|
||||
@ -40,10 +42,13 @@ import {
|
||||
createPostRes,
|
||||
mockFollowRes,
|
||||
mockLineageRes,
|
||||
mockTableProfileResponse,
|
||||
mockUnfollowRes,
|
||||
updateTagRes,
|
||||
} from './datasetDetailsPage.mock';
|
||||
|
||||
const mockShowErrorToast = jest.fn();
|
||||
|
||||
const mockUseParams = {
|
||||
datasetFQN: 'bigquery_gcp:shopify:dim_address',
|
||||
tab: 'schema',
|
||||
@ -68,8 +73,7 @@ jest.mock('../../AppState', () => ({
|
||||
|
||||
jest.mock('components/PermissionProvider/PermissionProvider', () => ({
|
||||
usePermissionProvider: jest.fn().mockImplementation(() => ({
|
||||
permissions: {},
|
||||
getEntityPermission: jest.fn().mockResolvedValue({
|
||||
getEntityPermissionByFqn: jest.fn().mockResolvedValue({
|
||||
Create: true,
|
||||
Delete: true,
|
||||
EditAll: true,
|
||||
@ -146,6 +150,7 @@ jest.mock('components/DatasetDetails/DatasetDetails.component', () => {
|
||||
handleRemoveColumnTest,
|
||||
deletePostHandler,
|
||||
entityLineageHandler,
|
||||
tableProfile,
|
||||
}) => (
|
||||
<div data-testid="datasetdetails-component">
|
||||
<button data-testid="version-button" onClick={versionHandler}>
|
||||
@ -231,6 +236,12 @@ jest.mock('components/DatasetDetails/DatasetDetails.component', () => {
|
||||
onClick={entityLineageHandler}>
|
||||
entityLineageHandler
|
||||
</button>
|
||||
{tableProfile && (
|
||||
<>
|
||||
<div data-testid="rowCount">{tableProfile.rowCount}</div>
|
||||
<div data-testid="columnCount">{tableProfile.columnCount}</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
@ -265,6 +276,19 @@ jest.mock('rest/tableAPI', () => ({
|
||||
removeFollower: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve(mockUnfollowRes)),
|
||||
getLatestTableProfileByFqn: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve(mockTableProfileResponse)),
|
||||
}));
|
||||
|
||||
jest.mock('react-i18next', () => ({
|
||||
useTranslation: jest.fn().mockImplementation(() => ({
|
||||
t: jest.fn().mockImplementation((str) => str),
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('../../utils/ToastUtils', () => ({
|
||||
showErrorToast: jest.fn().mockImplementation(() => mockShowErrorToast()),
|
||||
}));
|
||||
|
||||
jest.mock('../../utils/FeedUtils', () => ({
|
||||
@ -1159,5 +1183,45 @@ describe('Test DatasetDetails page', () => {
|
||||
|
||||
fireEvent.click(deletePostHandler);
|
||||
});
|
||||
|
||||
it('Table profile details should be passed correctly after successful API response', async () => {
|
||||
await act(async () => {
|
||||
render(<DatasetDetailsPage />, {
|
||||
wrapper: MemoryRouter,
|
||||
});
|
||||
});
|
||||
|
||||
const rowCount = screen.getByTestId('rowCount');
|
||||
const columnCount = screen.getByTestId('columnCount');
|
||||
|
||||
expect(rowCount).toBeInTheDocument();
|
||||
expect(columnCount).toBeInTheDocument();
|
||||
|
||||
expect(rowCount).toContainHTML(
|
||||
`${mockTableProfileResponse.profile.rowCount}`
|
||||
);
|
||||
expect(columnCount).toContainHTML(
|
||||
`${mockTableProfileResponse.profile.columnCount}`
|
||||
);
|
||||
});
|
||||
|
||||
it('An error should be thrown if table profile API throws error', async () => {
|
||||
(getLatestTableProfileByFqn as jest.Mock).mockImplementationOnce(() =>
|
||||
Promise.reject()
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
render(<DatasetDetailsPage />, {
|
||||
wrapper: MemoryRouter,
|
||||
});
|
||||
});
|
||||
|
||||
const rowCount = screen.queryByTestId('rowCount');
|
||||
const columnCount = screen.queryByTestId('columnCount');
|
||||
|
||||
expect(rowCount).toBeNull();
|
||||
expect(columnCount).toBeNull();
|
||||
expect(mockShowErrorToast).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -489,3 +489,12 @@ export const mockLineageRes = {
|
||||
upstreamEdges: [],
|
||||
downstreamEdges: [],
|
||||
};
|
||||
|
||||
export const mockTableProfileResponse = {
|
||||
profile: {
|
||||
timestamp: 1674466560,
|
||||
profileSampleType: 'PERCENTAGE',
|
||||
columnCount: 12,
|
||||
rowCount: 14567,
|
||||
},
|
||||
};
|
||||
|
@ -172,7 +172,7 @@ export const getEntityOverview = (
|
||||
isLink: false,
|
||||
},
|
||||
{
|
||||
name: i18next.t('label.columns-plural'),
|
||||
name: i18next.t('label.column-plural'),
|
||||
value: columns ? columns.length : '--',
|
||||
isLink: false,
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user