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();