diff --git a/src/containers/Tenant/Schema/SchemaTree/SchemaTree.tsx b/src/containers/Tenant/Schema/SchemaTree/SchemaTree.tsx index 2f7d52c0a..98fd08c98 100644 --- a/src/containers/Tenant/Schema/SchemaTree/SchemaTree.tsx +++ b/src/containers/Tenant/Schema/SchemaTree/SchemaTree.tsx @@ -7,7 +7,11 @@ import {NavigationTree} from 'ydb-ui-components'; import {useCreateDirectoryFeatureAvailable} from '../../../../store/reducers/capabilities/hooks'; import {schemaApi} from '../../../../store/reducers/schema/schema'; +import {tableSchemaDataApi} from '../../../../store/reducers/tableSchemaData'; +import type {GetTableSchemaDataParams} from '../../../../store/reducers/tableSchemaData'; import type {EPathType, TEvDescribeSchemeResult} from '../../../../types/api/schema'; +import {wait} from '../../../../utils'; +import {SECOND_IN_MS} from '../../../../utils/constants'; import {useQueryExecutionSettings, useTypedDispatch} from '../../../../utils/hooks'; import {getSchemaControls} from '../../utils/controls'; import {isChildlessPathType, mapPathTypeToNavigationTreeType} from '../../utils/schema'; @@ -22,10 +26,28 @@ interface SchemaTreeProps { onActivePathUpdate: (path: string) => void; } +const TABLE_SCHEMA_TIMEOUT = SECOND_IN_MS * 2; + export function SchemaTree(props: SchemaTreeProps) { const createDirectoryFeatureAvailable = useCreateDirectoryFeatureAvailable(); const {rootPath, rootName, rootType, currentPath, onActivePathUpdate} = props; const dispatch = useTypedDispatch(); + const [getTableSchemaDataMutation] = tableSchemaDataApi.useGetTableSchemaDataMutation(); + + const getTableSchemaDataPromise = React.useCallback( + async (args: GetTableSchemaDataParams) => { + try { + const result = await Promise.race([ + getTableSchemaDataMutation(args).unwrap(), + wait(TABLE_SCHEMA_TIMEOUT), + ]); + return result; + } catch (e) { + return undefined; + } + }, + [getTableSchemaDataMutation], + ); const [querySettings, setQueryExecutionSettings] = useQueryExecutionSettings(); const [createDirectoryOpen, setCreateDirectoryOpen] = React.useState(false); @@ -119,6 +141,7 @@ export function SchemaTree(props: SchemaTreeProps) { showCreateDirectoryDialog: createDirectoryFeatureAvailable ? handleOpenCreateDirectoryDialog : undefined, + getTableSchemaDataPromise, }, rootPath, )} diff --git a/src/containers/Tenant/utils/schema.ts b/src/containers/Tenant/utils/schema.ts index 4257c3e1c..1aaac1300 100644 --- a/src/containers/Tenant/utils/schema.ts +++ b/src/containers/Tenant/utils/schema.ts @@ -41,6 +41,14 @@ const pathTypeToNodeType: Record [EPathType.EPathTypeReplication]: 'async_replication', }; +export const nodeTableTypeToPathType: Partial> = { + table: EPathType.EPathTypeTable, + index: EPathType.EPathTypeTableIndex, + column_table: EPathType.EPathTypeColumnTable, + external_table: EPathType.EPathTypeExternalTable, + view: EPathType.EPathTypeView, +}; + export const mapPathTypeToNavigationTreeType = ( type: EPathType = EPathType.EPathTypeDir, subType?: EPathSubType, diff --git a/src/containers/Tenant/utils/schemaActions.ts b/src/containers/Tenant/utils/schemaActions.ts index 590b4e2ed..04bed6ab4 100644 --- a/src/containers/Tenant/utils/schemaActions.ts +++ b/src/containers/Tenant/utils/schemaActions.ts @@ -1,15 +1,19 @@ import copy from 'copy-to-clipboard'; import type {NavigationTreeNodeType, NavigationTreeProps} from 'ydb-ui-components'; +import type {AppDispatch} from '../../../store'; import {changeUserInput} from '../../../store/reducers/executeQuery'; +import type {GetTableSchemaDataParams} from '../../../store/reducers/tableSchemaData'; import {TENANT_PAGES_IDS, TENANT_QUERY_TABS_ID} from '../../../store/reducers/tenant/constants'; import {setQueryTab, setTenantPage} from '../../../store/reducers/tenant/tenant'; import type {QueryMode, QuerySettings} from '../../../types/store/query'; import createToast from '../../../utils/createToast'; import {transformPath} from '../ObjectSummary/transformPath'; +import type {SchemaData} from '../Schema/SchemaViewer/types'; import i18n from '../i18n'; -import type {SchemaQueryParams} from './schemaQueryTemplates'; +import {nodeTableTypeToPathType} from './schema'; +import type {TemplateFn} from './schemaQueryTemplates'; import { addTableIndex, alterAsyncReplicationTemplate, @@ -34,31 +38,60 @@ interface ActionsAdditionalEffects { updateQueryExecutionSettings: (settings?: Partial) => void; setActivePath: (path: string) => void; showCreateDirectoryDialog?: (path: string) => void; + getTableSchemaDataPromise?: ( + params: GetTableSchemaDataParams, + ) => Promise; +} + +interface BindActionParams { + tenantName: string; + type: NavigationTreeNodeType; + path: string; + relativePath: string; } const bindActions = ( - schemaQueryParams: SchemaQueryParams, - dispatch: React.Dispatch, + params: BindActionParams, + dispatch: AppDispatch, additionalEffects: ActionsAdditionalEffects, ) => { - const {setActivePath, updateQueryExecutionSettings, showCreateDirectoryDialog} = - additionalEffects; - - const inputQuery = (tmpl: (params?: SchemaQueryParams) => string, mode?: QueryMode) => () => { + const { + setActivePath, + updateQueryExecutionSettings, + showCreateDirectoryDialog, + getTableSchemaDataPromise, + } = additionalEffects; + + const inputQuery = (tmpl: TemplateFn, mode?: QueryMode) => () => { if (mode) { updateQueryExecutionSettings({queryMode: mode}); } - dispatch(changeUserInput({input: tmpl(schemaQueryParams)})); + const pathType = nodeTableTypeToPathType[params.type]; + const withTableData = [selectQueryTemplate, upsertQueryTemplate].includes(tmpl); + + const userInputDataPromise = + withTableData && pathType && getTableSchemaDataPromise + ? getTableSchemaDataPromise({ + path: params.path, + tenantName: params.tenantName, + type: pathType, + }) + : Promise.resolve(undefined); + + userInputDataPromise.then((tableData) => { + dispatch(changeUserInput({input: tmpl({...params, tableData})})); + }); + dispatch(setTenantPage(TENANT_PAGES_IDS.query)); dispatch(setQueryTab(TENANT_QUERY_TABS_ID.newQuery)); - setActivePath(schemaQueryParams.path); + setActivePath(params.path); }; return { createDirectory: showCreateDirectoryDialog ? () => { - showCreateDirectoryDialog(schemaQueryParams.path); + showCreateDirectoryDialog(params.path); } : undefined, createTable: inputQuery(createTableTemplate, 'script'), @@ -81,7 +114,7 @@ const bindActions = ( addTableIndex: inputQuery(addTableIndex, 'script'), copyPath: () => { try { - copy(schemaQueryParams.relativePath); + copy(params.relativePath); createToast({ name: 'Copied', title: i18n('actions.copied'), @@ -101,10 +134,14 @@ const bindActions = ( type ActionsSet = ReturnType['getActions']>; export const getActions = - (dispatch: React.Dispatch, additionalEffects: ActionsAdditionalEffects, rootPath = '') => + (dispatch: AppDispatch, additionalEffects: ActionsAdditionalEffects, rootPath = '') => (path: string, type: NavigationTreeNodeType) => { const relativePath = transformPath(path, rootPath); - const actions = bindActions({path, relativePath}, dispatch, additionalEffects); + const actions = bindActions( + {path, relativePath, tenantName: rootPath, type}, + dispatch, + additionalEffects, + ); const copyItem = {text: i18n('actions.copyPath'), action: actions.copyPath}; const DIR_SET: ActionsSet = [ diff --git a/src/containers/Tenant/utils/schemaQueryTemplates.ts b/src/containers/Tenant/utils/schemaQueryTemplates.ts index 0a319dd8b..d7c8a5508 100644 --- a/src/containers/Tenant/utils/schemaQueryTemplates.ts +++ b/src/containers/Tenant/utils/schemaQueryTemplates.ts @@ -1,8 +1,13 @@ +import type {SchemaData} from '../Schema/SchemaViewer/types'; + export interface SchemaQueryParams { path: string; relativePath: string; + tableData?: SchemaData[]; } +export type TemplateFn = (params?: SchemaQueryParams) => string; + export const createTableTemplate = (params?: SchemaQueryParams) => { return `-- docs: https://ydb.tech/en/docs/yql/reference/syntax/create_table CREATE TABLE \`${params?.relativePath || '$path'}/ydb_row_table\` ( @@ -67,13 +72,18 @@ export const alterTableTemplate = (params?: SchemaQueryParams) => { ADD COLUMN numeric_column Int32;`; }; export const selectQueryTemplate = (params?: SchemaQueryParams) => { - return `SELECT * + const columns = params?.tableData?.map((column) => '`' + column.name + '`').join(', ') || '*'; + + return `SELECT ${columns} FROM \`${params?.relativePath || '$path'}\` LIMIT 10;`; }; export const upsertQueryTemplate = (params?: SchemaQueryParams) => { + const columns = + params?.tableData?.map((column) => `\`${column.name}\``).join(', ') || `\`id\`, \`name\``; + return `UPSERT INTO \`${params?.relativePath || '$path'}\` - ( \`id\`, \`name\` ) + ( ${columns} ) VALUES ( );`; }; diff --git a/src/store/reducers/tableSchemaData.ts b/src/store/reducers/tableSchemaData.ts new file mode 100644 index 000000000..93485c34b --- /dev/null +++ b/src/store/reducers/tableSchemaData.ts @@ -0,0 +1,56 @@ +import { + prepareSchemaData, + prepareViewSchema, +} from '../../containers/Tenant/Schema/SchemaViewer/prepareData'; +import type {SchemaData} from '../../containers/Tenant/Schema/SchemaViewer/types'; +import {isViewType} from '../../containers/Tenant/utils/schema'; +import type {EPathType} from '../../types/api/schema'; +import {isQueryErrorResponse} from '../../utils/query'; + +import {api} from './api'; +import {overviewApi} from './overview/overview'; +import {viewSchemaApi} from './viewSchema/viewSchema'; + +export interface GetTableSchemaDataParams { + path: string; + tenantName: string; + type: EPathType; +} + +export const tableSchemaDataApi = api.injectEndpoints({ + endpoints: (build) => ({ + getTableSchemaData: build.mutation({ + queryFn: async ({path, tenantName, type}, {dispatch}) => { + try { + if (isViewType(type)) { + const response = await dispatch( + viewSchemaApi.endpoints.getViewSchema.initiate({ + database: tenantName, + path, + }), + ); + + if (isQueryErrorResponse(response)) { + return {error: response}; + } + + const result = prepareViewSchema(response.data); + return {data: result}; + } + + const schemaData = await dispatch( + overviewApi.endpoints.getOverview.initiate({ + path, + database: tenantName, + }), + ); + const result = prepareSchemaData(type, schemaData.data); + + return {data: result}; + } catch (error) { + return {error}; + } + }, + }), + }), +}); diff --git a/src/utils/index.ts b/src/utils/index.ts index 308dce79e..8957aa474 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -6,8 +6,8 @@ export function valueIsDefined(value: T | null | undefined): value is T { return value !== null && value !== undefined; } -export async function wait(time: number) { +export async function wait(time: number, value?: T): Promise { return new Promise((resolve) => { - setTimeout(resolve, time); + setTimeout(() => resolve(value), time); }); }