diff --git a/packages/ui/src/shared/yt-types.d.ts b/packages/ui/src/shared/yt-types.d.ts index 56b59704a..c1a2ba179 100644 --- a/packages/ui/src/shared/yt-types.d.ts +++ b/packages/ui/src/shared/yt-types.d.ts @@ -313,6 +313,10 @@ export type PipelineParams = { pipeline_path: string; }; +export type TableParams = { + path: string; +}; + export type ExpectedVersion = { expected_version?: string | number; }; diff --git a/packages/ui/src/ui/pages/navigation/content/Table/RemountAlert/RemountAlert.scss b/packages/ui/src/ui/pages/navigation/content/Table/RemountAlert/RemountAlert.scss new file mode 100644 index 000000000..34936d325 --- /dev/null +++ b/packages/ui/src/ui/pages/navigation/content/Table/RemountAlert/RemountAlert.scss @@ -0,0 +1,5 @@ +.remount-alert { + &__button { + width: fit-content; + } +} diff --git a/packages/ui/src/ui/pages/navigation/content/Table/RemountAlert/RemountAlert.tsx b/packages/ui/src/ui/pages/navigation/content/Table/RemountAlert/RemountAlert.tsx new file mode 100644 index 000000000..e62dde4af --- /dev/null +++ b/packages/ui/src/ui/pages/navigation/content/Table/RemountAlert/RemountAlert.tsx @@ -0,0 +1,56 @@ +import React, {useState} from 'react'; +import b from 'bem-cn-lite'; +import {useDispatch, useSelector} from 'react-redux'; +import {Alert, Button} from '@gravity-ui/uikit'; +import ypath from '../../../../../common/thor/ypath'; + +import {remountTable} from '../../../../../store/actions/navigation/content/table/remount-table'; +import {getAttributesWithTypes} from '../../../../../store/selectors/navigation'; + +import './RemountAlert.scss'; + +const block = b('remount-alert'); + +export function RemountAlert() { + const dispatch = useDispatch(); + const attributesWithTypes = useSelector(getAttributesWithTypes); + const [pending, setPending] = useState(false); + + const handleOnRemount = async () => { + setPending(true); + await dispatch(remountTable()); + setPending(false); + }; + + const [remountNeededTabletCount, tabletCount] = ypath.getValues(attributesWithTypes, [ + '/remount_needed_tablet_count', + '/tablet_count', + ]); + + const showDiffInfo = remountNeededTabletCount !== tabletCount; + const diffInfo = ` (${remountNeededTabletCount} of ${tabletCount} tablets pending)`; + + const message = `Table should be remounted to apply new settings${showDiffInfo ? diffInfo : ''}. + This action will not cause any downtime. See Mount config tab for details.`; + + return ( + + Remount + + } + > + ); +} diff --git a/packages/ui/src/ui/pages/navigation/content/Table/TableMeta/TableMeta.tsx b/packages/ui/src/ui/pages/navigation/content/Table/TableMeta/TableMeta.tsx index 240a94586..dd614b9c2 100644 --- a/packages/ui/src/ui/pages/navigation/content/Table/TableMeta/TableMeta.tsx +++ b/packages/ui/src/ui/pages/navigation/content/Table/TableMeta/TableMeta.tsx @@ -1,27 +1,31 @@ import React, {useMemo} from 'react'; import cn from 'bem-cn-lite'; import {connect, useSelector} from 'react-redux'; +import ypath from '../../../../../common/thor/ypath'; import {makeMetaItems} from '../../../../../components/MetaTable/presets/presets'; import CollapsibleSection from '../../../../../components/CollapsibleSection/CollapsibleSection'; import MetaTable from '../../../../../components/MetaTable/MetaTable'; +import {RemountAlert} from '../RemountAlert/RemountAlert'; import {getTableType} from '../../../../../store/selectors/navigation/content/table'; import {getIsDynamic} from '../../../../../store/selectors/navigation/content/table-ts'; -import {getAttributes} from '../../../../../store/selectors/navigation'; +import {getAttributes, getAttributesWithTypes} from '../../../../../store/selectors/navigation'; import {getTabletErrorsBackgroundCount} from '../../../../../store/selectors/navigation/tabs/tablet-errors-background'; import {Props as AutomaticModeSwitchProps} from './AutomaticModeSwitch'; +import {RootState} from '../../../../../store/reducers'; import {getCluster} from '../../../../../store/selectors/global'; -import './TableMeta.scss'; -import {RootState} from '../../../../../store/reducers'; import {UI_COLLAPSIBLE_SIZE} from '../../../../../constants/global'; +import './TableMeta.scss'; + const block = cn('navigation-meta-table'); interface Props { attributes: any; + attributesWithTypes: any; mediumList: string[]; isDynamic: boolean; tableType: string; @@ -30,6 +34,7 @@ interface Props { function TableMeta({ attributes, + attributesWithTypes, tableType, mediumList, isDynamic, @@ -58,9 +63,14 @@ function TableMeta({ onEditEnableReplicatedTableTracker, ]); + const remountNeeded = + Boolean(Number(ypath.getValue(attributesWithTypes, '/remount_needed_tablet_count'))) && + isDynamic; + return ( + {remountNeeded && } ); } @@ -71,9 +81,11 @@ const mapStateToProps = (state: RootState) => { const isDynamic = getIsDynamic(state); const tableType = getTableType(state); const attributes = getAttributes(state); + const attributesWithTypes = getAttributesWithTypes(state); return { attributes, + attributesWithTypes, mediumList, isDynamic, tableType, diff --git a/packages/ui/src/ui/rum/rum-wrap-api.ts b/packages/ui/src/ui/rum/rum-wrap-api.ts index 5f44869c3..de92dfa73 100644 --- a/packages/ui/src/ui/rum/rum-wrap-api.ts +++ b/packages/ui/src/ui/rum/rum-wrap-api.ts @@ -15,6 +15,7 @@ import { OutputFormat, PathParams, PipelineParams, + TableParams, } from '../../shared/yt-types'; import {YTApiId} from '../../shared/constants/yt-api-id'; @@ -102,6 +103,8 @@ type YTApiV4 = { getPipelineState(...args: ApiMethodParameters): Promise; getFlowView(...args: ApiMethodParameters): Promise; + remountTable(...args: ApiMethodParameters): Promise; + [method: string]: (...args: ApiMethodParameters) => Promise; }; diff --git a/packages/ui/src/ui/store/actions/navigation/content/table/remount-table.ts b/packages/ui/src/ui/store/actions/navigation/content/table/remount-table.ts new file mode 100644 index 000000000..a30c76a5d --- /dev/null +++ b/packages/ui/src/ui/store/actions/navigation/content/table/remount-table.ts @@ -0,0 +1,23 @@ +import {ThunkAction} from 'redux-thunk'; +import {UnknownAction} from '@reduxjs/toolkit'; + +import {RootState} from '../../../../reducers'; +import {updateView} from '../..'; + +import {wrapApiPromiseByToaster} from '../../../../../utils/utils'; +import {ytApiV4} from '../../../../../rum/rum-wrap-api'; + +type AsyncAction = ThunkAction; + +export function remountTable(): AsyncAction> { + return async (dispatch, getState) => { + const state = getState(); + const path = state.navigation.navigation.path; + + return wrapApiPromiseByToaster(ytApiV4.remountTable({path}), { + toasterName: 'remount_tabe', + }).finally(() => { + dispatch(updateView()); + }); + }; +} diff --git a/packages/ui/src/ui/store/actions/navigation/index.js b/packages/ui/src/ui/store/actions/navigation/index.js index 2b3e37bc9..4d695d27a 100644 --- a/packages/ui/src/ui/store/actions/navigation/index.js +++ b/packages/ui/src/ui/store/actions/navigation/index.js @@ -308,6 +308,7 @@ const attributesToLoad = [ 'effective_expiration', 'replicated_table_options', 'replica_path', + 'remount_needed_tablet_count', 'erasure_codec', 'id', 'in_memory_mode', @@ -335,6 +336,7 @@ const attributesToLoad = [ 'start_time', 'state', 'tablet_cell_bundle', + 'tablet_count', 'tablet_error_count', 'tablet_state', 'target_path', diff --git a/packages/ui/tests/init-cluster-e2e.sh b/packages/ui/tests/init-cluster-e2e.sh index 604fd37ec..cc2975581 100755 --- a/packages/ui/tests/init-cluster-e2e.sh +++ b/packages/ui/tests/init-cluster-e2e.sh @@ -18,6 +18,7 @@ function createAndMountDynamicTable { schema=$2 yt create -i --attributes "{dynamic=%true;schema=$schema}" table $path yt mount-table $path + yt set $path/@mount_config/temp 1 } # userColumnPresets diff --git a/packages/ui/tests/screenshots/pages/navigation/TablePage.ts b/packages/ui/tests/screenshots/pages/navigation/TablePage.ts index 1ad898c17..7a73bc3bb 100644 --- a/packages/ui/tests/screenshots/pages/navigation/TablePage.ts +++ b/packages/ui/tests/screenshots/pages/navigation/TablePage.ts @@ -3,13 +3,13 @@ import {replaceInnerHtml} from '../../../utils/dom'; import type {Locator} from '@playwright/test'; export class TablePage extends NavigationPage { - async waitForTablContent(selector: string, rowCount: number) { + async waitForTableContent(selector: string, rowCount: number) { await this.waitForTable(selector, rowCount); await this.page.waitForSelector(':text("Data weight")'); await this.waitForTableSyncedWidth(selector); } - async replaceStaticTableMeta() { + async replaceTableMeta() { await this.replaceBreadcrumbsTestDir(); await replaceInnerHtml(this.page, { '[data-qa="expiration_timeout_path"]': 'e2e.1970-01-01.00:00:00.xxxxxxxxxxx', diff --git a/packages/ui/tests/screenshots/pages/navigation/navigation.table.base.screen.ts b/packages/ui/tests/screenshots/pages/navigation/navigation.table.base.screen.ts index 8fc510445..d6d78ed86 100644 --- a/packages/ui/tests/screenshots/pages/navigation/navigation.table.base.screen.ts +++ b/packages/ui/tests/screenshots/pages/navigation/navigation.table.base.screen.ts @@ -11,8 +11,8 @@ function tablePage(page: Page) { test('Navigation: table - Content', async ({page}) => { await page.goto(makeClusterUrl(`navigation?path=${E2E_DIR}/static-table`)); - await tablePage(page).waitForTablContent('.navigation-table', 10); - await tablePage(page).replaceStaticTableMeta(); + await tablePage(page).waitForTableContent('.navigation-table', 10); + await tablePage(page).replaceTableMeta(); await expect(page).toHaveScreenshot(); @@ -63,6 +63,29 @@ test('Navigation: table - Schema', async ({page}) => { await expect(page).toHaveScreenshot(); }); +test('Navigation: table - Remount needed', async ({page}) => { + await page.goto(makeClusterUrl(`navigation?path=${E2E_DIR}/dynamic-table`)); + + page.locator('.data-table_theme_yt-internal').waitFor(); + + await tablePage(page).replaceBreadcrumbsTestDir(); + await tablePage(page).replaceTableMeta(); + + await test.step(('Remount alert visible'), async () => { + await expect(page).toHaveScreenshot(); + }); + await test.step(('Page after remount'), async () => { + await page.locator('.remount-alert__button').click(); + + await expect(page.locator('.remount-alert')).toBeHidden(); + + await tablePage(page).replaceBreadcrumbsTestDir(); + await tablePage(page).replaceTableMeta(); + + await expect(page).toHaveScreenshot(); + }); +}); + test('Navigation: table - Tablets', async ({page}) => { await page.goto(makeClusterUrl(`navigation?path=${E2E_DIR}/dynamic-table&navmode=tablets`)); @@ -81,8 +104,8 @@ test('Navigation: table - Tablets', async ({page}) => { test('Navigation: static-table - rowselector', async ({page}) => { await page.goto(makeClusterUrl(`navigation?path=${E2E_DIR}/static-table`)); - await tablePage(page).replaceStaticTableMeta(); - await tablePage(page).waitForTablContent('.navigation-table', 10); + await tablePage(page).replaceTableMeta(); + await tablePage(page).waitForTableContent('.navigation-table', 10); await page.click('.navigation-table-overview__input', {force: true}); const slider = await page.waitForSelector('.rc-slider-handle'); @@ -108,8 +131,8 @@ test('Navigation: table - userColumnPresets', async ({page, context}) => { await page.goto(makeClusterUrl(`navigation?path=${E2E_DIR}/static-table`)); - await tablePage(page).waitForTablContent('.navigation-table', 10); - await tablePage(page).replaceStaticTableMeta(); + await tablePage(page).waitForTableContent('.navigation-table', 10); + await tablePage(page).replaceTableMeta(); await test.step('select only the "key" column', async () => { await page.getByTestId('table-columns-button').click(); @@ -138,8 +161,8 @@ test('Navigation: table - userColumnPresets', async ({page, context}) => { await context.waitForEvent('page', { predicate: async (page) => { - await tablePage(page).waitForTablContent('.navigation-table', 10); - await tablePage(page).replaceStaticTableMeta(); + await tablePage(page).waitForTableContent('.navigation-table', 10); + await tablePage(page).replaceTableMeta(); await page.getByText('key0').waitFor(); @@ -154,8 +177,8 @@ test('Navigation: table - userColumnPresets', async ({page, context}) => { test('Navigation: yql-v3-types', async ({page}) => { await page.goto(makeClusterUrl(`navigation?path=${E2E_DIR}/tmp/yql-v3-types-table`)); - await tablePage(page).replaceStaticTableMeta(); - await tablePage(page).waitForTablContent('.navigation-table', 5); + await tablePage(page).replaceTableMeta(); + await tablePage(page).waitForTableContent('.navigation-table', 5); await test.step('yql-v3-types enabled', async () => { await expect(page).toHaveScreenshot(); @@ -170,7 +193,7 @@ test('Navigation: yql-v3-types', async ({page}) => { await test.step('yql-v3-types disabled', async () => { await tablePage(page).waitForHidden('.data-table__row .yql_datetime64'); - await tablePage(page).waitForTablContent('.navigation-table', 5); + await tablePage(page).waitForTableContent('.navigation-table', 5); await expect(page).toHaveScreenshot(); }); }); diff --git a/packages/ui/tests/screenshots/pages/navigation/navigation.table.base.screen.ts-snapshots/Navigation-table---Remount-needed-1-chromium-linux.png b/packages/ui/tests/screenshots/pages/navigation/navigation.table.base.screen.ts-snapshots/Navigation-table---Remount-needed-1-chromium-linux.png new file mode 100644 index 000000000..35f98e06b Binary files /dev/null and b/packages/ui/tests/screenshots/pages/navigation/navigation.table.base.screen.ts-snapshots/Navigation-table---Remount-needed-1-chromium-linux.png differ diff --git a/packages/ui/tests/screenshots/pages/navigation/navigation.table.base.screen.ts-snapshots/Navigation-table---Remount-needed-2-chromium-linux.png b/packages/ui/tests/screenshots/pages/navigation/navigation.table.base.screen.ts-snapshots/Navigation-table---Remount-needed-2-chromium-linux.png new file mode 100644 index 000000000..8ca4ea3bb Binary files /dev/null and b/packages/ui/tests/screenshots/pages/navigation/navigation.table.base.screen.ts-snapshots/Navigation-table---Remount-needed-2-chromium-linux.png differ diff --git a/packages/ui/tests/screenshots/pages/navigation/navigation.table.truncated.base.screen.ts b/packages/ui/tests/screenshots/pages/navigation/navigation.table.truncated.base.screen.ts index 89530e71e..461853cbd 100644 --- a/packages/ui/tests/screenshots/pages/navigation/navigation.table.truncated.base.screen.ts +++ b/packages/ui/tests/screenshots/pages/navigation/navigation.table.truncated.base.screen.ts @@ -9,8 +9,8 @@ test('Navigation: truncated table - Content', async ({page}) => { waitUntil: 'networkidle', }); - await tablePage.waitForTablContent('.navigation-table', 1); - await tablePage.replaceStaticTableMeta(); + await tablePage.waitForTableContent('.navigation-table', 1); + await tablePage.replaceTableMeta(); await tablePage.resizePageForScreenshot();