diff --git a/src/containers/Tenant/Diagnostics/Diagnostics.tsx b/src/containers/Tenant/Diagnostics/Diagnostics.tsx
index e3141e7c0a..26aaef7871 100644
--- a/src/containers/Tenant/Diagnostics/Diagnostics.tsx
+++ b/src/containers/Tenant/Diagnostics/Diagnostics.tsx
@@ -104,7 +104,14 @@ function Diagnostics(props: DiagnosticsProps) {
);
}
case TENANT_DIAGNOSTICS_TABS_IDS.schema: {
- return ;
+ return (
+
+ );
}
case TENANT_DIAGNOSTICS_TABS_IDS.topQueries: {
return ;
diff --git a/src/containers/Tenant/ObjectSummary/ObjectSummary.tsx b/src/containers/Tenant/ObjectSummary/ObjectSummary.tsx
index b824af56a4..91b02a4c66 100644
--- a/src/containers/Tenant/ObjectSummary/ObjectSummary.tsx
+++ b/src/containers/Tenant/ObjectSummary/ObjectSummary.tsx
@@ -45,7 +45,7 @@ import {
PaneVisibilityToggleButtons,
paneVisibilityToggleReducerCreator,
} from '../utils/paneVisibilityToggleHelpers';
-import {isIndexTableType, isTableType, isViewType} from '../utils/schema';
+import {isIndexTableType, isTableType} from '../utils/schema';
import './ObjectSummary.scss';
@@ -64,6 +64,7 @@ const getTenantCommonInfoState = () => {
interface ObjectSummaryProps {
type?: EPathType;
subType?: EPathSubType;
+ tenantName?: string;
onCollapseSummary: VoidFunction;
onExpandSummary: VoidFunction;
isCollapsed: boolean;
@@ -72,6 +73,7 @@ interface ObjectSummaryProps {
export function ObjectSummary({
type,
subType,
+ tenantName,
onCollapseSummary,
onExpandSummary,
isCollapsed,
@@ -97,15 +99,12 @@ export function ObjectSummary({
ignoreQueryPrefix: true,
});
- const {name: tenantName} = queryParams;
-
const pathData = tenantName ? data[tenantName.toString()]?.PathDescription?.Self : undefined;
const currentObjectData = currentSchemaPath ? data[currentSchemaPath] : undefined;
const currentSchemaData = currentObjectData?.PathDescription?.Self;
React.useEffect(() => {
- // TODO: enable schema tab for view when supported
- const isTable = isTableType(type) && !isViewType(type);
+ const isTable = isTableType(type);
if (type && !isTable && !TENANT_INFO_TABS.find((el) => el.id === summaryTab)) {
dispatch(setSummaryTab(TENANT_SUMMARY_TABS_IDS.overview));
@@ -113,8 +112,7 @@ export function ObjectSummary({
}, [dispatch, type, summaryTab]);
const renderTabs = () => {
- // TODO: enable schema tab for view when supported
- const isTable = isTableType(type) && !isViewType(type);
+ const isTable = isTableType(type);
const tabsItems = isTable ? [...TENANT_INFO_TABS, ...TENANT_SCHEMA_TAB] : TENANT_INFO_TABS;
return (
@@ -126,7 +124,7 @@ export function ObjectSummary({
wrapTo={({id}, node) => {
const path = createHref(routes.tenant, undefined, {
...queryParams,
- name: tenantName as string,
+ name: tenantName,
[TenantTabsGroups.summaryTab]: id,
});
return (
@@ -218,7 +216,9 @@ export function ObjectSummary({
return ;
}
case TENANT_SUMMARY_TABS_IDS.schema: {
- return ;
+ return (
+
+ );
}
default: {
return renderObjectOverview();
diff --git a/src/containers/Tenant/Schema/SchemaViewer/SchemaViewer.tsx b/src/containers/Tenant/Schema/SchemaViewer/SchemaViewer.tsx
index 39d1c64dc8..a3e30a2149 100644
--- a/src/containers/Tenant/Schema/SchemaViewer/SchemaViewer.tsx
+++ b/src/containers/Tenant/Schema/SchemaViewer/SchemaViewer.tsx
@@ -1,59 +1,94 @@
+import React from 'react';
+
import DataTable from '@gravity-ui/react-data-table';
+import {skipToken} from '@reduxjs/toolkit/query';
import {ResizeableDataTable} from '../../../../components/ResizeableDataTable/ResizeableDataTable';
import {TableSkeleton} from '../../../../components/TableSkeleton/TableSkeleton';
+import {viewSchemaApi} from '../../../../store/reducers/viewSchema/viewSchema';
import type {EPathType} from '../../../../types/api/schema';
-import {cn} from '../../../../utils/cn';
import {DEFAULT_TABLE_SETTINGS} from '../../../../utils/constants';
import {useTypedSelector} from '../../../../utils/hooks';
+import {
+ isColumnEntityType,
+ isExternalTableType,
+ isRowTableType,
+ isViewType,
+} from '../../utils/schema';
import {
SCHEMA_COLUMNS_WIDTH_LS_KEY,
- SchemaViewerColumns,
- prepareColumnDescriptions,
- prepareFamilies,
- prepareSchemaTableColumns,
-} from './helpers';
+ SCHEMA_TABLE_COLUMS_IDS,
+ getColumnTableColumns,
+ getExternalTableColumns,
+ getRowTableColumns,
+ getViewColumns,
+} from './columns';
+import {prepareSchemaData, prepareViewSchema} from './prepareData';
+import {b} from './shared';
import './SchemaViewer.scss';
-const b = cn('schema-viewer');
-
interface SchemaViewerProps {
type?: EPathType;
path?: string;
- withFamilies?: boolean;
+ tenantName?: string | null;
+ extended?: boolean;
}
-export const SchemaViewer = ({type, path, withFamilies = false}: SchemaViewerProps) => {
- const {data, loading} = useTypedSelector((state) => state.schema);
- const currentObjectData = path ? data[path] : undefined;
-
- const {columns, keyColumnIds} = prepareColumnDescriptions(type, currentObjectData);
- const families = prepareFamilies(currentObjectData);
-
- return (
-
- {loading ? (
-
- ) : (
-
- )}
-
- );
+export const SchemaViewer = ({type, path, tenantName, extended = false}: SchemaViewerProps) => {
+ const {data: schemaData, loading} = useTypedSelector((state) => state.schema);
+ const currentObjectData = path ? schemaData[path] : undefined;
+
+ const viewSchemaRequestParams =
+ isViewType(type) && path && tenantName ? {path, database: tenantName} : skipToken;
+
+ const {data: viewColumnsData, isLoading: isViewSchemaLoading} =
+ viewSchemaApi.useGetViewSchemaQuery(viewSchemaRequestParams);
+
+ const tableData = React.useMemo(() => {
+ if (isViewType(type)) {
+ return prepareViewSchema(viewColumnsData);
+ }
+
+ return prepareSchemaData(type, currentObjectData);
+ }, [currentObjectData, type, viewColumnsData]);
+
+ const columns = React.useMemo(() => {
+ if (isViewType(type)) {
+ return getViewColumns();
+ }
+ if (isExternalTableType(type)) {
+ return getExternalTableColumns();
+ }
+ if (isColumnEntityType(type)) {
+ return getColumnTableColumns();
+ }
+ if (isRowTableType(type)) {
+ return getRowTableColumns(extended);
+ }
+
+ return [];
+ }, [type, extended]);
+
+ const renderContent = () => {
+ if (loading || isViewSchemaLoading) {
+ return ;
+ }
+
+ return (
+
+ );
+ };
+
+ return {renderContent()}
;
};
diff --git a/src/containers/Tenant/Schema/SchemaViewer/columns.tsx b/src/containers/Tenant/Schema/SchemaViewer/columns.tsx
new file mode 100644
index 0000000000..b967a6c9ce
--- /dev/null
+++ b/src/containers/Tenant/Schema/SchemaViewer/columns.tsx
@@ -0,0 +1,123 @@
+import DataTable from '@gravity-ui/react-data-table';
+import {Icon} from '@gravity-ui/uikit';
+
+import i18n from './i18n';
+import {b} from './shared';
+import type {SchemaColumn, SchemaData} from './types';
+
+import keyIcon from '../../../../assets/icons/key.svg';
+
+export const SCHEMA_COLUMNS_WIDTH_LS_KEY = 'schemaTableColumnsWidth';
+
+export const SCHEMA_TABLE_COLUMS_IDS = {
+ id: 'id',
+ name: 'name',
+ isKeyColumn: 'isKeyColumn',
+ type: 'type',
+ notNull: 'notNull',
+ familyName: 'familyName',
+ prefferedPoolKind: 'prefferedPoolKind',
+ columnCodec: 'columnCodec',
+} satisfies Record;
+
+const idColumn: SchemaColumn = {
+ name: SCHEMA_TABLE_COLUMS_IDS.id,
+ get header() {
+ return i18n('column-title.id');
+ },
+ width: 60,
+ render: ({row}) => row.id,
+};
+const nameColumn: SchemaColumn = {
+ name: SCHEMA_TABLE_COLUMS_IDS.name,
+ get header() {
+ return i18n('column-title.name');
+ },
+ width: 100,
+ render: ({row}) => row.name,
+};
+const keyColumn: SchemaColumn = {
+ name: SCHEMA_TABLE_COLUMS_IDS.isKeyColumn,
+ get header() {
+ return i18n('column-title.key');
+ },
+ width: 70,
+ resizeMinWidth: 70,
+ // Table should start with key columns on sort click
+ defaultOrder: DataTable.ASCENDING,
+ sortAccessor: (row) => row.keyAccessor,
+ render: ({row}) => {
+ return row.isKeyColumn ? (
+
+
+
+ ) : null;
+ },
+};
+const typeColumn: SchemaColumn = {
+ name: SCHEMA_TABLE_COLUMS_IDS.type,
+ get header() {
+ return i18n('column-title.type');
+ },
+ width: 100,
+ render: ({row}) => row.type,
+};
+const notNullColumn: SchemaColumn = {
+ name: SCHEMA_TABLE_COLUMS_IDS.notNull,
+ get header() {
+ return i18n('column-title.notNull');
+ },
+ width: 100,
+ // Table should start with notNull columns on sort click
+ defaultOrder: DataTable.DESCENDING,
+ render: ({row}) => {
+ if (row.notNull) {
+ return '\u2713';
+ }
+
+ return undefined;
+ },
+};
+const familyColumn: SchemaColumn = {
+ name: SCHEMA_TABLE_COLUMS_IDS.familyName,
+ get header() {
+ return i18n('column-title.family');
+ },
+ width: 100,
+ render: ({row}) => row.familyName,
+};
+const mediaColumn: SchemaColumn = {
+ name: SCHEMA_TABLE_COLUMS_IDS.prefferedPoolKind,
+ get header() {
+ return i18n('column-title.media');
+ },
+ width: 100,
+ render: ({row}) => row.prefferedPoolKind,
+};
+const compressionColumn: SchemaColumn = {
+ name: SCHEMA_TABLE_COLUMS_IDS.columnCodec,
+ get header() {
+ return i18n('column-title.compression');
+ },
+ width: 100,
+ render: ({row}) => row.columnCodec,
+};
+
+export function getViewColumns(): SchemaColumn[] {
+ return [nameColumn, typeColumn];
+}
+export function getExternalTableColumns(): SchemaColumn[] {
+ return [idColumn, nameColumn, typeColumn, notNullColumn];
+}
+export function getColumnTableColumns(): SchemaColumn[] {
+ return [idColumn, keyColumn, nameColumn, typeColumn, notNullColumn];
+}
+export function getRowTableColumns(extended: boolean): SchemaColumn[] {
+ const rowTableColumns = [idColumn, keyColumn, nameColumn, typeColumn, notNullColumn];
+
+ if (extended) {
+ return rowTableColumns.concat(familyColumn, mediaColumn, compressionColumn);
+ }
+
+ return rowTableColumns;
+}
diff --git a/src/containers/Tenant/Schema/SchemaViewer/helpers.tsx b/src/containers/Tenant/Schema/SchemaViewer/helpers.tsx
deleted file mode 100644
index d94c47bbd6..0000000000
--- a/src/containers/Tenant/Schema/SchemaViewer/helpers.tsx
+++ /dev/null
@@ -1,208 +0,0 @@
-import type {ClassNameFormatter} from '@bem-react/classname';
-import DataTable from '@gravity-ui/react-data-table';
-import type {Column} from '@gravity-ui/react-data-table';
-import {Icon} from '@gravity-ui/uikit';
-
-import type {
- EPathType,
- TColumnDescription,
- TColumnTableDescription,
- TEvDescribeSchemeResult,
- TFamilyDescription,
-} from '../../../../types/api/schema';
-import {EColumnCodec} from '../../../../types/api/schema';
-import {
- isColumnEntityType,
- isExternalTableType,
- isRowTableType,
- isTableType,
-} from '../../utils/schema';
-
-import keyIcon from '../../../../assets/icons/key.svg';
-
-export const SchemaViewerColumns = {
- id: 'Id',
- name: 'Name',
- key: 'Key',
- type: 'Type',
- notNull: 'NotNull',
- familyName: 'Family',
- preferredPoolKind: 'Media',
- columnCodec: 'Compression',
-};
-
-function prepareOlapTableSchema(tableSchema: TColumnTableDescription = {}) {
- const {Name, Schema} = tableSchema;
-
- if (Schema) {
- const {Columns, KeyColumnNames} = Schema;
- const KeyColumnIds = KeyColumnNames?.map((name: string) => {
- const column = Columns?.find((el) => el.Name === name);
- return column?.Id;
- }).filter((id): id is number => id !== undefined);
-
- return {
- Columns,
- KeyColumnNames,
- Name,
- KeyColumnIds,
- };
- }
-
- return {
- Name,
- };
-}
-
-function formatColumnCodec(codec?: EColumnCodec) {
- if (!codec) {
- return null;
- }
- if (codec === EColumnCodec.ColumnCodecPlain) {
- return 'None';
- }
- return codec.replace('ColumnCodec', '').toLocaleLowerCase();
-}
-
-export function prepareColumnDescriptions(
- type?: EPathType,
- scheme?: TEvDescribeSchemeResult,
-): {columns: TColumnDescription[]; keyColumnIds: number[]} {
- let keyColumnIds: number[] = [];
- let columns: TColumnDescription[] = [];
-
- if (isTableType(type) && isColumnEntityType(type)) {
- const description = scheme?.PathDescription?.ColumnTableDescription;
- const columnTableSchema = prepareOlapTableSchema(description);
- keyColumnIds = columnTableSchema.KeyColumnIds ?? [];
- columns = columnTableSchema.Columns ?? [];
- } else if (isExternalTableType(type)) {
- columns = scheme?.PathDescription?.ExternalTableDescription?.Columns ?? [];
- } else {
- keyColumnIds = scheme?.PathDescription?.Table?.KeyColumnIds ?? [];
- columns = scheme?.PathDescription?.Table?.Columns ?? [];
- }
-
- return {columns, keyColumnIds};
-}
-
-export function prepareFamilies(
- scheme?: TEvDescribeSchemeResult,
-): Record {
- return (
- scheme?.PathDescription?.Table?.PartitionConfig?.ColumnFamilies?.reduce<
- Record
- >((acc, family) => {
- if (family.Id) {
- acc[family.Id] = family;
- }
- return acc;
- }, {}) ?? {}
- );
-}
-
-export const SCHEMA_COLUMNS_WIDTH_LS_KEY = 'schemaTableColumnsWidth';
-
-export function prepareSchemaTableColumns(options: {
- type?: EPathType;
- b: ClassNameFormatter;
- families: Record;
- keyColumnIds: number[];
- withFamilies: boolean;
-}): Column[] {
- const keyColumnsOrderValues = options.keyColumnIds.reduce>(
- (result, keyColumnId, index) => {
- // Put columns with negative values, so they will be the first with ascending sort
- // Minus keyColumnIds.length for the first key, -1 for the last
- result[keyColumnId] = index - options.keyColumnIds.length;
- return result;
- },
- {},
- );
-
- const columns: Column[] = [
- {
- name: SchemaViewerColumns.id,
- width: 60,
- },
- ];
-
- if (!isExternalTableType(options.type)) {
- // External tables don't have key columns
- columns.push({
- name: SchemaViewerColumns.key,
- width: 70,
- resizeMinWidth: 70,
- // Table should start with key columns on sort click
- defaultOrder: DataTable.ASCENDING,
- // Values in keyColumnsOrderValues are always negative, so it will be 1 for not key columns
- sortAccessor: (row) => (row.Id && keyColumnsOrderValues[row.Id]) || 1,
- render: ({row}) => {
- return row.Id && options.keyColumnIds.includes(row.Id) ? (
-
-
-
- ) : null;
- },
- });
- }
-
- columns.push(
- {
- name: SchemaViewerColumns.name,
- width: 100,
- },
- {
- name: SchemaViewerColumns.type,
- width: 100,
- },
- {
- name: SchemaViewerColumns.notNull,
- width: 100,
- // Table should start with notNull columns on sort click
- defaultOrder: DataTable.DESCENDING,
- render: ({row}) => {
- if (row.NotNull) {
- return '\u2713';
- }
-
- return undefined;
- },
- },
- );
-
- if (options.withFamilies && isRowTableType(options.type)) {
- columns.push(
- {
- name: SchemaViewerColumns.familyName,
- width: 100,
- render: ({row}) => (row.Family ? options.families[row.Family].Name : undefined),
- sortAccessor: (row) => (row.Family ? options.families[row.Family].Name : undefined),
- },
- {
- name: SchemaViewerColumns.preferredPoolKind,
- width: 100,
- render: ({row}) =>
- row.Family
- ? options.families[row.Family].StorageConfig?.Data?.PreferredPoolKind
- : undefined,
- sortAccessor: (row) =>
- row.Family
- ? options.families[row.Family].StorageConfig?.Data?.PreferredPoolKind
- : undefined,
- },
- {
- name: SchemaViewerColumns.columnCodec,
- width: 100,
- render: ({row}) =>
- row.Family
- ? formatColumnCodec(options.families[row.Family].ColumnCodec)
- : undefined,
- sortAccessor: (row) =>
- row.Family ? options.families[row.Family].ColumnCodec : undefined,
- },
- );
- }
-
- return columns;
-}
diff --git a/src/containers/Tenant/Schema/SchemaViewer/i18n/en.json b/src/containers/Tenant/Schema/SchemaViewer/i18n/en.json
new file mode 100644
index 0000000000..3ff93c5b91
--- /dev/null
+++ b/src/containers/Tenant/Schema/SchemaViewer/i18n/en.json
@@ -0,0 +1,10 @@
+{
+ "column-title.id": "Id",
+ "column-title.name": "Name",
+ "column-title.key": "Key",
+ "column-title.type": "Type",
+ "column-title.notNull": "NotNull",
+ "column-title.family": "Family",
+ "column-title.media": "Media",
+ "column-title.compression": "Compression"
+}
diff --git a/src/containers/Tenant/Schema/SchemaViewer/i18n/index.ts b/src/containers/Tenant/Schema/SchemaViewer/i18n/index.ts
new file mode 100644
index 0000000000..6f1751a296
--- /dev/null
+++ b/src/containers/Tenant/Schema/SchemaViewer/i18n/index.ts
@@ -0,0 +1,7 @@
+import {registerKeysets} from '../../../../../utils/i18n';
+
+import en from './en.json';
+
+const COMPONENT = 'ydb-schema-viewer';
+
+export default registerKeysets(COMPONENT, {en});
diff --git a/src/containers/Tenant/Schema/SchemaViewer/prepareData.ts b/src/containers/Tenant/Schema/SchemaViewer/prepareData.ts
new file mode 100644
index 0000000000..fc55a00052
--- /dev/null
+++ b/src/containers/Tenant/Schema/SchemaViewer/prepareData.ts
@@ -0,0 +1,165 @@
+import type {ColumnType} from '../../../../types/api/query';
+import type {
+ EPathType,
+ TColumnTableDescription,
+ TEvDescribeSchemeResult,
+ TExternalTableDescription,
+ TFamilyDescription,
+ TTableDescription,
+} from '../../../../types/api/schema';
+import {EColumnCodec} from '../../../../types/api/schema';
+import {isColumnEntityType, isExternalTableType, isRowTableType} from '../../utils/schema';
+
+import type {SchemaData} from './types';
+
+function getKeyColumnsSortAccessorMap(ids: T[] = []): Record {
+ return ids.reduce(
+ (result, keyColumnId, index) => {
+ // Put columns with negative values, so they will be the first with ascending sort
+ // Minus keyColumnIds.length for the first key, -1 for the last
+ return {
+ ...result,
+ [keyColumnId]: index - ids.length,
+ };
+ },
+ {} as Record,
+ );
+}
+
+function formatColumnCodec(codec?: EColumnCodec) {
+ if (!codec) {
+ return undefined;
+ }
+ if (codec === EColumnCodec.ColumnCodecPlain) {
+ return 'None';
+ }
+ return codec.replace('ColumnCodec', '').toLocaleLowerCase();
+}
+
+export function prepareFamilies(data?: TTableDescription): Record {
+ return (
+ data?.PartitionConfig?.ColumnFamilies?.reduce>(
+ (acc, family) => {
+ if (family.Id) {
+ return {
+ ...acc,
+ [family.Id]: family,
+ };
+ }
+ return acc;
+ },
+ {},
+ ) ?? {}
+ );
+}
+
+function prepareRowTableSchema(data: TTableDescription = {}): SchemaData[] {
+ const families = prepareFamilies(data);
+
+ const {Columns, KeyColumnIds} = data;
+
+ const keyAccessorsMap = getKeyColumnsSortAccessorMap(KeyColumnIds);
+
+ const preparedColumns = Columns?.map((column) => {
+ const {Id, Name, NotNull, Type, Family} = column;
+
+ const isKeyColumn = Boolean(KeyColumnIds?.find((keyColumnId) => keyColumnId === Id));
+ // Values in keyAccessorsMap are always negative, so it will be 1 for not key columns
+ const keyAccessor = Id && keyAccessorsMap[Id] ? keyAccessorsMap[Id] : 1;
+
+ const familyName = Family ? families[Family].Name : undefined;
+ const prefferedPoolKind = Family
+ ? families[Family].StorageConfig?.Data?.PreferredPoolKind
+ : undefined;
+ const columnCodec = Family ? formatColumnCodec(families[Family].ColumnCodec) : undefined;
+
+ return {
+ id: Id,
+ name: Name,
+ isKeyColumn,
+ keyAccessor,
+ type: Type,
+ notNull: NotNull,
+ familyName,
+ prefferedPoolKind,
+ columnCodec,
+ };
+ });
+
+ return preparedColumns || [];
+}
+
+function prepareExternalTableSchema(data: TExternalTableDescription = {}): SchemaData[] {
+ const {Columns} = data;
+ const preparedColumns = Columns?.map((column) => {
+ const {Id, Name, Type, NotNull} = column;
+ return {
+ id: Id,
+ name: Name,
+ type: Type,
+ notNull: NotNull,
+ };
+ });
+
+ return preparedColumns || [];
+}
+
+function prepareColumnTableSchema(data: TColumnTableDescription = {}): SchemaData[] {
+ const {Schema = {}} = data;
+ const {Columns, KeyColumnNames} = Schema;
+
+ const keyAccessorsMap = getKeyColumnsSortAccessorMap(KeyColumnNames);
+
+ const preparedColumns = Columns?.map((column) => {
+ const {Id, Name, Type, NotNull} = column;
+
+ const isKeyColumn = Boolean(
+ KeyColumnNames?.find((keyColumnName) => keyColumnName === Name),
+ );
+
+ // Values in keyAccessorsMap are always negative, so it will be 1 for not key columns
+ const keyAccessor = Name && keyAccessorsMap[Name] ? keyAccessorsMap[Name] : 1;
+
+ return {
+ id: Id,
+ name: Name,
+ isKeyColumn,
+ keyAccessor,
+ type: Type,
+ notNull: NotNull,
+ };
+ });
+
+ return preparedColumns || [];
+}
+
+export function prepareSchemaData(
+ type?: EPathType,
+ schema?: TEvDescribeSchemeResult,
+): SchemaData[] {
+ const {Table, ColumnTableDescription, ExternalTableDescription} = schema?.PathDescription || {};
+
+ if (isRowTableType(type)) {
+ return prepareRowTableSchema(Table);
+ } else if (isColumnEntityType(type)) {
+ return prepareColumnTableSchema(ColumnTableDescription);
+ } else if (isExternalTableType(type)) {
+ return prepareExternalTableSchema(ExternalTableDescription);
+ }
+
+ return [];
+}
+
+export function prepareViewSchema(columns?: ColumnType[]): SchemaData[] {
+ const preparedColumns = columns?.map((column) => {
+ // View may have columns like Uint64? or Utf8?
+ const type = column.type?.endsWith('?') ? column.type.slice(0, -1) : column.type;
+
+ return {
+ type,
+ name: column.name,
+ };
+ });
+
+ return preparedColumns || [];
+}
diff --git a/src/containers/Tenant/Schema/SchemaViewer/shared.ts b/src/containers/Tenant/Schema/SchemaViewer/shared.ts
new file mode 100644
index 0000000000..35a8bb45a4
--- /dev/null
+++ b/src/containers/Tenant/Schema/SchemaViewer/shared.ts
@@ -0,0 +1,3 @@
+import {cn} from '../../../../utils/cn';
+
+export const b = cn('schema-viewer');
diff --git a/src/containers/Tenant/Schema/SchemaViewer/types.ts b/src/containers/Tenant/Schema/SchemaViewer/types.ts
new file mode 100644
index 0000000000..2c820ad464
--- /dev/null
+++ b/src/containers/Tenant/Schema/SchemaViewer/types.ts
@@ -0,0 +1,18 @@
+import type {Column} from '@gravity-ui/react-data-table';
+
+export interface SchemaData {
+ id?: number;
+ name?: string;
+ isKeyColumn?: boolean;
+ /** value to sort keys, needed to ensure that key columns will be the first */
+ keyAccessor?: number;
+ type?: string;
+ notNull?: boolean;
+ familyName?: string;
+ prefferedPoolKind?: string;
+ columnCodec?: string;
+}
+
+export interface SchemaColumn extends Column {
+ name: keyof SchemaData;
+}
diff --git a/src/containers/Tenant/Tenant.tsx b/src/containers/Tenant/Tenant.tsx
index e14109403c..cf062c6ede 100644
--- a/src/containers/Tenant/Tenant.tsx
+++ b/src/containers/Tenant/Tenant.tsx
@@ -140,6 +140,7 @@ function Tenant(props: TenantProps) {
({
+ getViewSchema: build.query({
+ queryFn: async ({database, path}: {database: string; path: string}) => {
+ try {
+ const response = await window.api.sendQuery({
+ schema: 'modern',
+ query: createViewSchemaQuery(path),
+ database,
+ action: 'execute-scan',
+ });
+
+ if (isQueryErrorResponse(response)) {
+ return {error: response};
+ }
+
+ return {data: response.columns || []};
+ } catch (error) {
+ return {error: error};
+ }
+ },
+ providesTags: ['All'],
+ }),
+ }),
+ overrideExisting: 'throw',
+});
diff --git a/src/types/api/schema/columnEntity.ts b/src/types/api/schema/columnEntity.ts
index 070cd8f897..32459fe69b 100644
--- a/src/types/api/schema/columnEntity.ts
+++ b/src/types/api/schema/columnEntity.ts
@@ -95,6 +95,7 @@ interface TOlapColumnDescription {
Id?: number;
Name?: string;
Type?: string;
+ NotNull?: boolean;
TypeId?: number;
TypeInfo?: TTypeInfo;
}