fix: improvement and fixes around table styles (#20439)

* improvement and fixes around table styles

* fix the glossary table and other table column spacing issue

* fix the border and style issue in ingestion and agents tables

* move the pagination prop is usMemo

* remove the glossary code and fix the playwright test for user and serviceListing
This commit is contained in:
Ashish Gupta 2025-03-27 10:25:22 +05:30 committed by GitHub
parent 953892078c
commit 0b18d7b374
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 205 additions and 85 deletions

View File

@ -40,7 +40,7 @@ test.describe('Service Listing', () => {
test('should render the service listing page', async ({ page }) => {
await page.waitForLoadState('networkidle');
await page.getByRole('button', { name: 'filter' }).click();
await page.getByTestId('filter-icon').click();
const searchBigQueryResponse = page.waitForResponse(
`/api/v1/search/query?q=**%20AND%20(serviceType:BigQuery)&from=0&size=15&index=database_service_search_index`
@ -50,7 +50,7 @@ test.describe('Service Listing', () => {
await expect(page.getByTestId('service-name-sample_data')).toBeVisible();
await page.getByRole('button', { name: 'filter' }).click();
await page.getByTestId('filter-icon').click();
const searchResponse = page.waitForResponse(
`/api/v1/search/query?q=**%20AND%20(serviceType:BigQuery%20OR%20serviceType:Mysql)&from=0&size=15&index=database_service_search_index`
@ -67,7 +67,7 @@ test.describe('Service Listing', () => {
page.getByTestId(`service-name-${databaseService.entity.name}`)
).toBeVisible();
await page.getByRole('button', { name: 'filter' }).click();
await page.getByTestId('filter-icon').click();
await page.getByLabel('Big Query').uncheck();
await expect(

View File

@ -208,6 +208,7 @@ export const hardDeleteUserProfilePage = async (
// Wait for both the delete response and all toast elements to appear
await Promise.all([deleteResponse, ...toastPromises]);
await page.waitForLoadState('networkidle');
await toastNotification(page, /deleted successfully!/);
};

View File

@ -1 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none"><path fill="#76746F" d="M19.257 0H.82a.781.781 0 0 0-.78.781A7.63 7.63 0 0 0 2.597 6.48L6.019 9.52c.594.528.934 1.286.934 2.08v7.618c0 .622.696.996 1.214.65l4.61-3.073a.781.781 0 0 0 .347-.65v-4.546c0-.793.34-1.551.934-2.079l3.421-3.04A7.63 7.63 0 0 0 20.038.78.781.781 0 0 0 19.257 0ZM16.44 5.311 13.02 8.353a4.348 4.348 0 0 0-1.458 3.246v4.128l-3.047 2.031v-6.159c0-1.24-.531-2.423-1.458-3.246L3.636 5.312a6.069 6.069 0 0 1-1.984-3.75h16.773a6.067 6.067 0 0 1-1.984 3.75Z"/></svg>
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.728923 1.71946C0.838161 1.48401 1.07412 1.33337 1.33367 1.33337H14.667C14.9266 1.33337 15.1625 1.48401 15.2717 1.71946C15.381 1.9549 15.3436 2.23234 15.176 2.43052L10.0003 8.5508V14C10.0003 14.2311 9.8807 14.4457 9.68416 14.5671C9.48761 14.6886 9.24218 14.6997 9.03553 14.5963L6.36886 13.263C6.143 13.1501 6.00033 12.9192 6.00033 12.6667V8.55081L0.824621 2.43052C0.657023 2.23234 0.619684 1.9549 0.728923 1.71946ZM2.77054 2.66671L7.17605 7.87622C7.27782 7.99657 7.33367 8.14909 7.33367 8.30671V12.2547L8.667 12.9214V8.30671C8.667 8.14909 8.72285 7.99657 8.82462 7.87622L13.2301 2.66671H2.77054Z" fill="currentColor"/>
</svg>

Before

Width:  |  Height:  |  Size: 557 B

After

Width:  |  Height:  |  Size: 749 B

View File

@ -348,6 +348,9 @@ export const TestSuites = () => {
emptyText: <FilterTablePlaceHolder />,
}}
pagination={false}
scroll={{
x: true,
}}
size="small"
/>
</Col>

View File

@ -534,6 +534,7 @@ const DataQualityTab: React.FC<DataQualityTabProps> = ({
}}
pagination={false}
rowKey="id"
scroll={{ x: true }}
size="small"
onChange={handleTableChange}
/>

View File

@ -224,6 +224,7 @@ const ColumnProfileTable = () => {
title: t('label.test-plural'),
dataIndex: 'testCount',
key: 'Tests',
width: 100,
render: (_, record) => {
const testCounts = testCaseCounts.find((column) => {
return isEqual(

View File

@ -40,6 +40,7 @@ const SubDomainsTable = ({
title: t('label.sub-domain-plural'),
dataIndex: 'name',
key: 'name',
width: 200,
render: (name: string, record: Domain) => {
return (
<Link
@ -58,6 +59,7 @@ const SubDomainsTable = ({
title: t('label.description'),
dataIndex: 'description',
key: 'description',
width: 300,
render: (description: string) =>
description.trim() ? (
<RichTextEditorPreviewerNew

View File

@ -628,6 +628,9 @@ const IncidentManager = ({
}}
pagination={false}
rowKey="id"
scroll={{
x: true,
}}
size="small"
/>
</Col>

View File

@ -23,7 +23,6 @@ import { UseAirflowStatusProps } from '../../../../../hooks/useAirflowStatus';
import { PagingHandlerParams } from '../../../../common/NextPrevious/NextPrevious.interface';
export interface IngestionListTableProps {
bordered?: boolean;
tableContainerClassName?: string;
afterDeleteAction?: () => void;
airflowInformation?: UseAirflowStatusProps;

View File

@ -71,7 +71,6 @@ import IngestionStatusCount from './IngestionStatusCount/IngestionStatusCount';
import PipelineActions from './PipelineActions/PipelineActions';
function IngestionListTable({
bordered = true,
tableContainerClassName = '',
afterDeleteAction,
airflowInformation,
@ -434,7 +433,6 @@ function IngestionListTable({
gutter={[16, 16]}>
<Col span={24}>
<Table
bordered={bordered}
className={tableClassName}
columns={tableColumn}
{...(!isUndefined(ingestionPagingInfo) &&
@ -447,6 +445,7 @@ function IngestionListTable({
isNumberBased: isNumberBasedPaging,
pagingHandler: onPageChange,
showPagination: true,
onShowSizeChange: ingestionPagingInfo.handlePageSizeChange,
},
}
: {})}

View File

@ -196,7 +196,6 @@ function MetadataAgentsWidget({
<IngestionListTable
airflowInformation={airflowInformation}
bordered={false}
deployIngestion={deployIngestion}
handleEnableDisableIngestion={handleEnableDisableIngestion}
handleIngestionListUpdate={handleIngestionListUpdate}
@ -211,7 +210,7 @@ function MetadataAgentsWidget({
serviceCategory={serviceCategory}
serviceName={serviceName}
tableClassName="metadata-agents-widget-table"
tableContainerClassName="m-b-0"
tableContainerClassName="p-x-md"
triggerIngestion={triggerIngestion}
onIngestionWorkflowsUpdate={onIngestionWorkflowsUpdate}
onPageChange={onPageChange}

View File

@ -15,12 +15,9 @@ import { ColumnsType } from 'antd/lib/table';
import React from 'react';
import { PAGE_HEADERS } from '../../../constants/PageHeaders.constant';
import { PIPELINE_SERVICE_PLATFORM } from '../../../constants/Services.constant';
import { CursorType } from '../../../enums/pagination.enum';
import { ServiceCategory } from '../../../enums/service.enum';
import { PipelineServiceType } from '../../../generated/entity/data/pipeline';
import LimitWrapper from '../../../hoc/LimitWrapper';
import { getServices } from '../../../rest/serviceAPI';
import { PagingHandlerParams } from '../../common/NextPrevious/NextPrevious.interface';
import Services from './Services';
let isDescription = true;
@ -169,28 +166,6 @@ jest.mock('../../common/ErrorWithPlaceholder/ErrorPlaceHolder', () => {
return () => <div data-testid="error-placeholder">ErrorPlaceHolder</div>;
});
jest.mock('../../common/NextPrevious/NextPrevious', () => {
return ({
pagingHandler,
}: {
pagingHandler: ({ cursorType, currentPage }: PagingHandlerParams) => void;
}) => (
<div data-testid="next-previous-container">
NextPrevious
<button
data-testid="next-previous-button"
onClick={() =>
pagingHandler({
currentPage: 0,
cursorType: CursorType.AFTER,
})
}>
NextPrevious
</button>
</div>
);
});
jest.mock('../../common/OwnerLabel/OwnerLabel.component', () => ({
OwnerLabel: jest.fn().mockImplementation(() => <p>OwnerLabel</p>),
}));
@ -331,24 +306,6 @@ describe('Services', () => {
expect(mockPush).toHaveBeenCalledWith('/pipelineServices/add-service');
});
it('should call handleServicePageChange', async () => {
await act(async () => {
render(<Services serviceName={ServiceCategory.PIPELINE_SERVICES} />);
});
await act(async () => {
fireEvent.click(await screen.findByTestId('next-previous-button'));
});
expect(getServices as jest.Mock).toHaveBeenCalledWith({
after: undefined,
before: undefined,
include: 'non-deleted',
limit: 10,
serviceName: 'pipelineServices',
});
});
it('should render columns', async () => {
await act(async () => {
render(<Services serviceName={ServiceCategory.PIPELINE_SERVICES} />);

View File

@ -64,7 +64,6 @@ import {
import { showErrorToast } from '../../../utils/ToastUtils';
import ErrorPlaceHolder from '../../common/ErrorWithPlaceholder/ErrorPlaceHolder';
import { ListView } from '../../common/ListView/ListView.component';
import NextPrevious from '../../common/NextPrevious/NextPrevious';
import { PagingHandlerParams } from '../../common/NextPrevious/NextPrevious.interface';
import RichTextEditorPreviewerV1 from '../../common/RichTextEditor/RichTextEditorPreviewerV1';
import RichTextEditorPreviewerNew from '../../common/RichTextEditor/RichTextEditorPreviewNew';
@ -305,6 +304,30 @@ const Services = ({ serviceName }: ServicesProps) => {
}));
}, [serviceName]);
const customPaginationTableProps = useMemo(
() => ({
showPagination,
currentPage,
isLoading,
isNumberBased: !isEmpty(searchTerm) || !isEmpty(serviceTypeFilter),
pageSize,
paging,
pagingHandler: handleServicePageChange,
onShowSizeChange: handlePageSizeChange,
}),
[
showPagination,
currentPage,
isLoading,
searchTerm,
serviceTypeFilter,
pageSize,
paging,
handleServicePageChange,
handlePageSizeChange,
]
);
const columns: ColumnsType<ServicesType> = [
{
title: t('label.name'),
@ -486,6 +509,7 @@ const Services = ({ serviceName }: ServicesProps) => {
<Col span={24}>
<ListView<ServicesType>
cardRenderer={serviceCardRenderer}
customPaginationProps={customPaginationTableProps}
deleted={deleted}
handleDeletedSwitchChange={handleDeletedSwitchChange}
searchProps={{
@ -493,7 +517,6 @@ const Services = ({ serviceName }: ServicesProps) => {
search: searchTerm,
}}
tableProps={{
bordered: true,
columns,
dataSource: serviceDetails,
rowKey: 'fullyQualifiedName',
@ -507,19 +530,6 @@ const Services = ({ serviceName }: ServicesProps) => {
}}
/>
</Col>
<Col span={24}>
{showPagination && (
<NextPrevious
currentPage={currentPage}
isLoading={isLoading}
isNumberBased={!isEmpty(searchTerm) || !isEmpty(serviceTypeFilter)}
pageSize={pageSize}
paging={paging}
pagingHandler={handleServicePageChange}
onShowSizeChange={handlePageSizeChange}
/>
)}
</Col>
</Row>
);
};

View File

@ -13,7 +13,7 @@
import Icon from '@ant-design/icons/lib/components/Icon';
import { Button, Tooltip } from 'antd';
import Table, { ColumnsType } from 'antd/lib/table';
import { ColumnsType } from 'antd/lib/table';
import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
@ -27,6 +27,7 @@ import {
getRoleWithFqnPath,
} from '../../../../utils/RouterUtils';
import RichTextEditorPreviewerNew from '../../../common/RichTextEditor/RichTextEditorPreviewNew';
import Table from '../../../common/Table/Table';
const ListEntities = ({
list,
@ -47,7 +48,7 @@ const ListEntities = ({
{
title: t('label.name'),
dataIndex: 'name',
width: '200px',
width: 300,
key: 'name',
render: (_, record) => {
let link = '';
@ -79,6 +80,7 @@ const ListEntities = ({
title: t('label.description'),
dataIndex: 'description',
key: 'description',
width: 300,
render: (_, record) => (
<RichTextEditorPreviewerNew markdown={record?.description || ''} />
),
@ -86,7 +88,7 @@ const ListEntities = ({
{
title: t('label.action-plural'),
dataIndex: 'actions',
width: '80px',
width: 100,
key: 'actions',
render: (_, record) => {
return (

View File

@ -103,7 +103,7 @@ const TeamHierarchy: FC<TeamHierarchyProps> = ({
{
title: t('label.user-plural'),
dataIndex: 'userCount',
width: 60,
width: 80,
key: 'users',
render: (userCount: number) =>
isFetchingAllTeamAdvancedDetails ? (

View File

@ -18,6 +18,7 @@
align-items: center;
backdrop-filter: blur(500px);
padding: 8px 16px;
font-weight: @font-medium;
&.border-radius {
border-radius: 4px;
@ -25,12 +26,12 @@
&.success {
background-color: @success-bg-color;
color: @success-color;
color: @grey-700;
}
&.error {
background-color: @error-bg-color;
color: @error-color;
color: @grey-700;
}
.loading-spinner-success {

View File

@ -21,6 +21,7 @@ import {
COMMON_STATIC_TABLE_VISIBLE_COLUMNS,
DEFAULT_SERVICE_VISIBLE_COLUMNS,
} from '../../../constants/TableKeys.constants';
import NextPrevious from '../NextPrevious/NextPrevious';
import Searchbar from '../SearchBarComponent/SearchBar.component';
import Table from '../Table/Table';
import { ListViewOptions, ListViewProps } from './ListView.interface';
@ -32,6 +33,7 @@ export const ListView = <T extends object = any>({
searchProps: { search, onSearch },
handleDeletedSwitchChange,
deleted = false,
customPaginationProps,
}: ListViewProps<T>) => {
const [currentView, setCurrentView] = useState<ListViewOptions>(
ListViewOptions.TABLE
@ -94,6 +96,7 @@ export const ListView = <T extends object = any>({
<Col span={24}>
{currentView === ListViewOptions.TABLE ? (
<Table
customPaginationProps={customPaginationProps}
defaultVisibleColumns={DEFAULT_SERVICE_VISIBLE_COLUMNS}
staticVisibleColumns={COMMON_STATIC_TABLE_VISIBLE_COLUMNS}
{...tableProps}
@ -102,6 +105,13 @@ export const ListView = <T extends object = any>({
cardRender
)}
</Col>
{currentView !== ListViewOptions.TABLE && (
<Col span={24}>
{customPaginationProps.showPagination && (
<NextPrevious {...customPaginationProps} />
)}
</Col>
)}
</Row>
);
};

View File

@ -12,6 +12,7 @@
*/
import { TableProps } from 'antd';
import { ReactNode } from 'react';
import { NextPreviousProps } from '../NextPrevious/NextPrevious.interface';
interface SearchProps {
onSearch: ((search: string) => void) | ((search: string) => Promise<void>);
@ -24,6 +25,9 @@ export interface ListViewProps<T> {
searchProps: SearchProps;
deleted?: boolean;
handleDeletedSwitchChange?: () => void;
customPaginationProps: NextPreviousProps & {
showPagination: boolean;
};
}
export enum ListViewOptions {

View File

@ -12,6 +12,9 @@
*/
import { act, fireEvent, render, screen } from '@testing-library/react';
import React from 'react';
import { pagingObject } from '../../../constants/constants';
import { CursorType } from '../../../enums/pagination.enum';
import { PagingHandlerParams } from '../NextPrevious/NextPrevious.interface';
import { ListView } from './ListView.component';
const mockCardRenderer = jest.fn().mockImplementation(() => <>Card</>);
@ -26,11 +29,48 @@ jest.mock('../SearchBarComponent/SearchBar.component', () => {
return jest.fn().mockImplementation(() => <p>Searchbar</p>);
});
jest.mock('../NextPrevious/NextPrevious', () => {
return ({
pagingHandler,
}: {
pagingHandler: ({ cursorType, currentPage }: PagingHandlerParams) => void;
}) => (
<div data-testid="next-previous-container">
NextPrevious
<button
data-testid="next-previous-button"
onClick={() =>
pagingHandler({
currentPage: 0,
cursorType: CursorType.AFTER,
})
}>
NextPrevious
</button>
</div>
);
});
const mockPagingHandler = jest.fn();
const mockOnShowSizeChange = jest.fn();
const mockCustomPaginationProps = {
showPagination: true,
currentPage: 0,
isLoading: false,
isNumberBased: false,
pageSize: 10,
paging: pagingObject,
pagingHandler: mockPagingHandler,
onShowSizeChange: mockOnShowSizeChange,
};
describe('ListView component', () => {
it('should render toggle button for card and table', async () => {
render(
<ListView
cardRenderer={mockCardRenderer}
customPaginationProps={mockCustomPaginationProps}
deleted={false}
handleDeletedSwitchChange={mockHandleDeletedSwitchChange}
searchProps={{
@ -52,6 +92,7 @@ describe('ListView component', () => {
render(
<ListView
cardRenderer={mockCardRenderer}
customPaginationProps={mockCustomPaginationProps}
deleted={false}
handleDeletedSwitchChange={mockHandleDeletedSwitchChange}
searchProps={{
@ -71,6 +112,7 @@ describe('ListView component', () => {
render(
<ListView
cardRenderer={mockCardRenderer}
customPaginationProps={mockCustomPaginationProps}
deleted={false}
handleDeletedSwitchChange={mockHandleDeletedSwitchChange}
searchProps={{
@ -95,6 +137,7 @@ describe('ListView component', () => {
render(
<ListView
cardRenderer={mockCardRenderer}
customPaginationProps={mockCustomPaginationProps}
deleted={false}
handleDeletedSwitchChange={mockHandleDeletedSwitchChange}
searchProps={{
@ -115,4 +158,57 @@ describe('ListView component', () => {
expect(mockHandleDeletedSwitchChange).toHaveBeenCalledTimes(1);
});
it('should call pagingHandler in ListView', async () => {
render(
<ListView
cardRenderer={mockCardRenderer}
customPaginationProps={mockCustomPaginationProps}
deleted={false}
handleDeletedSwitchChange={mockHandleDeletedSwitchChange}
searchProps={{
onSearch: mockOnSearch,
}}
tableProps={{
columns: [],
dataSource: [{ name: 'test' }],
}}
/>
);
await act(async () => {
fireEvent.click(await screen.findByTestId('grid'));
});
await act(async () => {
fireEvent.click(await screen.findByTestId('next-previous-button'));
});
expect(mockPagingHandler).toHaveBeenCalledWith({
currentPage: 0,
cursorType: CursorType.AFTER,
});
});
it('should not visible pagination in TableView', async () => {
render(
<ListView
cardRenderer={mockCardRenderer}
customPaginationProps={mockCustomPaginationProps}
deleted={false}
handleDeletedSwitchChange={mockHandleDeletedSwitchChange}
searchProps={{
onSearch: mockOnSearch,
}}
tableProps={{
columns: [],
dataSource: [{ name: 'test' }],
}}
/>
);
expect(
screen.queryByTestId('next-previous-button')
).not.toBeInTheDocument();
});
});

View File

@ -913,3 +913,14 @@ a[href].link-text-grey,
letter-spacing: 0.25px;
width: 350px;
}
// Table icons
.filter-icon {
width: 16px;
height: 16px;
}
.filter-icon-active {
fill: @primary-color;
}

View File

@ -61,6 +61,11 @@
}
}
.ant-btn:disabled {
background-color: transparent;
border: transparent;
color: rgba(0, 0, 0, 0.25);
}
.pagination-button {
color: @grey-500;
font-weight: @font-semibold;

View File

@ -157,3 +157,15 @@
}
}
}
// Hide the border of the columns except the resizable columns
.ant-table-thead > tr {
// Regular header cells
> th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan])::before {
width: 0px;
}
// Resizable header cells
> .resizable-container:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan])::before {
width: 1px;
}
}

View File

@ -10,25 +10,27 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { FilterOutlined } from '@ant-design/icons';
import Icon from '@ant-design/icons';
import { ColumnsType } from 'antd/lib/table';
import classNames from 'classnames';
import React from 'react';
import { ReactComponent as FilterIcon } from '../assets/svg/ic-filter.svg';
import { OwnerLabel } from '../components/common/OwnerLabel/OwnerLabel.component';
import { ICON_DIMENSION } from '../constants/constants';
import { TABLE_COLUMNS_KEYS } from '../constants/TableKeys.constants';
import { EntityReference } from '../generated/type/entityReference';
import { useApplicationStore } from '../hooks/useApplicationStore';
import i18n from './i18next/LocalUtil';
export const columnFilterIcon = (filtered: boolean) => {
const { theme } = useApplicationStore.getState();
return (
<FilterOutlined
data-testid="tag-filter"
style={{ color: filtered ? theme?.primaryColor : undefined }}
/>
);
};
export const columnFilterIcon = (filtered: boolean) => (
<Icon
className={classNames('filter-icon', {
'filter-icon-active': filtered,
})}
component={FilterIcon}
data-testid="filter-icon"
style={ICON_DIMENSION}
/>
);
export const ownerTableObject = <
T extends { owners?: EntityReference[] }