diff --git a/src/containers/Cluster/Cluster.tsx b/src/containers/Cluster/Cluster.tsx index 57c83cd97..d6d1d3add 100644 --- a/src/containers/Cluster/Cluster.tsx +++ b/src/containers/Cluster/Cluster.tsx @@ -27,7 +27,7 @@ import {cn} from '../../utils/cn'; import {useTypedDispatch, useTypedSelector} from '../../utils/hooks'; import {parseVersionsToVersionToColorMap} from '../../utils/versions'; import {Nodes} from '../Nodes/Nodes'; -import {StorageWrapper} from '../Storage/StorageWrapper'; +import {PaginatedStorage} from '../Storage/PaginatedStorage'; import {TabletsTable} from '../Tablets/TabletsTable'; import {Tenants} from '../Tenants/Tenants'; import {Versions} from '../Versions/Versions'; @@ -199,7 +199,7 @@ export function Cluster({ getLocationObjectFromHref(getClusterPath(clusterTabsIds.storage)).pathname } > - + - + ); } diff --git a/src/containers/PDiskPage/PDiskPage.tsx b/src/containers/PDiskPage/PDiskPage.tsx index f7157ca2a..48963c8de 100644 --- a/src/containers/PDiskPage/PDiskPage.tsx +++ b/src/containers/PDiskPage/PDiskPage.tsx @@ -25,7 +25,7 @@ import {valueIsDefined} from '../../utils'; import {cn} from '../../utils/cn'; import {getPDiskId, getSeverityColor} from '../../utils/disks/helpers'; import {useAutoRefreshInterval, useTypedDispatch, useTypedSelector} from '../../utils/hooks'; -import {StorageWrapper} from '../Storage/StorageWrapper'; +import {PaginatedStorage} from '../Storage/PaginatedStorage'; import {DecommissionButton} from './DecommissionButton/DecommissionButton'; import {DecommissionLabel} from './DecommissionLabel/DecommissionLabel'; @@ -246,7 +246,15 @@ export function PDiskPage() { } case 'storage': { return pDiskParamsDefined ? ( - + ) : null; } default: diff --git a/src/containers/Storage/PaginatedStorage.tsx b/src/containers/Storage/PaginatedStorage.tsx index 8ec205bc5..36b45a6ed 100644 --- a/src/containers/Storage/PaginatedStorage.tsx +++ b/src/containers/Storage/PaginatedStorage.tsx @@ -10,7 +10,7 @@ export interface PaginatedStorageProps { groupId?: string | number; pDiskId?: string | number; - viewContext: StorageViewContext; + viewContext?: StorageViewContext; parentRef: React.RefObject; diff --git a/src/containers/Storage/PaginatedStorageNodes.tsx b/src/containers/Storage/PaginatedStorageNodes.tsx index b89165d69..d67721894 100644 --- a/src/containers/Storage/PaginatedStorageNodes.tsx +++ b/src/containers/Storage/PaginatedStorageNodes.tsx @@ -208,7 +208,7 @@ function useStorageNodesColumnsToSelect({ viewContext, }: { database?: string; - viewContext: StorageViewContext; + viewContext?: StorageViewContext; }) { const {balancer} = useClusterBaseInfo(); const {additionalNodesProps} = useAdditionalNodeProps({balancer}); diff --git a/src/containers/Storage/Storage.tsx b/src/containers/Storage/Storage.tsx deleted file mode 100644 index e2483beac..000000000 --- a/src/containers/Storage/Storage.tsx +++ /dev/null @@ -1,236 +0,0 @@ -import React from 'react'; - -import type {OrderType, SortOrder} from '@gravity-ui/react-data-table'; - -import {AccessDenied} from '../../components/Errors/403'; -import {isAccessError} from '../../components/Errors/PageError/PageError'; -import {ResponseError} from '../../components/Errors/ResponseError'; -import {TableWithControlsLayout} from '../../components/TableWithControlsLayout/TableWithControlsLayout'; -import { - useCapabilitiesLoaded, - useStorageGroupsHandlerAvailable, -} from '../../store/reducers/capabilities/hooks'; -import {useClusterBaseInfo} from '../../store/reducers/cluster/cluster'; -import type {NodesSortParams} from '../../store/reducers/nodes/types'; -import {filterGroups, filterNodes} from '../../store/reducers/storage/selectors'; -import {storageApi} from '../../store/reducers/storage/storage'; -import type {StorageSortParams} from '../../store/reducers/storage/types'; -import type {HandleSort} from '../../utils/hooks'; -import {useAutoRefreshInterval, useTableSort} from '../../utils/hooks'; -import {useAdditionalNodeProps} from '../AppWithClusters/useClusterData'; - -import {StorageGroupsControls, StorageNodesControls} from './StorageControls/StorageControls'; -import {StorageGroupsTable} from './StorageGroups/StorageGroupsTable'; -import {useStorageGroupsSelectedColumns} from './StorageGroups/columns/hooks'; -import {StorageNodesTable} from './StorageNodes/StorageNodesTable'; -import {useStorageNodesSelectedColumns} from './StorageNodes/columns/hooks'; -import {b} from './shared'; -import type {StorageViewContext} from './types'; -import {useStorageQueryParams} from './useStorageQueryParams'; -import {defaultSortNode, getDefaultSortGroup} from './utils'; - -import './Storage.scss'; - -export function useStorageSort< - T extends { - sortValue: string; - sortOrder: OrderType; - }, ->( - {sortValue, sortOrder}: T, - onSort: (params: T | undefined) => void, -): [SortOrder[] | undefined, HandleSort] { - return useTableSort({ - initialSortColumn: sortValue, - initialSortOrder: sortOrder, - multiple: false, - onSort: (sort) => { - const newSort = sort?.[0] - ? { - sortValue: sort[0].columnId, - sortOrder: sort[0].order, - } - : undefined; - - onSort(newSort as T); - }, - }); -} - -interface StorageProps { - database?: string; - nodeId?: string | number; - groupId?: string | number; - pDiskId?: string | number; - - viewContext: StorageViewContext; -} - -export const Storage = ({database, viewContext, nodeId, groupId, pDiskId}: StorageProps) => { - const {balancer} = useClusterBaseInfo(); - const {additionalNodesProps} = useAdditionalNodeProps({balancer}); - - const { - storageType, - searchValue, - visibleEntities, - nodesUptimeFilter, - - handleShowAllGroups, - handleShowAllNodes, - } = useStorageQueryParams(); - - const capabilitiesLoaded = useCapabilitiesLoaded(); - const groupsHandlerAvailable = useStorageGroupsHandlerAvailable(); - const [autoRefreshInterval] = useAutoRefreshInterval(); - - const isGroups = storageType === 'groups'; - const isNodes = storageType === 'nodes'; - - const [nodeSort, setNodeSort] = React.useState(undefined); - const nodesSortParams = nodeSort ? nodeSort : defaultSortNode; - - const [groupSort, setGroupSort] = React.useState(undefined); - const groupsSortParams = groupSort ? groupSort : getDefaultSortGroup(visibleEntities); - - const { - columnsToShow: storageNodesColumnsToShow, - columnsToSelect: storageNodesColumnsToSelect, - setColumns: setStorageNodesSelectedColumns, - } = useStorageNodesSelectedColumns({ - additionalNodesProps, - visibleEntities, - database, - viewContext, - }); - - const { - columnsToShow: storageGroupsColumnsToShow, - columnsToSelect: storageGroupsColumnsToSelect, - setColumns: setStorageGroupsSelectedColumns, - } = useStorageGroupsSelectedColumns({visibleEntities, viewContext}); - - const nodesQuery = storageApi.useGetStorageNodesInfoQuery( - { - database, - with: visibleEntities, - node_id: nodeId, - group_id: groupId, - }, - { - skip: !isNodes, - pollingInterval: autoRefreshInterval, - }, - ); - const groupsQuery = storageApi.useGetStorageGroupsInfoQuery( - { - database, - with: visibleEntities, - nodeId, - groupId, - pDiskId, - shouldUseGroupsHandler: groupsHandlerAvailable, - fieldsRequired: 'all', - }, - { - skip: !isGroups || !capabilitiesLoaded, - pollingInterval: autoRefreshInterval, - }, - ); - - const {currentData, isFetching, error} = isNodes ? nodesQuery : groupsQuery; - - const {currentData: {nodes = []} = {}} = nodesQuery; - const {currentData: {groups = []} = {}} = groupsQuery; - - const nodesTotalCount = nodesQuery.currentData?.total || 0; - const groupsTotalCount = groupsQuery.currentData?.total || 0; - - const isLoading = currentData === undefined && isFetching; - - const storageNodes = React.useMemo( - () => filterNodes(nodes, searchValue, nodesUptimeFilter), - [nodes, nodesUptimeFilter, searchValue], - ); - const storageGroups = React.useMemo( - () => filterGroups(groups, searchValue), - [searchValue, groups], - ); - - const [nodesSort, handleNodesSort] = useStorageSort(nodesSortParams, setNodeSort); - const [groupsSort, handleGroupsSort] = useStorageSort(groupsSortParams, setGroupSort); - - const renderDataTable = () => { - return ( - - {isGroups ? ( - - ) : null} - {isNodes ? ( - - ) : null} - - ); - }; - - const renderControls = () => { - return ( - - {isGroups ? ( - - ) : null} - {isNodes ? ( - - ) : null} - - ); - }; - - if (isAccessError(error)) { - return ; - } - - return ( - - {renderControls()} - {error ? : null} - - {currentData ? renderDataTable() : null} - - - ); -}; diff --git a/src/containers/Storage/StorageGroups/columns/hooks.ts b/src/containers/Storage/StorageGroups/columns/hooks.ts index bb0948a07..7cf0f490f 100644 --- a/src/containers/Storage/StorageGroups/columns/hooks.ts +++ b/src/containers/Storage/StorageGroups/columns/hooks.ts @@ -14,7 +14,7 @@ import { } from './constants'; import type {GetStorageGroupsColumnsParams} from './types'; -export function useGetStorageGroupsColumns(viewContext: StorageViewContext) { +export function useGetStorageGroupsColumns(viewContext?: StorageViewContext) { return React.useMemo(() => { return getStorageGroupsColumns({viewContext}); }, [viewContext]); diff --git a/src/containers/Storage/StorageGroups/columns/types.ts b/src/containers/Storage/StorageGroups/columns/types.ts index 03c615cd6..a7d30a47a 100644 --- a/src/containers/Storage/StorageGroups/columns/types.ts +++ b/src/containers/Storage/StorageGroups/columns/types.ts @@ -5,12 +5,12 @@ import type {StorageViewContext} from '../../types'; export type StorageGroupsColumn = Column; export interface GetStorageColumnsData { - viewContext: StorageViewContext; + viewContext?: StorageViewContext; } export interface GetStorageGroupsColumnsParams { visibleEntities?: VisibleEntities; - viewContext: StorageViewContext; + viewContext?: StorageViewContext; } export type StorageColumnsGetter = (data?: GetStorageColumnsData) => StorageGroupsColumn[]; diff --git a/src/containers/Storage/StorageNodes/StorageNodesTable.tsx b/src/containers/Storage/StorageNodes/StorageNodesTable.tsx deleted file mode 100644 index 2d99a0d2a..000000000 --- a/src/containers/Storage/StorageNodes/StorageNodesTable.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import type {SortOrder} from '@gravity-ui/react-data-table'; - -import {ResizeableDataTable} from '../../../components/ResizeableDataTable/ResizeableDataTable'; -import {VISIBLE_ENTITIES} from '../../../store/reducers/storage/constants'; -import type {PreparedStorageNode, VisibleEntities} from '../../../store/reducers/storage/types'; -import {DEFAULT_TABLE_SETTINGS} from '../../../utils/constants'; -import type {HandleSort} from '../../../utils/hooks/useTableSort'; -import {NodesUptimeFilterValues} from '../../../utils/nodes'; - -import {StorageNodesEmptyDataMessage} from './StorageNodesEmptyDataMessage'; -import {STORAGE_NODES_COLUMNS_WIDTH_LS_KEY} from './columns/constants'; -import type {StorageNodesColumn} from './columns/types'; -import i18n from './i18n'; -import {getRowUnavailableClassName} from './shared'; - -const tableSettings = { - ...DEFAULT_TABLE_SETTINGS, - dynamicRenderMinSize: 51, -} as const; - -interface StorageNodesTableProps { - data: PreparedStorageNode[]; - columns: StorageNodesColumn[]; - visibleEntities: VisibleEntities; - nodesUptimeFilter: NodesUptimeFilterValues; - sort?: SortOrder[]; - onShowAll?: VoidFunction; - handleSort?: HandleSort; -} - -export function StorageNodesTable({ - data, - columns, - visibleEntities, - nodesUptimeFilter, - sort, - onShowAll, - handleSort, -}: StorageNodesTableProps) { - if ( - !data.length && - (visibleEntities !== VISIBLE_ENTITIES.all || - nodesUptimeFilter !== NodesUptimeFilterValues.All) - ) { - return ( - - ); - } - - return ( - - ); -} diff --git a/src/containers/Storage/StorageNodes/columns/types.ts b/src/containers/Storage/StorageNodes/columns/types.ts index 049f17a9b..e144c5957 100644 --- a/src/containers/Storage/StorageNodes/columns/types.ts +++ b/src/containers/Storage/StorageNodes/columns/types.ts @@ -9,5 +9,5 @@ export interface GetStorageNodesColumnsParams { additionalNodesProps?: AdditionalNodesProps | undefined; visibleEntities?: VisibleEntities; database?: string; - viewContext: StorageViewContext; + viewContext?: StorageViewContext; } diff --git a/src/containers/Storage/StorageWrapper.tsx b/src/containers/Storage/StorageWrapper.tsx deleted file mode 100644 index 5b1b7120b..000000000 --- a/src/containers/Storage/StorageWrapper.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import {USE_PAGINATED_TABLES_KEY} from '../../utils/constants'; -import {useSetting} from '../../utils/hooks'; - -import {PaginatedStorage} from './PaginatedStorage'; -import {Storage} from './Storage'; -import type {StorageViewContext} from './types'; - -interface StorageWrapperProps { - database?: string; - nodeId?: string | number; - pDiskId?: string | number; - groupId?: string | number; - vDiskSlotId?: string | number; - parentRef: React.RefObject; -} - -export const StorageWrapper = ({parentRef, ...props}: StorageWrapperProps) => { - const [usePaginatedTables] = useSetting(USE_PAGINATED_TABLES_KEY); - - const viewContext: StorageViewContext = { - nodeId: props.nodeId?.toString(), - pDiskId: props.pDiskId?.toString(), - groupId: props.groupId?.toString(), - vDiskSlotId: props.vDiskSlotId?.toString(), - }; - - if (usePaginatedTables) { - return ; - } - - return ; -}; diff --git a/src/containers/Storage/utils/index.ts b/src/containers/Storage/utils/index.ts index 7606288d3..52e448eba 100644 --- a/src/containers/Storage/utils/index.ts +++ b/src/containers/Storage/utils/index.ts @@ -89,12 +89,14 @@ const DEFAULT_ENTITIES_COUNT = 10; // GroupPage - DEFAULT_ENTITIES_COUNT nodes // PDiskPage - 1 node // VDiskPage - 1 node -export function getStorageNodesInitialEntitiesCount({ - nodeId, - pDiskId, - vDiskSlotId, -}: StorageViewContext): number | undefined { - if (valueIsDefined(nodeId) || valueIsDefined(pDiskId) || valueIsDefined(vDiskSlotId)) { +export function getStorageNodesInitialEntitiesCount( + context?: StorageViewContext, +): number | undefined { + if ( + valueIsDefined(context?.nodeId) || + valueIsDefined(context?.pDiskId) || + valueIsDefined(context?.vDiskSlotId) + ) { return 1; } @@ -105,14 +107,13 @@ export function getStorageNodesInitialEntitiesCount({ // GroupPage - 1 group // PDiskPage - DEFAULT_ENTITIES_COUNT groups // VDiskPage - 1 group -export function getStorageGroupsInitialEntitiesCount({ - vDiskSlotId, - groupId, -}: StorageViewContext): number | undefined { - if (valueIsDefined(groupId)) { +export function getStorageGroupsInitialEntitiesCount( + context?: StorageViewContext, +): number | undefined { + if (valueIsDefined(context?.groupId)) { return 1; } - if (valueIsDefined(vDiskSlotId)) { + if (valueIsDefined(context?.vDiskSlotId)) { return 1; } diff --git a/src/containers/StorageGroupPage/StorageGroupPage.tsx b/src/containers/StorageGroupPage/StorageGroupPage.tsx index 68058f4ea..edfc9cb66 100644 --- a/src/containers/StorageGroupPage/StorageGroupPage.tsx +++ b/src/containers/StorageGroupPage/StorageGroupPage.tsx @@ -19,7 +19,7 @@ import {EFlag} from '../../types/api/enums'; import {valueIsDefined} from '../../utils'; import {cn} from '../../utils/cn'; import {useAutoRefreshInterval, useTypedDispatch} from '../../utils/hooks'; -import {StorageWrapper} from '../Storage/StorageWrapper'; +import {PaginatedStorage} from '../Storage/PaginatedStorage'; import {storageGroupPageKeyset} from './i18n'; @@ -110,7 +110,13 @@ export function StorageGroupPage() {
{storageGroupPageKeyset('storage')}
- + ); }; diff --git a/src/containers/Tenant/Diagnostics/Diagnostics.tsx b/src/containers/Tenant/Diagnostics/Diagnostics.tsx index 75a32bb13..98cddb400 100644 --- a/src/containers/Tenant/Diagnostics/Diagnostics.tsx +++ b/src/containers/Tenant/Diagnostics/Diagnostics.tsx @@ -16,7 +16,7 @@ import {useTypedDispatch, useTypedSelector} from '../../../utils/hooks'; import {Heatmap} from '../../Heatmap'; import {Nodes} from '../../Nodes/Nodes'; import {Operations} from '../../Operations'; -import {StorageWrapper} from '../../Storage/StorageWrapper'; +import {PaginatedStorage} from '../../Storage/PaginatedStorage'; import {Tablets} from '../../Tablets'; import {SchemaViewer} from '../Schema/SchemaViewer/SchemaViewer'; import {TenantTabsGroups, getTenantPath} from '../TenantPages'; @@ -114,7 +114,7 @@ function Diagnostics(props: DiagnosticsProps) { return ; } case TENANT_DIAGNOSTICS_TABS_IDS.storage: { - return ; + return ; } case TENANT_DIAGNOSTICS_TABS_IDS.network: { return ( diff --git a/src/containers/UserSettings/i18n/en.json b/src/containers/UserSettings/i18n/en.json index 86f8e52dc..541c692b5 100644 --- a/src/containers/UserSettings/i18n/en.json +++ b/src/containers/UserSettings/i18n/en.json @@ -12,7 +12,7 @@ "section.about": "About", "settings.editor.autocomplete.title": "Enable autocomplete", - "settings.editor.autocomplete.description": "You’re always able to get suggestions by pressing Ctrl+Space.", + "settings.editor.autocomplete.description": "You're always able to get suggestions by pressing Ctrl+Space.", "settings.editor.autocomplete-on-enter.title": "Accept suggestion on Enter", "settings.editor.autocomplete-on-enter.description": "Controls whether suggestions should be accepted on Enter, in addition to Tab. Helps to avoid ambiguity between inserting new lines or accepting suggestions.", @@ -30,9 +30,6 @@ "settings.invertedDisks.title": "Inverted disks space indicators", - "settings.usePaginatedTables.title": "Use paginated tables", - "settings.usePaginatedTables.description": " Use table with data load on scroll for Nodes and Storage tabs. It will increase performance, but could work unstable", - "settings.enableNetworkTable.title": "Enable network table", "settings.useShowPlanToSvg.title": "Execution plan", diff --git a/src/containers/UserSettings/settings.tsx b/src/containers/UserSettings/settings.tsx index e23e12c92..eccf42e40 100644 --- a/src/containers/UserSettings/settings.tsx +++ b/src/containers/UserSettings/settings.tsx @@ -12,7 +12,6 @@ import { SHOW_DOMAIN_DATABASE_KEY, THEME_KEY, USE_CLUSTER_BALANCER_AS_BACKEND_KEY, - USE_PAGINATED_TABLES_KEY, USE_SHOW_PLAN_SVG_KEY, } from '../../utils/constants'; import {Lang, defaultLang} from '../../utils/i18n'; @@ -92,11 +91,6 @@ export const invertedDisksSetting: SettingProps = { settingKey: INVERTED_DISKS_KEY, title: i18n('settings.invertedDisks.title'), }; -export const usePaginatedTables: SettingProps = { - settingKey: USE_PAGINATED_TABLES_KEY, - title: i18n('settings.usePaginatedTables.title'), - description: i18n('settings.usePaginatedTables.description'), -}; export const enableNetworkTable: SettingProps = { settingKey: ENABLE_NETWORK_TABLE_KEY, @@ -148,11 +142,13 @@ export const appearanceSection: SettingsSection = { showDomainDatabase, ], }; + export const experimentsSection: SettingsSection = { id: 'experimentsSection', title: i18n('section.experiments'), - settings: [usePaginatedTables, enableNetworkTable, useShowPlanToSvgTables], + settings: [enableNetworkTable, useShowPlanToSvgTables], }; + export const devSettingsSection: SettingsSection = { id: 'devSettingsSection', title: i18n('section.dev-setting'), @@ -172,6 +168,7 @@ export const generalPage: SettingsPage = { sections: [appearanceSection], showTitle: false, }; + export const experimentsPage: SettingsPage = { id: 'experimentsPage', title: i18n('page.experiments'), @@ -179,6 +176,7 @@ export const experimentsPage: SettingsPage = { sections: [experimentsSection], showTitle: false, }; + export const editorPage: SettingsPage = { id: 'editorPage', title: i18n('page.editor'), diff --git a/src/containers/VDiskPage/VDiskPage.tsx b/src/containers/VDiskPage/VDiskPage.tsx index 3e5de71b8..49591f284 100644 --- a/src/containers/VDiskPage/VDiskPage.tsx +++ b/src/containers/VDiskPage/VDiskPage.tsx @@ -21,7 +21,7 @@ import {valueIsDefined} from '../../utils'; import {cn} from '../../utils/cn'; import {getSeverityColor, getVDiskSlotBasedId} from '../../utils/disks/helpers'; import {useAutoRefreshInterval, useTypedDispatch, useTypedSelector} from '../../utils/hooks'; -import {StorageWrapper} from '../Storage/StorageWrapper'; +import {PaginatedStorage} from '../Storage/PaginatedStorage'; import {vDiskPageKeyset} from './i18n'; @@ -180,12 +180,17 @@ export function VDiskPage() { return (
{vDiskPageKeyset('storage')}
-
); diff --git a/src/services/settings.ts b/src/services/settings.ts index 5485ed177..2f8855888 100644 --- a/src/services/settings.ts +++ b/src/services/settings.ts @@ -20,7 +20,6 @@ import { TENANT_INITIAL_PAGE_KEY, THEME_KEY, USE_CLUSTER_BALANCER_AS_BACKEND_KEY, - USE_PAGINATED_TABLES_KEY, USE_SHOW_PLAN_SVG_KEY, } from '../utils/constants'; import {DEFAULT_QUERY_SETTINGS, QUERY_ACTIONS} from '../utils/query'; @@ -39,7 +38,6 @@ export const DEFAULT_USER_SETTINGS = { [LAST_USED_QUERY_ACTION_KEY]: QUERY_ACTIONS.execute, [ASIDE_HEADER_COMPACT_KEY]: true, [PARTITIONS_HIDDEN_COLUMNS_KEY]: [], - [USE_PAGINATED_TABLES_KEY]: true, [ENABLE_NETWORK_TABLE_KEY]: false, [USE_SHOW_PLAN_SVG_KEY]: false, [USE_CLUSTER_BALANCER_AS_BACKEND_KEY]: true, diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 5bfacc13f..682a08c72 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -132,10 +132,6 @@ export const PARTITIONS_HIDDEN_COLUMNS_KEY = 'partitionsHiddenColumns'; // Remain "tab" in key name for backward compatibility export const TENANT_INITIAL_PAGE_KEY = 'saved_tenant_initial_tab'; -// Setting to use paginated tables -// Old key value for backward compatibility -export const USE_PAGINATED_TABLES_KEY = 'useBackendParamsForTables'; - export const ENABLE_NETWORK_TABLE_KEY = 'enableNetworkTable'; export const USE_SHOW_PLAN_SVG_KEY = 'useShowPlanToSvg'; diff --git a/tests/suites/memoryViewer/memoryViewer.test.ts b/tests/suites/memoryViewer/memoryViewer.test.ts index 1a8c3ceac..e02088608 100644 --- a/tests/suites/memoryViewer/memoryViewer.test.ts +++ b/tests/suites/memoryViewer/memoryViewer.test.ts @@ -1,7 +1,7 @@ import {expect, test} from '@playwright/test'; import {NodesPage} from '../nodes/NodesPage'; -import {PaginatedTable} from '../paginatedTable/paginatedTable'; +import {ClusterNodesTable} from '../paginatedTable/paginatedTable'; import {MemoryViewer} from './MemoryViewer'; @@ -11,13 +11,14 @@ test.describe('Memory Viewer Widget', () => { const memoryViewer = new MemoryViewer(page); await nodesPage.goto(); - const paginatedTable = new PaginatedTable(page); + const paginatedTable = new ClusterNodesTable(page); await paginatedTable.waitForTableVisible(); await paginatedTable.waitForTableData(); if (!(await memoryViewer.isVisible())) { - await paginatedTable.openColumnSetup(); - await paginatedTable.setColumnChecked('Memory'); - await paginatedTable.applyColumnVisibility(); + const controls = paginatedTable.getControls(); + await controls.openColumnSetup(); + await controls.setColumnChecked('Memory'); + await controls.applyColumnVisibility(); } await memoryViewer.waitForVisible(); }); diff --git a/tests/suites/nodes/nodes.test.ts b/tests/suites/nodes/nodes.test.ts index 58086ed48..1084900cc 100644 --- a/tests/suites/nodes/nodes.test.ts +++ b/tests/suites/nodes/nodes.test.ts @@ -2,7 +2,7 @@ import {expect, test} from '@playwright/test'; import {backend} from '../../utils/constants'; import {NodesPage} from '../nodes/NodesPage'; -import {PaginatedTable} from '../paginatedTable/paginatedTable'; +import {ClusterNodesTable} from '../paginatedTable/paginatedTable'; test.describe('Test Nodes page', async () => { test('Nodes page is OK', async ({page}) => { @@ -30,7 +30,7 @@ test.describe('Test Nodes Paginated Table', async () => { }); test('Table loads and displays data', async ({page}) => { - const paginatedTable = new PaginatedTable(page); + const paginatedTable = new ClusterNodesTable(page); await paginatedTable.waitForTableToLoad(); await paginatedTable.waitForTableData(); @@ -40,13 +40,13 @@ test.describe('Test Nodes Paginated Table', async () => { }); test('Search by hostname filters the table', async ({page}) => { - const paginatedTable = new PaginatedTable(page); + const paginatedTable = new ClusterNodesTable(page); await paginatedTable.waitForTableToLoad(); await paginatedTable.waitForTableData(); const initialRowCount = await paginatedTable.getRowCount(); - await paginatedTable.search('localhost'); + await paginatedTable.getControls().search('localhost'); await page.waitForTimeout(1000); // Wait for the table to update @@ -56,7 +56,7 @@ test.describe('Test Nodes Paginated Table', async () => { test('Table groups displayed correctly if group by option is selected', async ({page}) => { const nodesPage = new NodesPage(page); - const nodesTable = new PaginatedTable(page); + const nodesTable = new ClusterNodesTable(page); await nodesTable.waitForTableToLoad(); await nodesTable.waitForTableData(); @@ -74,19 +74,19 @@ test.describe('Test Nodes Paginated Table', async () => { }); test('Node count is displayed correctly', async ({page}) => { - const paginatedTable = new PaginatedTable(page); + const paginatedTable = new ClusterNodesTable(page); await paginatedTable.waitForTableToLoad(); await paginatedTable.waitForTableData(); - const nodeCount = await paginatedTable.getCount(); + const nodeCount = await paginatedTable.getControls().getCount(); const rowCount = await paginatedTable.getRowCount(); expect(nodeCount).toBe(rowCount); }); test('Uptime values are displayed in correct format', async ({page}) => { - const paginatedTable = new PaginatedTable(page); + const paginatedTable = new ClusterNodesTable(page); await paginatedTable.waitForTableToLoad(); await paginatedTable.waitForTableData(); @@ -99,14 +99,14 @@ test.describe('Test Nodes Paginated Table', async () => { }); test('Refresh button updates the table data', async ({page}) => { - const paginatedTable = new PaginatedTable(page); + const paginatedTable = new ClusterNodesTable(page); await paginatedTable.waitForTableToLoad(); await paginatedTable.waitForTableData(); const initialUptimeValues = await paginatedTable.getColumnValues('Uptime'); await page.waitForTimeout(2000); // Wait for some time to pass - await paginatedTable.clickRefreshButton(); + await paginatedTable.getControls().clickRefreshButton(); await paginatedTable.waitForTableData(); const updatedUptimeValues = await paginatedTable.getColumnValues('Uptime'); @@ -114,12 +114,12 @@ test.describe('Test Nodes Paginated Table', async () => { }); test('Row data can be retrieved correctly', async ({page}) => { - const storageTable = new PaginatedTable(page); + const nodesTable = new ClusterNodesTable(page); - await storageTable.waitForTableToLoad(); - await storageTable.waitForTableData(); + await nodesTable.waitForTableToLoad(); + await nodesTable.waitForTableData(); - const rowData = await storageTable.getRowData(0); + const rowData = await nodesTable.getRowData(0); expect(rowData).toHaveProperty('Host'); expect(rowData).toHaveProperty('Uptime'); @@ -130,7 +130,7 @@ test.describe('Test Nodes Paginated Table', async () => { }); test('Column values can be retrieved correctly', async ({page}) => { - const paginatedTable = new PaginatedTable(page); + const paginatedTable = new ClusterNodesTable(page); await paginatedTable.waitForTableToLoad(); await paginatedTable.waitForTableData(); @@ -144,12 +144,12 @@ test.describe('Test Nodes Paginated Table', async () => { }); test('Table displays empty data message when no entities', async ({page}) => { - const paginatedTable = new PaginatedTable(page); + const paginatedTable = new ClusterNodesTable(page); await paginatedTable.waitForTableToLoad(); await paginatedTable.waitForTableData(); - await paginatedTable.search('Some Invalid search string !%#@[]'); + await paginatedTable.getControls().search('Some Invalid search string !%#@[]'); await paginatedTable.waitForTableData(); @@ -158,19 +158,19 @@ test.describe('Test Nodes Paginated Table', async () => { }); test('Autorefresh updates data when initially empty data', async ({page}) => { - const paginatedTable = new PaginatedTable(page); + const paginatedTable = new ClusterNodesTable(page); const emptyRequest = page.route(`${backend}/viewer/json/nodes?*`, async (route) => { await route.fulfill({json: {FoundNodes: 0, TotalNodes: 0, Nodes: []}}); }); - await paginatedTable.clickRefreshButton(); + await paginatedTable.getControls().clickRefreshButton(); await emptyRequest; const emptyDataMessage = await paginatedTable.getEmptyDataMessageLocator(); await expect(emptyDataMessage).toContainText('No such nodes'); - await paginatedTable.setRefreshInterval('15 sec'); + await paginatedTable.getControls().setRefreshInterval('15 sec'); const requestWithData = page.route(`${backend}/viewer/json/nodes?*`, async (route) => { await route.continue(); diff --git a/tests/suites/paginatedTable/mocks.ts b/tests/suites/paginatedTable/mocks.ts new file mode 100644 index 000000000..cfafbe894 --- /dev/null +++ b/tests/suites/paginatedTable/mocks.ts @@ -0,0 +1,114 @@ +import type {Page} from '@playwright/test'; + +import {backend} from '../../utils/constants'; + +const MOCK_DELAY = 200; // 200ms delay to simulate network latency + +interface NodeMockOptions { + offset: number; + limit: number; +} + +export const generateNodeMock = async ({offset, limit}: NodeMockOptions) => { + return Array.from({length: limit}, (_, i) => ({ + NodeId: offset + i + 1, + SystemState: { + Host: `host-${offset + i}.test`, + DataCenter: `dc-${Math.floor((offset + i) / 10)}`, + Rack: `rack-${Math.floor((offset + i) / 5)}`, + Version: 'main.b7cfb36', + StartTime: (Date.now() - 4 * 60 * 60 * 1000).toString(), // 4 hours ago + LoadAverage: [0.1, 0.2, 0.3], + NumberOfCpus: 8, + SystemState: 'Green', + MemoryUsed: ((8 + (i % 4)) * 1024 * 1024 * 1024).toString(), // 8-12GB + MemoryLimit: (16 * 1024 * 1024 * 1024).toString(), // 16GB + TotalSessions: 100, + Tenants: ['local'], + }, + CpuUsage: 10 + (i % 20), + UptimeSeconds: 4 * 60 * 60, // 4 hours + Disconnected: false, + Tablets: [ + { + TabletId: `tablet-${i}-1`, + Type: 'DataShard', + State: 'Active', + Leader: true, + }, + { + TabletId: `tablet-${i}-2`, + Type: 'DataShard', + State: 'Active', + Leader: false, + }, + ], + })); +}; + +export const setupNodesMock = async (page: Page) => { + await page.route(`${backend}/viewer/json/nodes?*`, async (route) => { + const url = new URL(route.request().url()); + const offset = parseInt(url.searchParams.get('offset') || '0', 10); + const limit = parseInt(url.searchParams.get('limit') || '50', 10); + + const nodes = await generateNodeMock({offset, limit}); + + await new Promise((resolve) => setTimeout(resolve, MOCK_DELAY)); + + await route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ + Overall: 'Green', + Nodes: nodes, + TotalNodes: '100', + FoundNodes: '100', + }), + }); + }); +}; + +export const setupEmptyNodesMock = async (page: Page) => { + await page.route(`${backend}/viewer/json/nodes?*`, async (route) => { + await new Promise((resolve) => setTimeout(resolve, MOCK_DELAY)); + + await route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ + Overall: 'Green', + Nodes: [], + TotalNodes: '0', + FoundNodes: '0', + }), + }); + }); +}; + +export const setupLargeNodesMock = async (page: Page, totalNodes = 1000) => { + await page.route(`${backend}/viewer/json/nodes?*`, async (route) => { + const url = new URL(route.request().url()); + const offset = parseInt(url.searchParams.get('offset') || '0', 10); + const limit = parseInt(url.searchParams.get('limit') || '100', 10); + + // Generate nodes for the requested chunk + const nodes = await generateNodeMock({ + offset, + limit: Math.min(limit, totalNodes - offset), // Ensure we don't generate more than total + }); + + await new Promise((resolve) => setTimeout(resolve, MOCK_DELAY)); + + await route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ + Overall: 'Green', + Nodes: nodes, + TotalNodes: totalNodes.toString(), + FoundNodes: totalNodes.toString(), + }), + }); + }); +}; diff --git a/tests/suites/paginatedTable/paginatedTable.test.ts b/tests/suites/paginatedTable/paginatedTable.test.ts new file mode 100644 index 000000000..2422c8a09 --- /dev/null +++ b/tests/suites/paginatedTable/paginatedTable.test.ts @@ -0,0 +1,143 @@ +import {expect, test} from '@playwright/test'; + +import {NodesPage} from '../nodes/NodesPage'; + +import {setupEmptyNodesMock, setupLargeNodesMock, setupNodesMock} from './mocks'; +import {ClusterNodesTable} from './paginatedTable'; + +test.describe('PaginatedTable', () => { + test('loads data in chunks when scrolling', async ({page}) => { + // Setup mocks + await setupNodesMock(page); + + // Navigate to nodes page which uses PaginatedTable + const nodesPage = new NodesPage(page); + await nodesPage.goto(); + + const paginatedTable = new ClusterNodesTable(page); + await paginatedTable.waitForTableVisible(); + await paginatedTable.waitForTableData(); + + // Get initial row count (should be first chunk) + const initialVisibleRows = await paginatedTable.getRowCount(); + expect(initialVisibleRows).toBeGreaterThan(0); + expect(initialVisibleRows).toBeLessThan(100); // Should not show all rows initially + + // Get data from first visible row to verify initial chunk + const firstRowData = await paginatedTable.getRowData(0); + expect(firstRowData['Host']).toBe('host-0.test'); + expect(firstRowData['Version']).toBe('main.b7cfb36'); + + await paginatedTable.scrollToBottom(); + + await paginatedTable.waitForTableData(); + + // Get data from last row to verify second chunk loaded + const rowCount = await paginatedTable.getRowCount(); + const lastRowData = await paginatedTable.getRowData(rowCount - 1); + expect(lastRowData['Host']).toBe('host-99.test'); + expect(lastRowData['Version']).toBe('main.b7cfb36'); + + // Verify uptime format matches the pattern from nodes.test.ts + const uptimeValues = await paginatedTable.getColumnValues('Uptime'); + for (const uptime of uptimeValues) { + expect(uptime).toMatch(/^(\d+d\s)?(\d+):(\d{2}):(\d{2})$/); // Format: DDd? HH:MM:SS + } + }); + + test('loads data when scrolling to middle of table', async ({page}) => { + // Setup mocks with large dataset + await setupLargeNodesMock(page); + + // Navigate to nodes page which uses PaginatedTable + const nodesPage = new NodesPage(page); + await nodesPage.goto(); + + const paginatedTable = new ClusterNodesTable(page); + await paginatedTable.waitForTableVisible(); + await paginatedTable.waitForTableData(); + + // Get initial row count + const initialVisibleRows = await paginatedTable.getRowCount(); + expect(initialVisibleRows).toBeGreaterThan(0); + expect(initialVisibleRows).toBeLessThan(1000); // Should not show all rows initially + + // Scroll to middle of container + await paginatedTable.scrollToMiddle(); + await paginatedTable.waitForTableData(); + + // Get data from middle rows to verify middle chunk loaded + const rowCount = await paginatedTable.getRowCount(); + const middleRowIndex = Math.floor(rowCount / 2); + const middleRowData = await paginatedTable.getRowData(middleRowIndex); + expect(middleRowData['Host']).toBe('host-500.test'); + expect(middleRowData['Version']).toBe('main.b7cfb36'); + }); + + test('displays empty state when no data is present', async ({page}) => { + // Setup mocks with empty data + await setupEmptyNodesMock(page); + + const nodesPage = new NodesPage(page); + await nodesPage.goto(); + + const paginatedTable = new ClusterNodesTable(page); + await paginatedTable.waitForTableVisible(); + + // Verify empty state + const rowCount = await paginatedTable.getRowCount(); + expect(rowCount).toBe(1); + const emptyDataMessage = await paginatedTable.getEmptyDataMessageLocator(); + await expect(emptyDataMessage).toContainText('No such nodes'); + }); + + test('handles 10 pages of data correctly', async ({page}) => { + // Setup mocks with 1000 nodes (100 per page * 10 pages) + await setupLargeNodesMock(page); + + const nodesPage = new NodesPage(page); + await nodesPage.goto(); + + const paginatedTable = new ClusterNodesTable(page); + await paginatedTable.waitForTableVisible(); + await paginatedTable.waitForTableData(); + + // Verify initial data load + const initialRowCount = await paginatedTable.getRowCount(); + expect(initialRowCount).toBeGreaterThan(0); + expect(initialRowCount).toBeLessThan(1000); // Should not load all rows at once + + await paginatedTable.scrollToBottom(); + await paginatedTable.waitForTableData(); + + // Verify we can load data from the last page + const finalRowCount = await paginatedTable.getRowCount(); + const lastRowData = await paginatedTable.getRowData(finalRowCount - 1); + expect(lastRowData['Host']).toBe('host-999.test'); // Last node in 1000 nodes (0-999) + }); + + test('handles 100 pages of data correctly', async ({page}) => { + // Setup mocks with 10000 nodes (100 per page * 10 pages) + await setupLargeNodesMock(page, 10000); + + const nodesPage = new NodesPage(page); + await nodesPage.goto(); + + const paginatedTable = new ClusterNodesTable(page); + await paginatedTable.waitForTableVisible(); + await paginatedTable.waitForTableData(); + + // Verify initial data load + const initialRowCount = await paginatedTable.getRowCount(); + expect(initialRowCount).toBeGreaterThan(0); + expect(initialRowCount).toBeLessThan(10000); // Should not load all rows at once + + await paginatedTable.scrollToBottom(); + await paginatedTable.waitForTableData(); + + // Verify we can load data from the last page + const finalRowCount = await paginatedTable.getRowCount(); + const lastRowData = await paginatedTable.getRowData(finalRowCount - 1); + expect(lastRowData['Host']).toBe('host-9999.test'); // Last node in 1000 nodes (0-999) + }); +}); diff --git a/tests/suites/paginatedTable/paginatedTable.ts b/tests/suites/paginatedTable/paginatedTable.ts index 8ec0d5c71..2a9f95533 100644 --- a/tests/suites/paginatedTable/paginatedTable.ts +++ b/tests/suites/paginatedTable/paginatedTable.ts @@ -2,29 +2,23 @@ import type {Locator, Page} from '@playwright/test'; import {VISIBILITY_TIMEOUT} from '../tenant/TenantPage'; -export class PaginatedTable { - private page: Page; - private tableSelector: Locator; - private searchInput: Locator; - private radioButtons: Locator; - private countLabel: Locator; - private tableRows: Locator; - private emptyTableRows: Locator; - private refreshButton: Locator; - private refreshIntervalSelect: Locator; - private headCells: Locator; - private columnSetupButton: Locator; - private columnSetupPopup: Locator; - - constructor(page: Page) { +export class TableControls { + protected page: Page; + protected tableSelector: Locator; + protected searchInput: Locator; + protected radioButtons: Locator; + protected countLabel: Locator; + protected refreshButton: Locator; + protected refreshIntervalSelect: Locator; + protected columnSetupButton: Locator; + protected columnSetupPopup: Locator; + + constructor(page: Page, tableSelector: Locator) { this.page = page; - this.tableSelector = page.locator('.ydb-table-with-controls-layout'); + this.tableSelector = tableSelector; this.searchInput = this.tableSelector.locator('.ydb-search input'); this.radioButtons = this.tableSelector.locator('.g-radio-button'); this.countLabel = this.tableSelector.locator('.ydb-entities-count .g-label__content'); - this.headCells = this.tableSelector.locator('.ydb-paginated-table__head-cell'); - this.tableRows = this.tableSelector.locator('.ydb-paginated-table__row'); - this.emptyTableRows = this.tableSelector.locator('.ydb-paginated-table__row_empty'); this.refreshButton = page.locator('.auto-refresh-control button[aria-label="Refresh"]'); this.refreshIntervalSelect = page.getByTestId('ydb-autorefresh-select'); this.columnSetupButton = this.tableSelector.locator( @@ -33,10 +27,6 @@ export class PaginatedTable { this.columnSetupPopup = page.locator('.g-popup .g-select-popup.g-tree-select__popup'); } - async waitForTableVisible() { - await this.tableSelector.waitFor({state: 'visible', timeout: VISIBILITY_TIMEOUT}); - } - async search(searchTerm: string) { await this.searchInput.fill(searchTerm); } @@ -44,7 +34,6 @@ export class PaginatedTable { async selectRadioOption(groupIndex: number, optionText: string) { const radioGroup = this.radioButtons.nth(groupIndex); const option = radioGroup.locator(`.g-radio-button__option:has-text("${optionText}")`); - await option.evaluate((el) => (el as HTMLElement).click()); } @@ -54,6 +43,100 @@ export class PaginatedTable { return match ? parseInt(match[1], 10) : 0; } + async clickRefreshButton() { + await this.refreshButton.click(); + } + + async setRefreshInterval(interval: string) { + await this.refreshIntervalSelect.click(); + await this.page.locator('.g-select-list__option', {hasText: interval}).click(); + } + + async getRefreshInterval(): Promise { + const text = await this.refreshIntervalSelect + .locator('.g-select-control__option-text') + .innerText(); + return text; + } + + async openColumnSetup() { + await this.columnSetupButton.click(); + await this.columnSetupPopup.waitFor({state: 'visible'}); + } + + async setColumnChecked(columnName: string) { + const columnOption = this.columnSetupPopup.locator(`[data-list-item="${columnName}"]`); + const checkIcon = columnOption.locator('.g-icon.g-color-text_color_info'); + const isVisible = await checkIcon.isVisible(); + if (!isVisible) { + await columnOption.click(); + } + } + + async setColumnUnchecked(columnName: string) { + const columnOption = this.columnSetupPopup.locator(`[data-list-item="${columnName}"]`); + const checkIcon = columnOption.locator('.g-icon.g-color-text_color_info'); + const isVisible = await checkIcon.isVisible(); + if (isVisible) { + await columnOption.click(); + } + } + + async applyColumnVisibility() { + const applyButton = this.columnSetupPopup.locator('button:has-text("Apply")'); + await applyButton.click(); + await this.columnSetupPopup.waitFor({state: 'hidden'}); + } + + async getVisibleColumnsCount(): Promise { + const statusText = await this.columnSetupButton + .locator('.g-table-column-setup__status') + .innerText(); + return statusText; + } + + async isColumnVisible(columnName: string): Promise { + const columnOption = this.columnSetupPopup.locator(`[data-list-item="${columnName}"]`); + const checkIcon = columnOption.locator('.g-icon.g-color-text_color_info'); + return await checkIcon.isVisible(); + } +} + +export class PaginatedTable { + protected controls: TableControls; + protected page: Page; + private tableSelector: Locator; + private tableRows: Locator; + private emptyTableRows: Locator; + private headCells: Locator; + private scrollContainer: string; + + constructor(page: Page, scrollContainer = '.ydb-cluster') { + this.page = page; + this.tableSelector = page.locator('.ydb-table-with-controls-layout'); + this.headCells = this.tableSelector.locator('.ydb-paginated-table__head-cell'); + this.tableRows = this.tableSelector.locator('.ydb-paginated-table__row'); + this.emptyTableRows = this.tableSelector.locator('.ydb-paginated-table__row_empty'); + this.scrollContainer = scrollContainer; + this.controls = new TableControls(page, this.tableSelector); + } + + getControls(): TableControls { + return this.controls; + } + + async waitForTableVisible() { + await this.tableSelector.waitFor({state: 'visible', timeout: VISIBILITY_TIMEOUT}); + } + + async getCount(): Promise { + return this.controls.getCount(); + } + + async search(searchTerm: string) { + await this.controls.search(searchTerm); + } + async getColumnValues(columnName: string): Promise { const columnIndex = await this.getColumnIndex(columnName); return this.tableRows.evaluateAll( @@ -109,19 +192,6 @@ export class PaginatedTable { await this.page.waitForTimeout(1000); } - async clickRefreshButton() { - await this.refreshButton.click(); - } - - async setRefreshInterval(interval: string) { - await this.refreshIntervalSelect.click(); - await this.page.locator('.g-select-list__option', {hasText: interval}).click(); - } - - async getRefreshInterval(): Promise { - return this.refreshIntervalSelect.locator('.g-select-control__option-text').innerText(); - } - async sortByColumn(columnName: string) { const columnHeader = this.tableSelector.locator( `.ydb-paginated-table__head-cell:has-text("${columnName}")`, @@ -131,46 +201,25 @@ export class PaginatedTable { await this.waitForTableData(); } - async openColumnSetup() { - await this.columnSetupButton.click(); - await this.columnSetupPopup.waitFor({state: 'visible'}); - } - - async setColumnChecked(columnName: string) { - const columnOption = this.columnSetupPopup.locator(`[data-list-item="${columnName}"]`); - const checkIcon = columnOption.locator('.g-icon.g-color-text_color_info'); - const isVisible = await checkIcon.isVisible(); - if (!isVisible) { - await columnOption.click(); - } - } - - async setColumnUnchecked(columnName: string) { - const columnOption = this.columnSetupPopup.locator(`[data-list-item="${columnName}"]`); - const checkIcon = columnOption.locator('.g-icon.g-color-text_color_info'); - const isVisible = await checkIcon.isVisible(); - if (isVisible) { - await columnOption.click(); - } - } - - async applyColumnVisibility() { - const applyButton = this.columnSetupPopup.locator('button:has-text("Apply")'); - await applyButton.click(); - await this.columnSetupPopup.waitFor({state: 'hidden'}); - } - - async getVisibleColumnsCount(): Promise { - const statusText = await this.columnSetupButton - .locator('.g-table-column-setup__status') - .innerText(); - return statusText; + async scrollToBottom() { + await this.page.evaluate((selector) => { + const container = document.querySelector(selector); + if (container) { + container.scrollTo({top: container.scrollHeight, behavior: 'instant'}); + } + }, this.scrollContainer); } - async isColumnVisible(columnName: string): Promise { - const columnOption = this.columnSetupPopup.locator(`[data-list-item="${columnName}"]`); - const checkIcon = columnOption.locator('.g-icon.g-color-text_color_info'); - return await checkIcon.isVisible(); + async scrollToMiddle() { + await this.page.evaluate((selector) => { + const container = document.querySelector(selector); + if (container) { + container.scrollTo({ + top: Math.floor(container.scrollHeight / 2), + behavior: 'instant', + }); + } + }, this.scrollContainer); } private async getColumnIndex(columnName: string): Promise { @@ -184,3 +233,15 @@ export class PaginatedTable { throw new Error(`Column "${columnName}" not found`); } } + +export class ClusterNodesTable extends PaginatedTable { + constructor(page: Page) { + super(page, '.ydb-cluster'); + } +} + +export class ClusterStorageTable extends PaginatedTable { + constructor(page: Page) { + super(page, '.ydb-cluster'); + } +} diff --git a/tests/suites/storage/storage.test.ts b/tests/suites/storage/storage.test.ts index 95e64efa4..bd99bf846 100644 --- a/tests/suites/storage/storage.test.ts +++ b/tests/suites/storage/storage.test.ts @@ -1,6 +1,6 @@ import {expect, test} from '@playwright/test'; -import {PaginatedTable} from '../paginatedTable/paginatedTable'; +import {ClusterStorageTable} from '../paginatedTable/paginatedTable'; import {StoragePage} from './StoragePage'; @@ -46,7 +46,7 @@ test.describe('Test Storage Paginated Table', async () => { }); test('Table loads and displays data', async ({page}) => { - const paginatedTable = new PaginatedTable(page); + const paginatedTable = new ClusterStorageTable(page); await paginatedTable.waitForTableToLoad(); await paginatedTable.waitForTableData(); @@ -56,7 +56,7 @@ test.describe('Test Storage Paginated Table', async () => { }); test('Search by pool name filters the table', async ({page}) => { - const paginatedTable = new PaginatedTable(page); + const paginatedTable = new ClusterStorageTable(page); await paginatedTable.waitForTableToLoad(); await paginatedTable.waitForTableData(); @@ -71,13 +71,13 @@ test.describe('Test Storage Paginated Table', async () => { }); test('Radio button selection changes displayed data', async ({page}) => { - const paginatedTable = new PaginatedTable(page); + const paginatedTable = new ClusterStorageTable(page); await paginatedTable.waitForTableToLoad(); await paginatedTable.waitForTableData(); const initialRowCount = await paginatedTable.getRowCount(); - await paginatedTable.selectRadioOption(0, 'Nodes'); + await paginatedTable.getControls().selectRadioOption(0, 'Nodes'); await page.waitForTimeout(1000); // Wait for the table to update @@ -86,7 +86,7 @@ test.describe('Test Storage Paginated Table', async () => { }); test('Groups count is displayed correctly', async ({page}) => { - const paginatedTable = new PaginatedTable(page); + const paginatedTable = new ClusterStorageTable(page); await paginatedTable.waitForTableToLoad(); await paginatedTable.waitForTableData(); @@ -98,7 +98,7 @@ test.describe('Test Storage Paginated Table', async () => { }); test('Row data can be retrieved correctly', async ({page}) => { - const storageTable = new PaginatedTable(page); + const storageTable = new ClusterStorageTable(page); await storageTable.waitForTableToLoad(); await storageTable.waitForTableData(); @@ -113,7 +113,7 @@ test.describe('Test Storage Paginated Table', async () => { }); test('Column values can be retrieved correctly', async ({page}) => { - const paginatedTable = new PaginatedTable(page); + const paginatedTable = new ClusterStorageTable(page); await paginatedTable.waitForTableToLoad(); await paginatedTable.waitForTableData(); @@ -127,7 +127,7 @@ test.describe('Test Storage Paginated Table', async () => { }); test('Clicking on Group ID header sorts the table', async ({page}) => { - const storageTable = new PaginatedTable(page); + const storageTable = new ClusterStorageTable(page); await storageTable.waitForTableToLoad(); await storageTable.waitForTableData();