From 72f1a6700184f5ae04086841418cac54b2d31789 Mon Sep 17 00:00:00 2001 From: Vladimir Stepanenko Date: Wed, 29 Oct 2025 15:13:16 +0300 Subject: [PATCH 01/21] Add editor charts api --- src/shared/components/public-api/utils/index.ts | 11 +++++++++++ src/shared/schema/us/types/fields.ts | 1 + 2 files changed, 12 insertions(+) create mode 100644 src/shared/components/public-api/utils/index.ts diff --git a/src/shared/components/public-api/utils/index.ts b/src/shared/components/public-api/utils/index.ts new file mode 100644 index 0000000000..b4b944fbce --- /dev/null +++ b/src/shared/components/public-api/utils/index.ts @@ -0,0 +1,11 @@ +const registeredComponents = new Set(); + +export const registerComponentId = (id: string): string => { + if (registeredComponents.has(id)) { + throw new Error(`OpenAPI component ${id} is already registered`); + } + + registeredComponents.add(id); + + return id; +}; diff --git a/src/shared/schema/us/types/fields.ts b/src/shared/schema/us/types/fields.ts index be2895ca45..96e2316536 100644 --- a/src/shared/schema/us/types/fields.ts +++ b/src/shared/schema/us/types/fields.ts @@ -7,6 +7,7 @@ export type EntryFieldPublishedId = null | string; // corresponds to RETURN_COLUMNS from US export interface EntryFields { + version?: number | null; displayAlias?: string | null; createdAt: string; createdBy: string; From 4db2cce802554fc10c300de5313d9c20562b7b75 Mon Sep 17 00:00:00 2001 From: Vladimir Stepanenko Date: Fri, 31 Oct 2025 15:25:57 +0300 Subject: [PATCH 02/21] Editor actions --- src/shared/schema/gateway-utils.ts | 35 +++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/shared/schema/gateway-utils.ts b/src/shared/schema/gateway-utils.ts index d6ae312926..8f7765d1bd 100644 --- a/src/shared/schema/gateway-utils.ts +++ b/src/shared/schema/gateway-utils.ts @@ -23,7 +23,7 @@ const VALIDATION_SCHEMA_KEY = Symbol('$schema'); const registerValidationSchema = (value: T, schema: TypedActionSchema): T => { Object.defineProperty(value, VALIDATION_SCHEMA_KEY, { value: schema, - enumerable: false, + configurable: true, }); return value; @@ -62,6 +62,39 @@ export const createTypedAction = ( return registerValidationSchema(actionConfig, schemaValidationObject); }; +export const createExtendedAction = + ( + actionConfig: ApiServiceActionConfig< + AppContext, + Request, + Response, + TConfigOutput, + TConfigParams, + TConfigTransformed + >, + ) => + (schema: { + resultSchema: z.ZodType; + paramsSchema: z.ZodType; + }) => { + const schemaValidationObject = { + paramsSchema: schema.paramsSchema, + resultSchema: schema.resultSchema, + }; + + return registerValidationSchema( + actionConfig as unknown as ApiServiceActionConfig< + AppContext, + Request, + Response, + TConfigOutput, + TParams, + TResult + >, + schemaValidationObject, + ); + }; + type AuthArgsData = { userAccessToken?: string; serviceUserAccessToken?: string; From 138084f2044882f3de574ec11fd70b4a91e7b1e8 Mon Sep 17 00:00:00 2001 From: Vladimir Stepanenko Date: Fri, 31 Oct 2025 16:20:50 +0300 Subject: [PATCH 03/21] Editor actions --- src/shared/schema/gateway-utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/schema/gateway-utils.ts b/src/shared/schema/gateway-utils.ts index 8f7765d1bd..4cbc8331f2 100644 --- a/src/shared/schema/gateway-utils.ts +++ b/src/shared/schema/gateway-utils.ts @@ -62,7 +62,7 @@ export const createTypedAction = ( return registerValidationSchema(actionConfig, schemaValidationObject); }; -export const createExtendedAction = +export const createExtendedTypedAction = ( actionConfig: ApiServiceActionConfig< AppContext, From 23254226549faac1300c33b2224f1d2469dd4623 Mon Sep 17 00:00:00 2001 From: Vladimir Stepanenko Date: Fri, 31 Oct 2025 18:12:00 +0300 Subject: [PATCH 04/21] Editor actions --- .../components/public-api/utils/init-public-api-swagger.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/server/components/public-api/utils/init-public-api-swagger.ts b/src/server/components/public-api/utils/init-public-api-swagger.ts index acc8160618..3a8248c61b 100644 --- a/src/server/components/public-api/utils/init-public-api-swagger.ts +++ b/src/server/components/public-api/utils/init-public-api-swagger.ts @@ -93,6 +93,8 @@ export const initPublicApiSwagger = (app: ExpressKit) => { const swaggerOptions = { url: jsonPath, validatorUrl: null, + tagsSorter: 'alpha', + operationsSorter: 'alpha', }; app.express.use( From ec559a2cea171968e15bd640d066bd587f60dabb Mon Sep 17 00:00:00 2001 From: Vladimir Stepanenko Date: Sat, 1 Nov 2025 12:52:22 +0300 Subject: [PATCH 05/21] Editor actions --- src/shared/schema/mix/schemas/ql.ts | 2 +- src/shared/schema/mix/schemas/wizard.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shared/schema/mix/schemas/ql.ts b/src/shared/schema/mix/schemas/ql.ts index 2b548def92..3a05673d54 100644 --- a/src/shared/schema/mix/schemas/ql.ts +++ b/src/shared/schema/mix/schemas/ql.ts @@ -8,7 +8,7 @@ export const deleteQLChartResultSchema = z.object({}); export const getQLChartArgsSchema = z.strictObject({ chartId: z.string(), - workbookId: z.union([z.string(), z.null()]).optional(), + workbookId: z.string().nullable().optional(), revId: z.string().optional(), includePermissions: z.boolean().optional(), includeLinks: z.boolean().optional(), diff --git a/src/shared/schema/mix/schemas/wizard.ts b/src/shared/schema/mix/schemas/wizard.ts index 8f6bbc0458..96d45cb66a 100644 --- a/src/shared/schema/mix/schemas/wizard.ts +++ b/src/shared/schema/mix/schemas/wizard.ts @@ -8,7 +8,7 @@ export const deleteWizardChartResultSchema = z.object({}); export const getWizardChartArgsSchema = z.strictObject({ chartId: z.string(), - workbookId: z.union([z.string(), z.null()]).optional(), + workbookId: z.string().nullable().optional(), revId: z.string().optional(), includePermissions: z.boolean().optional(), includeLinks: z.boolean().optional(), From 8bfdd86cab838b34b86b5210991064bde5d98bbe Mon Sep 17 00:00:00 2001 From: Vladimir Stepanenko Date: Tue, 4 Nov 2025 14:28:34 +0300 Subject: [PATCH 06/21] Fix --- src/shared/schema/us/schemas/entries/list-directory.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/shared/schema/us/schemas/entries/list-directory.ts b/src/shared/schema/us/schemas/entries/list-directory.ts index aa4da12c68..e90c856be8 100644 --- a/src/shared/schema/us/schemas/entries/list-directory.ts +++ b/src/shared/schema/us/schemas/entries/list-directory.ts @@ -19,8 +19,7 @@ export const listDirectoryArgsSchema = z.object({ page: z.number().optional(), pageSize: z.number().optional(), includePermissionsInfo: z.boolean().optional(), - // Broken in US controller - // ignoreWorkbookEntries: z.boolean().optional(), + ignoreWorkbookEntries: z.boolean().optional(), }); export const listDirectoryBreadCrumbSchema = z.object({ From 7e7fdf1fa0469bd4400dbf2a9de45d1e622af45d Mon Sep 17 00:00:00 2001 From: Vladimir Stepanenko Date: Wed, 5 Nov 2025 14:22:45 +0300 Subject: [PATCH 07/21] Fix --- src/shared/utils/array/assert-non-empty-array.ts | 5 +++++ src/shared/utils/array/index.ts | 1 + 2 files changed, 6 insertions(+) create mode 100644 src/shared/utils/array/assert-non-empty-array.ts create mode 100644 src/shared/utils/array/index.ts diff --git a/src/shared/utils/array/assert-non-empty-array.ts b/src/shared/utils/array/assert-non-empty-array.ts new file mode 100644 index 0000000000..315304b9ca --- /dev/null +++ b/src/shared/utils/array/assert-non-empty-array.ts @@ -0,0 +1,5 @@ +export function assertNonEmptyArray(arr: T[]): asserts arr is [T, ...T[]] { + if (arr.length === 0) { + throw new Error('Array for discriminated union must not be empty'); + } +} diff --git a/src/shared/utils/array/index.ts b/src/shared/utils/array/index.ts new file mode 100644 index 0000000000..7c3135d54b --- /dev/null +++ b/src/shared/utils/array/index.ts @@ -0,0 +1 @@ +export {assertNonEmptyArray} from './assert-non-empty-array'; From c44486de0eafd5687ac9649e79bb9046344320c6 Mon Sep 17 00:00:00 2001 From: Vladimir Stepanenko Date: Wed, 5 Nov 2025 14:54:14 +0300 Subject: [PATCH 08/21] Fix --- src/shared/utils/array/assert-non-empty-array.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shared/utils/array/assert-non-empty-array.ts b/src/shared/utils/array/assert-non-empty-array.ts index 315304b9ca..f74d567762 100644 --- a/src/shared/utils/array/assert-non-empty-array.ts +++ b/src/shared/utils/array/assert-non-empty-array.ts @@ -1,5 +1,5 @@ -export function assertNonEmptyArray(arr: T[]): asserts arr is [T, ...T[]] { +export function assertNonEmptyArray(arr: T[], message?: string): asserts arr is [T, ...T[]] { if (arr.length === 0) { - throw new Error('Array for discriminated union must not be empty'); + throw new Error(message ?? 'Array must not be empty!'); } } From e1aaaef5ce53f7d4be6a2fa4b0cf2cf14afe0726 Mon Sep 17 00:00:00 2001 From: Vladimir Stepanenko Date: Wed, 5 Nov 2025 15:30:21 +0300 Subject: [PATCH 09/21] Fix --- src/shared/schema/us/schemas/entries/list-directory.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/shared/schema/us/schemas/entries/list-directory.ts b/src/shared/schema/us/schemas/entries/list-directory.ts index e90c856be8..7b9f656e9d 100644 --- a/src/shared/schema/us/schemas/entries/list-directory.ts +++ b/src/shared/schema/us/schemas/entries/list-directory.ts @@ -19,7 +19,6 @@ export const listDirectoryArgsSchema = z.object({ page: z.number().optional(), pageSize: z.number().optional(), includePermissionsInfo: z.boolean().optional(), - ignoreWorkbookEntries: z.boolean().optional(), }); export const listDirectoryBreadCrumbSchema = z.object({ From da877095c3aefc62abd3dd52920661ed2a91901f Mon Sep 17 00:00:00 2001 From: Vladimir Stepanenko Date: Fri, 7 Nov 2025 12:23:38 +0300 Subject: [PATCH 10/21] Dash public api actions --- src/server/components/sdk/dash.ts | 6 +- .../components/workbook-transfer/dash.ts | 8 +- src/server/controllers/workbook-transfer.ts | 4 +- src/shared/modules/dash-scheme-converter.ts | 135 ++++++++---------- .../mix/actions/dash/create-dashboard.ts | 16 +++ .../mix/actions/dash/delete-dashboard.ts | 22 +++ .../schema/mix/actions/dash/get-dashboard.ts | 78 ++++++++++ .../mix/actions/{dash.ts => dash/index.ts} | 133 ++++------------- .../mix/actions/dash/update-dashboard.ts | 20 +++ src/shared/schema/mix/actions/editor.ts | 4 +- src/shared/schema/mix/actions/entries.ts | 8 +- src/shared/schema/mix/schemas/dash.ts | 35 +++-- src/shared/schema/mix/types/dash.ts | 4 +- .../__tests__/editor.test.ts | 0 .../{helpers/dash.ts => utils/dash/index.ts} | 14 +- .../mix/utils/dash/migrate-dash-to-v1.ts | 77 ++++++++++ .../mix/{helpers => utils}/editor/index.ts | 0 .../{helpers => utils}/editor/validation.ts | 0 .../schema/mix/{helpers => utils}/entries.ts | 0 .../schema/mix/{helpers => utils}/index.ts | 0 .../mix/{helpers => utils}/validation.ts | 0 .../DialogChartWidget/DialogChartWidget.tsx | 2 +- .../DialogPublic/useDialogPublicState.ts | 2 +- .../units/dash/store/actions/base/actions.ts | 2 +- 24 files changed, 352 insertions(+), 218 deletions(-) create mode 100644 src/shared/schema/mix/actions/dash/create-dashboard.ts create mode 100644 src/shared/schema/mix/actions/dash/delete-dashboard.ts create mode 100644 src/shared/schema/mix/actions/dash/get-dashboard.ts rename src/shared/schema/mix/actions/{dash.ts => dash/index.ts} (69%) create mode 100644 src/shared/schema/mix/actions/dash/update-dashboard.ts rename src/shared/schema/mix/{helpers => utils}/__tests__/editor.test.ts (100%) rename src/shared/schema/mix/{helpers/dash.ts => utils/dash/index.ts} (89%) create mode 100644 src/shared/schema/mix/utils/dash/migrate-dash-to-v1.ts rename src/shared/schema/mix/{helpers => utils}/editor/index.ts (100%) rename src/shared/schema/mix/{helpers => utils}/editor/validation.ts (100%) rename src/shared/schema/mix/{helpers => utils}/entries.ts (100%) rename src/shared/schema/mix/{helpers => utils}/index.ts (100%) rename src/shared/schema/mix/{helpers => utils}/validation.ts (100%) diff --git a/src/server/components/sdk/dash.ts b/src/server/components/sdk/dash.ts index 3b56d02552..41603c09d8 100644 --- a/src/server/components/sdk/dash.ts +++ b/src/server/components/sdk/dash.ts @@ -248,7 +248,7 @@ class Dash { isEnabledServerFeature(Feature.DashServerMigrationEnable), ); if (isServerMigrationEnabled && DashSchemeConverter.isUpdateNeeded(usData.data)) { - usData.data = await DashSchemeConverter.update(usData.data); + usData.data = DashSchemeConverter.update(usData.data); } usData.links = gatherLinks(usData.data); @@ -302,7 +302,7 @@ class Dash { (options?.forceMigrate || isServerMigrationEnabled) && DashSchemeConverter.isUpdateNeeded(result.data) ) { - result.data = await Dash.migrate(result.data); + result.data = Dash.migrate(result.data); } ctx.log('SDK_DASH_READ_SUCCESS', US.getLoggedEntry(result)); @@ -315,7 +315,7 @@ class Dash { } } - static async migrate(data: DashEntry['data']) { + static migrate(data: DashEntry['data']) { return DashSchemeConverter.update(data); } diff --git a/src/server/components/workbook-transfer/dash.ts b/src/server/components/workbook-transfer/dash.ts index a338636d5e..31421ed8d6 100644 --- a/src/server/components/workbook-transfer/dash.ts +++ b/src/server/components/workbook-transfer/dash.ts @@ -18,11 +18,11 @@ import { warningTransferNotification, } from './create-transfer-notifications'; -export async function prepareDashImportData( +export function prepareDashImportData( entryData: {data: DashEntry['data']; name: string; annotation?: EntryAnnotation}, idMapping: TransferIdMapping, ) { - const data = await Dash.migrate(entryData.data); + const data = Dash.migrate(entryData.data); const notifications: TransferNotification[] = []; const description = entryData.annotation?.description ?? ''; const defaults = { @@ -86,8 +86,8 @@ export async function prepareDashImportData( }; } -export async function prepareDashExportData(entry: DashEntry, idMapping: TransferIdMapping) { - const data = await Dash.migrate(entry.data); +export function prepareDashExportData(entry: DashEntry, idMapping: TransferIdMapping) { + const data = Dash.migrate(entry.data); const notifications: TransferNotification[] = []; let isMissingMapping = false; diff --git a/src/server/controllers/workbook-transfer.ts b/src/server/controllers/workbook-transfer.ts index a5e0e43c3b..7dc7884cd1 100644 --- a/src/server/controllers/workbook-transfer.ts +++ b/src/server/controllers/workbook-transfer.ts @@ -126,7 +126,7 @@ export const prepareExportData = async (req: Request, res: Response) => { ]); } - const {dash, notifications} = await prepareDashExportData( + const {dash, notifications} = prepareDashExportData( Dash.migrateDescription(entry as unknown as DashEntry), idMapping, ); @@ -270,7 +270,7 @@ export const prepareImportData = async (req: Request, res: Response) => { return createImportResponseData(notifications, responseData.entryId); } case EntryScope.Dash: { - const {dash, notifications} = await prepareDashImportData(entryData.dash, idMapping); + const {dash, notifications} = prepareDashImportData(entryData.dash, idMapping); if (!dash) { return createImportResponseData(notifications); diff --git a/src/shared/modules/dash-scheme-converter.ts b/src/shared/modules/dash-scheme-converter.ts index 108e5bfba5..0d256085ce 100644 --- a/src/shared/modules/dash-scheme-converter.ts +++ b/src/shared/modules/dash-scheme-converter.ts @@ -73,7 +73,7 @@ class DashSchemeConverter { return data; } - static async upTo3(data: any) { + static upTo3(data: any) { const {salt, pages, counter, schemeVersion} = data; if (schemeVersion >= 3) { @@ -86,74 +86,63 @@ class DashSchemeConverter { const {id: pageId, tabs} = page; - const convertedTabs = await Promise.all( - tabs.map(async (tab: any) => { - const {id: tabId, items, title, layout, ignores = []} = tab; - return { - id: tabId, - items: await Promise.all( - items.map( - async ({ - id, - data: itemData, - tabs, - type, - defaults, - namespace = 'default', - }: any) => { - const data = itemData || tabs; - if (type === DashTabItemType.Control && !defaults) { - const defaultValue = data.control.defaultValue || ''; - - if (data.dataset) { - const {id: datasetId, fieldName} = data.dataset; - - try { - const {fields} = savedResponses[datasetId]; - // || await sdk.bi.getDataSetFieldsById({dataSetId: datasetId}); - savedResponses[datasetId] = {fields}; - - const field = fields.find( - ({title}: any) => title === fieldName, - ); - - if (field) { - data.dataset.fieldId = field.guid; - delete data.dataset.fieldName; - delete data.dataset.name; - defaults = {[field.guid]: defaultValue}; - } else { - defaults = {[fieldName]: defaultValue}; - } - } catch (error) { - console.error('DATASET_FIELDS', id, error); - defaults = {[fieldName]: defaultValue}; - } + const convertedTabs = tabs.map((tab: any) => { + const {id: tabId, items, title, layout, ignores = []} = tab; + return { + id: tabId, + items: items.map( + ({id, data: itemData, tabs, type, defaults, namespace = 'default'}: any) => { + const data = itemData || tabs; + if (type === DashTabItemType.Control && !defaults) { + const defaultValue = data.control.defaultValue || ''; + + if (data.dataset) { + const {id: datasetId, fieldName} = data.dataset; + + try { + const {fields} = savedResponses[datasetId]; + + savedResponses[datasetId] = {fields}; + + const field = fields.find( + ({title}: any) => title === fieldName, + ); + + if (field) { + data.dataset.fieldId = field.guid; + delete data.dataset.fieldName; + delete data.dataset.name; + defaults = {[field.guid]: defaultValue}; } else { - const connection = tab.connections.find( - ({fromId}: any) => fromId === id, - ); - - if (connection) { - data.control.fieldName = connection.param; - defaults = {[connection.param]: defaultValue}; - } else { - defaults = {}; - } + defaults = {[fieldName]: defaultValue}; } - } else if (!defaults) { + } catch (error) { + console.error('DATASET_FIELDS', id, error); + defaults = {[fieldName]: defaultValue}; + } + } else { + const connection = tab.connections.find( + ({fromId}: any) => fromId === id, + ); + + if (connection) { + data.control.fieldName = connection.param; + defaults = {[connection.param]: defaultValue}; + } else { defaults = {}; } - return {id, data, type, defaults, namespace}; - }, - ), - ), - title, - layout, - ignores, - }; - }), - ); + } + } else if (!defaults) { + defaults = {}; + } + return {id, data, type, defaults, namespace}; + }, + ), + title, + layout, + ignores, + }; + }); return { salt, @@ -164,8 +153,8 @@ class DashSchemeConverter { } // adding the aliases field for each tab - static async upTo4(originalData: any) { - const data = await DashSchemeConverter.upTo3(originalData); + static upTo4(originalData: any) { + const data = DashSchemeConverter.upTo3(originalData); const {salt, pages, schemeVersion, counter} = data; @@ -188,8 +177,8 @@ class DashSchemeConverter { } // ignors for the WIDGET-elements is translated into ignors for tabs WIDGET-elements - static async upTo6(originalData: any) { - const data = await DashSchemeConverter.upTo4(originalData); + static upTo6(originalData: any) { + const data = DashSchemeConverter.upTo4(originalData); const {salt, pages, counter, schemeVersion} = data; @@ -223,8 +212,8 @@ class DashSchemeConverter { }; } - static async upTo7(originalData: any): Promise { - const data = await DashSchemeConverter.upTo6(originalData); + static upTo7(originalData: any): DashData { + const data = DashSchemeConverter.upTo6(originalData); const {salt, pages, counter, schemeVersion, settings = {}} = data; @@ -345,8 +334,8 @@ class DashSchemeConverter { return result; } - static async upTo8(originalData: any): Promise { - const data = await DashSchemeConverter.upTo7(originalData); + static upTo8(originalData: any): DashData { + const data = DashSchemeConverter.upTo7(originalData); const {schemeVersion} = data; if (schemeVersion >= 8) { diff --git a/src/shared/schema/mix/actions/dash/create-dashboard.ts b/src/shared/schema/mix/actions/dash/create-dashboard.ts new file mode 100644 index 0000000000..52f293634c --- /dev/null +++ b/src/shared/schema/mix/actions/dash/create-dashboard.ts @@ -0,0 +1,16 @@ +import {Dash} from '../../../../../server/components/sdk'; +import {createTypedAction} from '../../../gateway-utils'; +import {createDashArgsSchema, createDashResultSchema} from '../../schemas/dash'; +import type {CreateDashResponse} from '../../types'; + +export const createDashboard = createTypedAction( + { + paramsSchema: createDashArgsSchema, + resultSchema: createDashResultSchema, + }, + async (_, args, {headers, ctx}) => { + const I18n = ctx.get('i18n'); + + return (await Dash.create(args, headers, ctx, I18n)) as unknown as CreateDashResponse; + }, +); diff --git a/src/shared/schema/mix/actions/dash/delete-dashboard.ts b/src/shared/schema/mix/actions/dash/delete-dashboard.ts new file mode 100644 index 0000000000..4310601286 --- /dev/null +++ b/src/shared/schema/mix/actions/dash/delete-dashboard.ts @@ -0,0 +1,22 @@ +import {EntryScope} from '../../../..'; +import {createTypedAction} from '../../../gateway-utils'; +import {getTypedApi} from '../../../simple-schema'; +import {deleteDashArgsSchema, deleteDashResultSchema} from '../../schemas/dash'; + +export const deleteDashboard = createTypedAction( + { + paramsSchema: deleteDashArgsSchema, + resultSchema: deleteDashResultSchema, + }, + async (api, {lockToken, dashboardId}) => { + const typedApi = getTypedApi(api); + + await typedApi.us._deleteUSEntry({ + entryId: dashboardId, + lockToken, + scope: EntryScope.Dash, + }); + + return {}; + }, +); diff --git a/src/shared/schema/mix/actions/dash/get-dashboard.ts b/src/shared/schema/mix/actions/dash/get-dashboard.ts new file mode 100644 index 0000000000..30dc11b081 --- /dev/null +++ b/src/shared/schema/mix/actions/dash/get-dashboard.ts @@ -0,0 +1,78 @@ +import z from 'zod'; + +import {EntryScope} from '../../../..'; +import {registerComponentId} from '../../../../components/public-api/utils'; +import {ServerError} from '../../../../constants/error'; +import {permissionsSchema} from '../../../../zod-schemas/permissions'; +import {createTypedAction} from '../../../gateway-utils'; +import {getTypedApi} from '../../../simple-schema'; +import {dashSchemaV1} from '../../schemas/dash'; +import {migrateDashToV1} from '../../utils/dash/migrate-dash-to-v1'; + +export const getDashArgsSchema = z + .object({ + dashboardId: z.string(), + revId: z.string().optional(), + includePermissions: z.boolean().optional().default(false), + includeLinks: z.boolean().optional().default(false), + includeFavorite: z.boolean().optional().default(false), + branch: z.enum(['published', 'saved']).optional(), + workbookId: z.string().optional(), + }) + .openapi(registerComponentId('GetDashboardArgs'), { + title: 'GetDashboardArgs', + }); + +export const getDashResultSchema = z + .object({ + entry: dashSchemaV1, + isFavorite: z.boolean().optional(), + permissions: permissionsSchema.optional(), + }) + .openapi(registerComponentId('GetDashboardResult'), { + title: 'GetDashboardResult', + }); + +export const getDashboard = createTypedAction( + { + paramsSchema: getDashArgsSchema, + resultSchema: getDashResultSchema, + }, + async (api, args) => { + const { + dashboardId, + includePermissions, + includeLinks, + includeFavorite, + branch, + revId, + workbookId, + } = args; + + const typedApi = getTypedApi(api); + + const getEntryResponse = await typedApi.us.getEntry({ + entryId: dashboardId, + includePermissionsInfo: includePermissions, + includeLinks, + includeFavorite, + revId, + workbookId, + branch: branch ?? 'published', + }); + + if (getEntryResponse.scope !== EntryScope.Dash) { + throw new ServerError('No entry found', { + status: 404, + }); + } + + const entry = migrateDashToV1(getEntryResponse); + + return { + entry, + isFavorite: getEntryResponse.isFavorite, + permissions: getEntryResponse.permissions, + }; + }, +); diff --git a/src/shared/schema/mix/actions/dash.ts b/src/shared/schema/mix/actions/dash/index.ts similarity index 69% rename from src/shared/schema/mix/actions/dash.ts rename to src/shared/schema/mix/actions/dash/index.ts index dcfe9bb40d..69125819b6 100644 --- a/src/shared/schema/mix/actions/dash.ts +++ b/src/shared/schema/mix/actions/dash/index.ts @@ -1,42 +1,41 @@ import type {DeepNonNullable} from 'utility-types'; -import {EntryScope} from '../../..'; -import Dash from '../../../../server/components/sdk/dash'; -import type {ChartsStats} from '../../../types/charts'; -import {createAction, createTypedAction} from '../../gateway-utils'; -import {getTypedApi} from '../../simple-schema'; -import {getEntryVisualizationType} from '../helpers'; -import type {DatasetDictResponse, DatasetFieldsDictResponse} from '../helpers/dash'; -import { - fetchDataset, - fetchDatasetFieldsById, - prepareDatasetData, - prepareWidgetDatasetData, -} from '../helpers/dash'; -import { - createDashArgsSchema, - createDashResultSchema, - deleteDashArgsSchema, - deleteDashResultSchema, - getDashArgsSchema, - getDashResultSchema, - updateDashArgsSchema, - updateDashResultSchema, -} from '../schemas/dash'; +import type {ChartsStats} from '../../../../types/charts'; +import {createAction} from '../../../gateway-utils'; +import {getTypedApi} from '../../../simple-schema'; import type { CollectChartkitStatsArgs, CollectChartkitStatsResponse, CollectDashStatsArgs, CollectDashStatsResponse, - CreateDashResponse, GetEntriesDatasetsFieldsArgs, GetEntriesDatasetsFieldsResponse, GetWidgetsDatasetsFieldsArgs, GetWidgetsDatasetsFieldsResponse, - UpdateDashResponse, -} from '../types'; +} from '../../types'; +import {getEntryVisualizationType} from '../../utils'; +import type {DatasetDictResponse, DatasetFieldsDictResponse} from '../../utils/dash'; +import { + fetchDataset, + fetchDatasetFieldsById, + prepareDatasetData, + prepareWidgetDatasetData, +} from '../../utils/dash'; + +import {createDashboard} from './create-dashboard'; +import {deleteDashboard} from './delete-dashboard'; +import {getDashboard} from './get-dashboard'; +import {updateDashboard} from './update-dashboard'; export const dashActions = { + // WIP + __getDashboard__: getDashboard, + // WIP + __updateDashboard__: updateDashboard, + // WIP + __createDashboard__: createDashboard, + _deleteDashboard: deleteDashboard, + collectDashStats: createAction( async (_, args, {ctx}) => { ctx.stats('dashStats', { @@ -203,86 +202,4 @@ export const dashActions = { }); return res; }), - - // WIP - __getDashboard__: createTypedAction( - { - paramsSchema: getDashArgsSchema, - resultSchema: getDashResultSchema, - }, - async (_, args, {headers, ctx}) => { - const {dashboardId, includePermissions, includeLinks, includeFavorite, branch, revId} = - args; - - const result = await Dash.read( - dashboardId, - { - includePermissionsInfo: includePermissions - ? includePermissions.toString() - : '0', - includeLinks: includeLinks ? includeLinks.toString() : '0', - includeFavorite, - ...(branch ? {branch} : {branch: 'published'}), - ...(revId ? {revId} : {}), - }, - headers, - ctx, - {forceMigrate: true}, - ); - - if (result.scope !== EntryScope.Dash) { - throw new Error('No entry found'); - } - - return result as any; - }, - ), - - // WIP - __updateDashboard__: createTypedAction( - { - paramsSchema: updateDashArgsSchema, - resultSchema: updateDashResultSchema, - }, - async (_, args, {headers, ctx}) => { - const {entryId} = args; - - const I18n = ctx.get('i18n'); - - return (await Dash.update(entryId, args, headers, ctx, I18n, { - forceMigrate: true, - })) as unknown as UpdateDashResponse; - }, - ), - - // WIP - __createDashboard__: createTypedAction( - { - paramsSchema: createDashArgsSchema, - resultSchema: createDashResultSchema, - }, - async (_, args, {headers, ctx}) => { - const I18n = ctx.get('i18n'); - - return (await Dash.create(args, headers, ctx, I18n)) as unknown as CreateDashResponse; - }, - ), - - _deleteDashboard: createTypedAction( - { - paramsSchema: deleteDashArgsSchema, - resultSchema: deleteDashResultSchema, - }, - async (api, {lockToken, dashboardId}) => { - const typedApi = getTypedApi(api); - - await typedApi.us._deleteUSEntry({ - entryId: dashboardId, - lockToken, - scope: EntryScope.Dash, - }); - - return {}; - }, - ), }; diff --git a/src/shared/schema/mix/actions/dash/update-dashboard.ts b/src/shared/schema/mix/actions/dash/update-dashboard.ts new file mode 100644 index 0000000000..9a7149f024 --- /dev/null +++ b/src/shared/schema/mix/actions/dash/update-dashboard.ts @@ -0,0 +1,20 @@ +import {Dash} from '../../../../../server/components/sdk'; +import {createTypedAction} from '../../../gateway-utils'; +import {updateDashArgsSchema, updateDashResultSchema} from '../../schemas/dash'; +import type {UpdateDashResponse} from '../../types'; + +export const updateDashboard = createTypedAction( + { + paramsSchema: updateDashArgsSchema, + resultSchema: updateDashResultSchema, + }, + async (_, args, {headers, ctx}) => { + const {entryId} = args; + + const I18n = ctx.get('i18n'); + + return (await Dash.update(entryId, args, headers, ctx, I18n, { + forceMigrate: true, + })) as unknown as UpdateDashResponse; + }, +); diff --git a/src/shared/schema/mix/actions/editor.ts b/src/shared/schema/mix/actions/editor.ts index ac72cfa3e5..ed535249a0 100644 --- a/src/shared/schema/mix/actions/editor.ts +++ b/src/shared/schema/mix/actions/editor.ts @@ -7,8 +7,8 @@ import type { UpdateEditorChartArgs, UpdateEditorChartResponse, } from '../../us/types'; -import {getEntryLinks} from '../helpers'; -import {validateData} from '../helpers/editor/validation'; +import {getEntryLinks} from '../utils'; +import {validateData} from '../utils/editor/validation'; export const editorActions = { createEditorChart: createAction( diff --git a/src/shared/schema/mix/actions/entries.ts b/src/shared/schema/mix/actions/entries.ts index 4e8e8ee65c..b803781962 100644 --- a/src/shared/schema/mix/actions/entries.ts +++ b/src/shared/schema/mix/actions/entries.ts @@ -9,12 +9,6 @@ import type { GetRelationsEntry, SwitchPublicationStatusResponse, } from '../../us/types'; -import { - checkEntriesForPublication, - escapeStringForLike, - getEntryMetaStatusByError, -} from '../helpers'; -import {isValidPublishLink} from '../helpers/validation'; import type { DeleteEntryArgs, DeleteEntryResponse, @@ -32,6 +26,8 @@ import type { ResolveEntryByLinkArgs, ResolveEntryByLinkResponse, } from '../types'; +import {checkEntriesForPublication, escapeStringForLike, getEntryMetaStatusByError} from '../utils'; +import {isValidPublishLink} from '../utils/validation'; export const entriesActions = { deleteEntry: createAction(async (api, args) => { diff --git a/src/shared/schema/mix/schemas/dash.ts b/src/shared/schema/mix/schemas/dash.ts index 46806e2c96..aa28af411d 100644 --- a/src/shared/schema/mix/schemas/dash.ts +++ b/src/shared/schema/mix/schemas/dash.ts @@ -55,13 +55,30 @@ export const createDashArgsSchema = z.strictObject({ export const createDashResultSchema = dashUsSchema; -export const getDashArgsSchema = z.strictObject({ - dashboardId: z.string(), - revId: z.string().optional(), - includePermissions: z.boolean().optional().default(false), - includeLinks: z.boolean().optional().default(false), - includeFavorite: z.boolean().optional().default(false), - branch: z.enum(['published', 'saved']).optional(), +export const dashSchemaV1 = z.object({ + annotation: z + .object({ + description: z.string().optional(), + }) + .nullable() + .optional(), + createdAt: z.string(), + createdBy: z.string(), + data: dataSchema, + entryId: z.string(), + hidden: z.boolean(), + key: z.union([z.null(), z.string()]), + links: z.record(z.string(), z.string()).optional(), + meta: z.record(z.string(), z.string()), + public: z.boolean(), + publishedId: z.string(), + revId: z.string(), + savedId: z.string(), + scope: z.literal(EntryScope.Dash), + tenantId: z.string(), + type: z.literal(''), + updatedAt: z.string(), + updatedBy: z.string(), + version: z.literal(1), + workbookId: z.union([z.null(), z.string()]), }); - -export const getDashResultSchema = dashUsSchema; diff --git a/src/shared/schema/mix/types/dash.ts b/src/shared/schema/mix/types/dash.ts index cd1c9f8e10..68834b9c25 100644 --- a/src/shared/schema/mix/types/dash.ts +++ b/src/shared/schema/mix/types/dash.ts @@ -3,7 +3,7 @@ import type z from 'zod'; import type {WizardVisualizationId} from '../../../constants'; import type {ChartsStats, DashStats, WorkbookId} from '../../../types'; import type {GetEntriesEntryResponse} from '../../us/types'; -import type {createDashResultSchema, updateDashResultSchema} from '../schemas/dash'; +import type {createDashResultSchema, dashSchemaV1, updateDashResultSchema} from '../schemas/dash'; export type CollectDashStatsResponse = { status: string; @@ -58,3 +58,5 @@ export type GetWidgetsDatasetsFieldsArgs = { export type UpdateDashResponse = z.infer; export type CreateDashResponse = z.infer; + +export type DashV1 = z.infer; diff --git a/src/shared/schema/mix/helpers/__tests__/editor.test.ts b/src/shared/schema/mix/utils/__tests__/editor.test.ts similarity index 100% rename from src/shared/schema/mix/helpers/__tests__/editor.test.ts rename to src/shared/schema/mix/utils/__tests__/editor.test.ts diff --git a/src/shared/schema/mix/helpers/dash.ts b/src/shared/schema/mix/utils/dash/index.ts similarity index 89% rename from src/shared/schema/mix/helpers/dash.ts rename to src/shared/schema/mix/utils/dash/index.ts index 43a960727c..b92ccc4861 100644 --- a/src/shared/schema/mix/helpers/dash.ts +++ b/src/shared/schema/mix/utils/dash/index.ts @@ -3,13 +3,13 @@ import type {IncomingHttpHeaders} from 'http'; import type {ContextApiWithRoot} from '@gravity-ui/gateway/build/models/common'; import type {AppContext, AppContextParams} from '@gravity-ui/nodekit'; -import {registry} from '../../../../server/registry'; -import type {DatalensGatewaySchemas} from '../../../../server/types/gateway'; -import type {WizardVisualizationId} from '../../../constants'; -import type {DatasetField, WorkbookId} from '../../../types'; -import type {GetDataSetFieldsByIdResponse} from '../../bi/types'; -import type {simpleSchema} from '../../simple-schema'; -import type {GetEntryResponse} from '../../us/types'; +import {registry} from '../../../../../server/registry'; +import type {DatalensGatewaySchemas} from '../../../../../server/types/gateway'; +import type {WizardVisualizationId} from '../../../../constants'; +import type {DatasetField, WorkbookId} from '../../../../types'; +import type {GetDataSetFieldsByIdResponse} from '../../../bi/types'; +import type {simpleSchema} from '../../../simple-schema'; +import type {GetEntryResponse} from '../../../us/types'; export type DatasetDictResponse = {datasetId: string; data: GetEntryResponse | null}; diff --git a/src/shared/schema/mix/utils/dash/migrate-dash-to-v1.ts b/src/shared/schema/mix/utils/dash/migrate-dash-to-v1.ts new file mode 100644 index 0000000000..c23e1274b9 --- /dev/null +++ b/src/shared/schema/mix/utils/dash/migrate-dash-to-v1.ts @@ -0,0 +1,77 @@ +import type {DashData} from '../../../..'; +import {DashSchemeConverter} from '../../../..'; +import {ServerError} from '../../../../constants/error'; +import type {DashV1, GetEntryResponse} from '../../../types'; + +export const migrateDashToV1 = ({ + version, + scope, + entryId, + key, + createdAt, + createdBy, + updatedAt, + updatedBy, + revId, + savedId, + publishedId, + tenantId, + hidden, + public: isPublic, + workbookId, + meta, + type, + data, + annotation, + links, +}: GetEntryResponse): DashV1 => { + if (version && version > 1) { + throw new ServerError(`Unsupported dashboard version: ${version}`, { + status: 500, + }); + } + + let migratedAnnotation = annotation; + + if ( + !migratedAnnotation && + data && + 'description' in data && + typeof data.description === 'string' + ) { + migratedAnnotation = { + description: data.description, + }; + } + + let migratedData: DashData; + + if (version === 1 || !DashSchemeConverter.isUpdateNeeded(data as unknown as DashData)) { + migratedData = data as unknown as DashData; + } else { + migratedData = DashSchemeConverter.update(data as unknown as DashData); + } + + return { + version: 1, + scope, + entryId, + key, + createdAt, + createdBy, + updatedAt, + updatedBy, + revId, + savedId, + publishedId, + tenantId, + hidden, + public: isPublic, + workbookId, + meta, + annotation: migratedAnnotation, + links, + type, + data: migratedData, + } as DashV1; +}; diff --git a/src/shared/schema/mix/helpers/editor/index.ts b/src/shared/schema/mix/utils/editor/index.ts similarity index 100% rename from src/shared/schema/mix/helpers/editor/index.ts rename to src/shared/schema/mix/utils/editor/index.ts diff --git a/src/shared/schema/mix/helpers/editor/validation.ts b/src/shared/schema/mix/utils/editor/validation.ts similarity index 100% rename from src/shared/schema/mix/helpers/editor/validation.ts rename to src/shared/schema/mix/utils/editor/validation.ts diff --git a/src/shared/schema/mix/helpers/entries.ts b/src/shared/schema/mix/utils/entries.ts similarity index 100% rename from src/shared/schema/mix/helpers/entries.ts rename to src/shared/schema/mix/utils/entries.ts diff --git a/src/shared/schema/mix/helpers/index.ts b/src/shared/schema/mix/utils/index.ts similarity index 100% rename from src/shared/schema/mix/helpers/index.ts rename to src/shared/schema/mix/utils/index.ts diff --git a/src/shared/schema/mix/helpers/validation.ts b/src/shared/schema/mix/utils/validation.ts similarity index 100% rename from src/shared/schema/mix/helpers/validation.ts rename to src/shared/schema/mix/utils/validation.ts diff --git a/src/ui/components/DialogChartWidget/DialogChartWidget.tsx b/src/ui/components/DialogChartWidget/DialogChartWidget.tsx index b64a37f23b..0483031d00 100644 --- a/src/ui/components/DialogChartWidget/DialogChartWidget.tsx +++ b/src/ui/components/DialogChartWidget/DialogChartWidget.tsx @@ -17,7 +17,7 @@ import type { WizardVisualizationId, } from 'shared'; import {DashCommonQa, DialogDashWidgetQA, EntryScope, Feature, ParamsSettingsQA} from 'shared'; -import {getEntryVisualizationType} from 'shared/schema/mix/helpers'; +import {getEntryVisualizationType} from 'shared/schema/mix/utils'; import {Collapse} from 'ui/components/Collapse/Collapse'; import {Interpolate} from 'ui/components/Interpolate'; import {TabMenu} from 'ui/components/TabMenu/TabMenu'; diff --git a/src/ui/components/EntryDialogues/DialogSwitchPublic/DialogPublic/useDialogPublicState.ts b/src/ui/components/EntryDialogues/DialogSwitchPublic/DialogPublic/useDialogPublicState.ts index c1c9388a07..9f328ceb47 100644 --- a/src/ui/components/EntryDialogues/DialogSwitchPublic/DialogPublic/useDialogPublicState.ts +++ b/src/ui/components/EntryDialogues/DialogSwitchPublic/DialogPublic/useDialogPublicState.ts @@ -3,7 +3,7 @@ import React from 'react'; import {toaster} from '@gravity-ui/uikit/toaster-singleton'; import {i18n} from 'i18n'; import {useDispatch} from 'react-redux'; -import {isValidPublishLink} from 'shared/schema/mix/helpers/validation'; +import {isValidPublishLink} from 'shared/schema/mix/utils/validation'; import {showToast} from 'store/actions/toaster'; import type {DataLensApiError} from 'typings'; import {CounterName, GoalId, reachMetricaGoal} from 'ui/libs/metrica'; diff --git a/src/ui/units/dash/store/actions/base/actions.ts b/src/ui/units/dash/store/actions/base/actions.ts index 6fbccb9820..ac8a23f638 100644 --- a/src/ui/units/dash/store/actions/base/actions.ts +++ b/src/ui/units/dash/store/actions/base/actions.ts @@ -303,7 +303,7 @@ export const load = ({ type: SET_STATE, payload: {mode: Mode.Updating}, }); - data = await DashSchemeConverter.update(entry.data); + data = DashSchemeConverter.update(entry.data); convertedEntryData = data; } From 491f9eb858d1dd8d1eb727f2bf274d59e3bce584 Mon Sep 17 00:00:00 2001 From: Vladimir Stepanenko Date: Fri, 7 Nov 2025 12:30:41 +0300 Subject: [PATCH 11/21] Dash public api actions --- src/server/components/public-api/config/v0.ts | 2 +- .../actions/dash/{get-dashboard.ts => get-dashboard-v1.ts} | 2 +- src/shared/schema/mix/actions/dash/index.ts | 5 ++--- 3 files changed, 4 insertions(+), 5 deletions(-) rename src/shared/schema/mix/actions/dash/{get-dashboard.ts => get-dashboard-v1.ts} (97%) diff --git a/src/server/components/public-api/config/v0.ts b/src/server/components/public-api/config/v0.ts index ff968f49d7..5ab1bdb9d2 100644 --- a/src/server/components/public-api/config/v0.ts +++ b/src/server/components/public-api/config/v0.ts @@ -107,7 +107,7 @@ export const getPublicApiActionsV0 = < // Dashboard getDashboard: { - resolve: (api) => api.mix.__getDashboard__, + resolve: (api) => api.mix.getDashboardV1, openApi: { summary: 'Get dashboard', tags: [ApiTag.Dashboard], diff --git a/src/shared/schema/mix/actions/dash/get-dashboard.ts b/src/shared/schema/mix/actions/dash/get-dashboard-v1.ts similarity index 97% rename from src/shared/schema/mix/actions/dash/get-dashboard.ts rename to src/shared/schema/mix/actions/dash/get-dashboard-v1.ts index 30dc11b081..059f050926 100644 --- a/src/shared/schema/mix/actions/dash/get-dashboard.ts +++ b/src/shared/schema/mix/actions/dash/get-dashboard-v1.ts @@ -33,7 +33,7 @@ export const getDashResultSchema = z title: 'GetDashboardResult', }); -export const getDashboard = createTypedAction( +export const getDashboardV1 = createTypedAction( { paramsSchema: getDashArgsSchema, resultSchema: getDashResultSchema, diff --git a/src/shared/schema/mix/actions/dash/index.ts b/src/shared/schema/mix/actions/dash/index.ts index 69125819b6..871c8ff760 100644 --- a/src/shared/schema/mix/actions/dash/index.ts +++ b/src/shared/schema/mix/actions/dash/index.ts @@ -24,12 +24,11 @@ import { import {createDashboard} from './create-dashboard'; import {deleteDashboard} from './delete-dashboard'; -import {getDashboard} from './get-dashboard'; +import {getDashboardV1} from './get-dashboard-v1'; import {updateDashboard} from './update-dashboard'; export const dashActions = { - // WIP - __getDashboard__: getDashboard, + getDashboardV1, // WIP __updateDashboard__: updateDashboard, // WIP From 781e018ee37ed0ca478f6305506f2ca2aaee9625 Mon Sep 17 00:00:00 2001 From: Vladimir Stepanenko Date: Mon, 10 Nov 2025 21:50:55 +0300 Subject: [PATCH 12/21] Dash public api actions --- src/server/components/public-api/config/v0.ts | 10 +-- src/server/components/sdk/dash.ts | 4 +- src/server/controllers/public-api/index.ts | 2 +- src/server/utils/index.ts | 2 +- .../mix/actions/dash/create-dashboard-v1.ts | 52 +++++++++++++++ .../mix/actions/dash/create-dashboard.ts | 16 ----- src/shared/schema/mix/actions/dash/index.ts | 12 ++-- .../mix/actions/dash/update-dashboard-v1.ts | 65 +++++++++++++++++++ .../mix/actions/dash/update-dashboard.ts | 20 ------ src/shared/schema/mix/actions/ql.ts | 2 +- src/shared/schema/mix/actions/wizard.ts | 2 +- src/shared/schema/mix/schemas/dash.ts | 50 +++++++------- .../schema/us/actions/entries/create-entry.ts | 39 +++++++++++ src/shared/schema/us/actions/entries/index.ts | 4 ++ .../schema/us/actions/entries/update-entry.ts | 25 +++++++ src/shared/schema/us/types/entries.ts | 37 ++++++++++- src/shared/zod-schemas/dash.ts | 11 +--- 17 files changed, 260 insertions(+), 93 deletions(-) create mode 100644 src/shared/schema/mix/actions/dash/create-dashboard-v1.ts delete mode 100644 src/shared/schema/mix/actions/dash/create-dashboard.ts create mode 100644 src/shared/schema/mix/actions/dash/update-dashboard-v1.ts delete mode 100644 src/shared/schema/mix/actions/dash/update-dashboard.ts create mode 100644 src/shared/schema/us/actions/entries/create-entry.ts create mode 100644 src/shared/schema/us/actions/entries/update-entry.ts diff --git a/src/server/components/public-api/config/v0.ts b/src/server/components/public-api/config/v0.ts index 5ab1bdb9d2..ff27a35b9d 100644 --- a/src/server/components/public-api/config/v0.ts +++ b/src/server/components/public-api/config/v0.ts @@ -82,7 +82,7 @@ export const getPublicApiActionsV0 = < }, }, deleteWizardChart: { - resolve: (api) => api.mix._deleteWizardChart, + resolve: (api) => api.mix.deleteWizardChart, openApi: { summary: 'Delete wizard chart', tags: [ApiTag.Wizard], @@ -98,7 +98,7 @@ export const getPublicApiActionsV0 = < }, }, deleteQLChart: { - resolve: (api) => api.mix._deleteQLChart, + resolve: (api) => api.mix.deleteQLChart, openApi: { summary: 'Delete QL chart', tags: [ApiTag.QL], @@ -114,21 +114,21 @@ export const getPublicApiActionsV0 = < }, }, createDashboard: { - resolve: (api) => api.mix.__createDashboard__, + resolve: (api) => api.mix.createDashboardV1, openApi: { summary: 'Create dashboard', tags: [ApiTag.Dashboard], }, }, updateDashboard: { - resolve: (api) => api.mix.__updateDashboard__, + resolve: (api) => api.mix.updateDashboardV1, openApi: { summary: 'Update dashboard', tags: [ApiTag.Dashboard], }, }, deleteDashboard: { - resolve: (api) => api.mix._deleteDashboard, + resolve: (api) => api.mix.deleteDashboard, openApi: { summary: 'Delete dashboard', tags: [ApiTag.Dashboard], diff --git a/src/server/components/sdk/dash.ts b/src/server/components/sdk/dash.ts index 41603c09d8..e5c9178b3b 100644 --- a/src/server/components/sdk/dash.ts +++ b/src/server/components/sdk/dash.ts @@ -93,7 +93,7 @@ function processLinks(data: DashData, matchCallback?: MatchCallback) { ); } -function gatherLinks(data: DashData) { +export function gatherLinks(data: DashData) { return processLinks(data); } @@ -144,7 +144,7 @@ function setDefaultData( const needSetDefaultData = (data: DashData) => DASH_DATA_REQUIRED_FIELDS.some((fieldName) => !(fieldName in data)); -function validateData(data: DashData) { +export function validateData(data: DashData) { const allTabsIds: Set = new Set(); const allItemsIds: Set = new Set(); const allWidgetTabsIds: Set = new Set(); diff --git a/src/server/controllers/public-api/index.ts b/src/server/controllers/public-api/index.ts index 69f0d829e5..5d81e6b2bd 100644 --- a/src/server/controllers/public-api/index.ts +++ b/src/server/controllers/public-api/index.ts @@ -67,7 +67,7 @@ export const createPublicApiController = () => { const {ctx} = req; - const headers = Utils.pickRpcHeaders(req); + const headers = Utils.pickPublicApiHeaders(req); const requestId = ctx.get(REQUEST_ID_PARAM_NAME) || ''; const gatewayAction = action.resolve(gatewayApi); diff --git a/src/server/utils/index.ts b/src/server/utils/index.ts index 916b648099..5a75b7c1c7 100644 --- a/src/server/utils/index.ts +++ b/src/server/utils/index.ts @@ -87,7 +87,7 @@ class Utils { }; } - static pickRpcHeaders(req: Request) { + static pickPublicApiHeaders(req: Request) { const headersMap = req.ctx.config.headersMap; const orgId = req.headers[PUBLIC_API_ORG_ID_HEADER]; diff --git a/src/shared/schema/mix/actions/dash/create-dashboard-v1.ts b/src/shared/schema/mix/actions/dash/create-dashboard-v1.ts new file mode 100644 index 0000000000..f6a15967e8 --- /dev/null +++ b/src/shared/schema/mix/actions/dash/create-dashboard-v1.ts @@ -0,0 +1,52 @@ +import {EntryScope} from '../../../..'; +import {Dash} from '../../../../../server/components/sdk'; +import {createTypedAction} from '../../../gateway-utils'; +import {getTypedApi} from '../../../simple-schema'; +import {createDashArgsSchema, createDashResultSchema} from '../../schemas/dash'; +import type {DashV1} from '../../types'; + +export const createDashboardV1 = createTypedAction( + { + paramsSchema: createDashArgsSchema, + resultSchema: createDashResultSchema, + }, + async (api, args) => { + const typedApi = getTypedApi(api); + + const links = Dash.gatherLinks(args.data); + + Dash.validateData(args.data); + + const createEntryResult = await typedApi.us._createEntry({ + ...args, + type: '', + scope: EntryScope.Dash, + links, + }); + + return { + entry: { + version: 1 as const, + data: createEntryResult.data as DashV1['data'], + meta: createEntryResult.meta as DashV1['meta'], + scope: createEntryResult.scope as EntryScope.Dash, + type: createEntryResult.type as DashV1['type'], + entryId: createEntryResult.entryId, + key: createEntryResult.key, + createdAt: createEntryResult.createdAt, + createdBy: createEntryResult.createdBy, + updatedAt: createEntryResult.updatedAt, + updatedBy: createEntryResult.updatedBy, + revId: createEntryResult.revId, + savedId: createEntryResult.savedId, + publishedId: createEntryResult.publishedId, + tenantId: createEntryResult.tenantId, + hidden: createEntryResult.hidden, + public: createEntryResult.public, + workbookId: createEntryResult.workbookId, + annotation: createEntryResult.annotation, + links: createEntryResult.links, + }, + }; + }, +); diff --git a/src/shared/schema/mix/actions/dash/create-dashboard.ts b/src/shared/schema/mix/actions/dash/create-dashboard.ts deleted file mode 100644 index 52f293634c..0000000000 --- a/src/shared/schema/mix/actions/dash/create-dashboard.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {Dash} from '../../../../../server/components/sdk'; -import {createTypedAction} from '../../../gateway-utils'; -import {createDashArgsSchema, createDashResultSchema} from '../../schemas/dash'; -import type {CreateDashResponse} from '../../types'; - -export const createDashboard = createTypedAction( - { - paramsSchema: createDashArgsSchema, - resultSchema: createDashResultSchema, - }, - async (_, args, {headers, ctx}) => { - const I18n = ctx.get('i18n'); - - return (await Dash.create(args, headers, ctx, I18n)) as unknown as CreateDashResponse; - }, -); diff --git a/src/shared/schema/mix/actions/dash/index.ts b/src/shared/schema/mix/actions/dash/index.ts index 871c8ff760..490e46511e 100644 --- a/src/shared/schema/mix/actions/dash/index.ts +++ b/src/shared/schema/mix/actions/dash/index.ts @@ -22,18 +22,16 @@ import { prepareWidgetDatasetData, } from '../../utils/dash'; -import {createDashboard} from './create-dashboard'; +import {createDashboardV1} from './create-dashboard-v1'; import {deleteDashboard} from './delete-dashboard'; import {getDashboardV1} from './get-dashboard-v1'; -import {updateDashboard} from './update-dashboard'; +import {updateDashboardV1} from './update-dashboard-v1'; export const dashActions = { getDashboardV1, - // WIP - __updateDashboard__: updateDashboard, - // WIP - __createDashboard__: createDashboard, - _deleteDashboard: deleteDashboard, + createDashboardV1, + updateDashboardV1, + deleteDashboard, collectDashStats: createAction( async (_, args, {ctx}) => { diff --git a/src/shared/schema/mix/actions/dash/update-dashboard-v1.ts b/src/shared/schema/mix/actions/dash/update-dashboard-v1.ts new file mode 100644 index 0000000000..7b22cf5811 --- /dev/null +++ b/src/shared/schema/mix/actions/dash/update-dashboard-v1.ts @@ -0,0 +1,65 @@ +import {type EntryScope, EntryUpdateMode} from '../../../..'; +import {Dash} from '../../../../../server/components/sdk'; +import {createTypedAction} from '../../../gateway-utils'; +import {getTypedApi} from '../../../simple-schema'; +import {updateDashArgsSchema, updateDashResultSchema} from '../../schemas/dash'; +import type {DashV1} from '../../types'; + +export const updateDashboardV1 = createTypedAction( + { + paramsSchema: updateDashArgsSchema, + resultSchema: updateDashResultSchema, + }, + async ( + api, + {entryId, mode = EntryUpdateMode.Publish, meta, data, revId, links, annotation}, + ) => { + const typedApi = getTypedApi(api); + + // const gatheredLinks = Dash.gatherLinks(data); + + Dash.validateData(data); + + const updateEntryResult = await typedApi.us._updateEntry({ + entryId, + mode, + meta, + data, + revId, + links, // OR gatheredLinks ? ?? ?? ? ??? + annotation, + skipSyncLinks: mode !== EntryUpdateMode.Publish, + }); + + // const I18n = ctx.get('i18n'); + + // return (await Dash.update(entryId, args, headers, ctx, I18n, { + // forceMigrate: true, + // })) as unknown as UpdateDashResponse; + + return { + entry: { + version: 1 as const, + data: updateEntryResult.data as DashV1['data'], + meta: updateEntryResult.meta as DashV1['meta'], + scope: updateEntryResult.scope as EntryScope.Dash, + type: updateEntryResult.type as DashV1['type'], + entryId: updateEntryResult.entryId, + key: updateEntryResult.key, + createdAt: updateEntryResult.createdAt, + createdBy: updateEntryResult.createdBy, + updatedAt: updateEntryResult.updatedAt, + updatedBy: updateEntryResult.updatedBy, + revId: updateEntryResult.revId, + savedId: updateEntryResult.savedId, + publishedId: updateEntryResult.publishedId, + tenantId: updateEntryResult.tenantId, + hidden: updateEntryResult.hidden, + public: updateEntryResult.public, + workbookId: updateEntryResult.workbookId, + annotation: updateEntryResult.annotation, + links: updateEntryResult.links, + }, + }; + }, +); diff --git a/src/shared/schema/mix/actions/dash/update-dashboard.ts b/src/shared/schema/mix/actions/dash/update-dashboard.ts deleted file mode 100644 index 9a7149f024..0000000000 --- a/src/shared/schema/mix/actions/dash/update-dashboard.ts +++ /dev/null @@ -1,20 +0,0 @@ -import {Dash} from '../../../../../server/components/sdk'; -import {createTypedAction} from '../../../gateway-utils'; -import {updateDashArgsSchema, updateDashResultSchema} from '../../schemas/dash'; -import type {UpdateDashResponse} from '../../types'; - -export const updateDashboard = createTypedAction( - { - paramsSchema: updateDashArgsSchema, - resultSchema: updateDashResultSchema, - }, - async (_, args, {headers, ctx}) => { - const {entryId} = args; - - const I18n = ctx.get('i18n'); - - return (await Dash.update(entryId, args, headers, ctx, I18n, { - forceMigrate: true, - })) as unknown as UpdateDashResponse; - }, -); diff --git a/src/shared/schema/mix/actions/ql.ts b/src/shared/schema/mix/actions/ql.ts index 3b24fc0f5f..74658e5c37 100644 --- a/src/shared/schema/mix/actions/ql.ts +++ b/src/shared/schema/mix/actions/ql.ts @@ -52,7 +52,7 @@ export const qlActions = { }, ), - _deleteQLChart: createTypedAction( + deleteQLChart: createTypedAction( { paramsSchema: deleteQLChartArgsSchema, resultSchema: deleteQLChartResultSchema, diff --git a/src/shared/schema/mix/actions/wizard.ts b/src/shared/schema/mix/actions/wizard.ts index 37b26f9103..9ac1a8b4a3 100644 --- a/src/shared/schema/mix/actions/wizard.ts +++ b/src/shared/schema/mix/actions/wizard.ts @@ -60,7 +60,7 @@ export const wizardActions = { }, ), - _deleteWizardChart: createTypedAction( + deleteWizardChart: createTypedAction( { paramsSchema: deleteWizardChartArgsSchema, resultSchema: deleteWizardChartResultSchema, diff --git a/src/shared/schema/mix/schemas/dash.ts b/src/shared/schema/mix/schemas/dash.ts index aa28af411d..d986178b86 100644 --- a/src/shared/schema/mix/schemas/dash.ts +++ b/src/shared/schema/mix/schemas/dash.ts @@ -1,7 +1,7 @@ import z from 'zod'; import {EntryScope, EntryUpdateMode} from '../../..'; -import {dashSchema, dataSchema} from '../../../zod-schemas/dash'; +import {dataSchema} from '../../../zod-schemas/dash'; export const deleteDashArgsSchema = z.strictObject({ dashboardId: z.string(), @@ -10,26 +10,6 @@ export const deleteDashArgsSchema = z.strictObject({ export const deleteDashResultSchema = z.object({}); -const dashUsSchema = z.object({ - ...dashSchema.shape, - entryId: z.string(), - scope: z.literal(EntryScope.Dash), - public: z.boolean(), - isFavorite: z.boolean(), - createdAt: z.string(), - createdBy: z.string(), - updatedAt: z.string(), - updatedBy: z.string(), - revId: z.string(), - savedId: z.string(), - publishedId: z.string(), - meta: z.record(z.string(), z.string()), - links: z.record(z.string(), z.string()).optional(), - key: z.union([z.null(), z.string()]), - workbookId: z.union([z.null(), z.string()]), - type: z.literal(''), -}); - export const updateDashArgsSchema = z.strictObject({ key: z.string().min(1), workbookId: z.string().optional(), @@ -39,22 +19,28 @@ export const updateDashArgsSchema = z.strictObject({ entryId: z.string(), revId: z.string(), mode: z.enum(EntryUpdateMode), + annotation: z + .object({ + description: z.string(), + }) + .optional(), }); -export const updateDashResultSchema = dashUsSchema; - export const createDashArgsSchema = z.strictObject({ key: z.string().min(1), data: dataSchema, - meta: z.record(z.any(), z.any()).optional(), + meta: z.record(z.string(), z.string()), links: z.record(z.string(), z.string()).optional(), workbookId: z.string().optional(), lockToken: z.string().optional(), mode: z.enum(EntryUpdateMode), + annotation: z + .object({ + description: z.string(), + }) + .optional(), }); -export const createDashResultSchema = dashUsSchema; - export const dashSchemaV1 = z.object({ annotation: z .object({ @@ -68,10 +54,10 @@ export const dashSchemaV1 = z.object({ entryId: z.string(), hidden: z.boolean(), key: z.union([z.null(), z.string()]), - links: z.record(z.string(), z.string()).optional(), + links: z.record(z.string(), z.string()).nullable().optional(), meta: z.record(z.string(), z.string()), public: z.boolean(), - publishedId: z.string(), + publishedId: z.string().nullable(), revId: z.string(), savedId: z.string(), scope: z.literal(EntryScope.Dash), @@ -82,3 +68,11 @@ export const dashSchemaV1 = z.object({ version: z.literal(1), workbookId: z.union([z.null(), z.string()]), }); + +export const createDashResultSchema = z.object({ + entry: dashSchemaV1, +}); + +export const updateDashResultSchema = z.object({ + entry: dashSchemaV1, +}); diff --git a/src/shared/schema/us/actions/entries/create-entry.ts b/src/shared/schema/us/actions/entries/create-entry.ts new file mode 100644 index 0000000000..64330c9ea6 --- /dev/null +++ b/src/shared/schema/us/actions/entries/create-entry.ts @@ -0,0 +1,39 @@ +import {EntryUpdateMode} from '../../../..'; +import {createAction} from '../../../gateway-utils'; +import type {CreateEntryArgs, CreateEntryResponse} from '../../types'; + +export const _createEntry = createAction({ + method: 'POST', + path: () => '/v1/entries', + params: ( + { + scope, + type, + key, + data, + meta = {}, + name, + workbookId, + mode = EntryUpdateMode.Publish, + links, + annotation, + }, + headers, + ) => { + return { + body: { + scope, + type, + key, + meta, + data, + name, + workbookId, + mode, + links, + annotation, + }, + headers, + }; + }, +}); diff --git a/src/shared/schema/us/actions/entries/index.ts b/src/shared/schema/us/actions/entries/index.ts index 672a0f94ed..a02180c4d5 100644 --- a/src/shared/schema/us/actions/entries/index.ts +++ b/src/shared/schema/us/actions/entries/index.ts @@ -48,14 +48,18 @@ import type { SwitchPublicationStatusResponse, } from '../../types'; +import {_createEntry} from './create-entry'; import {getEntries} from './get-entries'; import {listDirectory} from './list-directory'; +import {_updateEntry} from './update-entry'; const PATH_PREFIX = '/v1'; const PATH_PREFIX_V2 = '/v2'; const PRIVATE_PATH_PREFIX = '/private'; export const entriesActions = { + _createEntry, + _updateEntry, listDirectory, getEntries, getEntry: createAction({ diff --git a/src/shared/schema/us/actions/entries/update-entry.ts b/src/shared/schema/us/actions/entries/update-entry.ts new file mode 100644 index 0000000000..5717d9a21f --- /dev/null +++ b/src/shared/schema/us/actions/entries/update-entry.ts @@ -0,0 +1,25 @@ +import {EntryUpdateMode} from '../../../..'; +import {createAction} from '../../../gateway-utils'; +import {filterUrlFragment} from '../../../utils'; +import type {UpdateEntryArgs, UpdateEntryResponse} from '../../types'; + +export const _updateEntry = createAction({ + method: 'POST', + path: ({entryId}) => `/v1/entries/${filterUrlFragment(entryId)}`, + params: ( + {data, meta = {}, revId, mode = EntryUpdateMode.Publish, links, annotation}, + headers, + ) => { + return { + body: { + mode, + meta, + data, + revId, + links, + annotation, + }, + headers, + }; + }, +}); diff --git a/src/shared/schema/us/types/entries.ts b/src/shared/schema/us/types/entries.ts index 87363c6a8c..700eee24ae 100644 --- a/src/shared/schema/us/types/entries.ts +++ b/src/shared/schema/us/types/entries.ts @@ -1,6 +1,6 @@ import type z from 'zod'; -import type {EntryScope, WorkbookId} from '../../..'; +import type {EntryAnnotationArgs, EntryScope, EntryUpdateMode, WorkbookId} from '../../..'; import type {Permissions} from '../../../types'; import type { getEntriesEntryResponseSchema, @@ -16,6 +16,7 @@ import type {EntriesCommonArgs} from './common'; import type { EntryFieldData, EntryFieldLinks, + EntryFieldMeta, EntryFields, EntryMetaFields, EntryNavigationFields, @@ -262,3 +263,37 @@ export interface GetEntriesAnnotationArgs { scope?: EntryScope; type?: string; } + +export interface CreateEntryResponse extends EntryFields { + links: EntryFieldLinks; +} + +export interface CreateEntryArgs { + scope: EntryScope; + type: string; + data: EntryFieldData; + key?: string; + meta?: EntryFieldMeta; + workbookId?: string; + name?: string; + mode?: EntryUpdateMode; + links?: EntryFieldLinks; + description?: string; + annotation?: EntryAnnotationArgs; +} + +export interface UpdateEntryResponse extends EntryFields { + links?: EntryFieldLinks; +} + +export interface UpdateEntryArgs { + entryId: string; + mode: EntryUpdateMode; + data: EntryFieldData; + revId?: string; + meta?: EntryFieldMeta; + links?: EntryFieldLinks; + description?: string; + annotation?: EntryAnnotationArgs; + skipSyncLinks?: boolean; +} diff --git a/src/shared/zod-schemas/dash.ts b/src/shared/zod-schemas/dash.ts index 8a380cbbb0..9eb25377d9 100644 --- a/src/shared/zod-schemas/dash.ts +++ b/src/shared/zod-schemas/dash.ts @@ -259,18 +259,9 @@ const settingsSchema = z.object({ export const dataSchema = z.object({ counter: z.number().int().min(1), salt: z.string().min(1), - schemeVersion: z.literal(DASH_CURRENT_SCHEME_VERSION).default(DASH_CURRENT_SCHEME_VERSION), + schemeVersion: z.literal(DASH_CURRENT_SCHEME_VERSION), tabs: z.array(tabSchema), settings: settingsSchema, supportDescription: z.string().optional(), accessDescription: z.string().optional(), }); - -// Main dashboard API validation schema -export const dashSchema = z.object({ - key: z.string().min(1).optional(), - workbookId: z.union([z.null(), z.string()]).optional(), - data: dataSchema, - meta: z.record(z.any(), z.any()).optional(), - links: z.record(z.string(), z.string()).optional(), -}); From 428460925dd8483af01fc78eff3830e3bae6a2e6 Mon Sep 17 00:00:00 2001 From: Vladimir Stepanenko Date: Tue, 11 Nov 2025 11:52:40 +0300 Subject: [PATCH 13/21] Dash public api actions --- .../mix/actions/dash/update-dashboard-v1.ts | 19 ++++++------------- src/shared/schema/mix/schemas/dash.ts | 2 -- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/src/shared/schema/mix/actions/dash/update-dashboard-v1.ts b/src/shared/schema/mix/actions/dash/update-dashboard-v1.ts index 7b22cf5811..037d3829ee 100644 --- a/src/shared/schema/mix/actions/dash/update-dashboard-v1.ts +++ b/src/shared/schema/mix/actions/dash/update-dashboard-v1.ts @@ -10,33 +10,26 @@ export const updateDashboardV1 = createTypedAction( paramsSchema: updateDashArgsSchema, resultSchema: updateDashResultSchema, }, - async ( - api, - {entryId, mode = EntryUpdateMode.Publish, meta, data, revId, links, annotation}, - ) => { + async (api, {entryId, mode = EntryUpdateMode.Publish, meta, data, revId, annotation}) => { const typedApi = getTypedApi(api); - // const gatheredLinks = Dash.gatherLinks(data); + const links = Dash.gatherLinks(data); Dash.validateData(data); + const skipSyncLinks = mode !== EntryUpdateMode.Publish; + const updateEntryResult = await typedApi.us._updateEntry({ entryId, mode, meta, data, revId, - links, // OR gatheredLinks ? ?? ?? ? ??? + links, annotation, - skipSyncLinks: mode !== EntryUpdateMode.Publish, + skipSyncLinks, }); - // const I18n = ctx.get('i18n'); - - // return (await Dash.update(entryId, args, headers, ctx, I18n, { - // forceMigrate: true, - // })) as unknown as UpdateDashResponse; - return { entry: { version: 1 as const, diff --git a/src/shared/schema/mix/schemas/dash.ts b/src/shared/schema/mix/schemas/dash.ts index d986178b86..848c751aa3 100644 --- a/src/shared/schema/mix/schemas/dash.ts +++ b/src/shared/schema/mix/schemas/dash.ts @@ -15,7 +15,6 @@ export const updateDashArgsSchema = z.strictObject({ workbookId: z.string().optional(), data: dataSchema, meta: z.record(z.any(), z.any()), - links: z.record(z.string(), z.string()), entryId: z.string(), revId: z.string(), mode: z.enum(EntryUpdateMode), @@ -30,7 +29,6 @@ export const createDashArgsSchema = z.strictObject({ key: z.string().min(1), data: dataSchema, meta: z.record(z.string(), z.string()), - links: z.record(z.string(), z.string()).optional(), workbookId: z.string().optional(), lockToken: z.string().optional(), mode: z.enum(EntryUpdateMode), From e75862cc31639852b529721e15b41a264d885f0a Mon Sep 17 00:00:00 2001 From: Vladimir Stepanenko Date: Tue, 11 Nov 2025 14:24:40 +0300 Subject: [PATCH 14/21] Dash public api actions --- .../mix/actions/dash/update-dashboard-v1.ts | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/shared/schema/mix/actions/dash/update-dashboard-v1.ts b/src/shared/schema/mix/actions/dash/update-dashboard-v1.ts index 037d3829ee..1ee203a013 100644 --- a/src/shared/schema/mix/actions/dash/update-dashboard-v1.ts +++ b/src/shared/schema/mix/actions/dash/update-dashboard-v1.ts @@ -1,10 +1,13 @@ -import {type EntryScope, EntryUpdateMode} from '../../../..'; +import {EntryScope, EntryUpdateMode} from '../../../..'; import {Dash} from '../../../../../server/components/sdk'; +import {ServerError} from '../../../../constants/error'; import {createTypedAction} from '../../../gateway-utils'; import {getTypedApi} from '../../../simple-schema'; import {updateDashArgsSchema, updateDashResultSchema} from '../../schemas/dash'; import type {DashV1} from '../../types'; +const CURRENT_DASH_VERSION = 1 as const; + export const updateDashboardV1 = createTypedAction( { paramsSchema: updateDashArgsSchema, @@ -13,6 +16,24 @@ export const updateDashboardV1 = createTypedAction( async (api, {entryId, mode = EntryUpdateMode.Publish, meta, data, revId, annotation}) => { const typedApi = getTypedApi(api); + const savedEntry = await typedApi.us.getEntry({entryId}); + + if (savedEntry.scope !== EntryScope.Dash) { + throw new ServerError('Entry not found', { + status: 404, + }); + } + + if (savedEntry.version && savedEntry.version > CURRENT_DASH_VERSION) { + throw new ServerError( + `The entry was created or updated using a newer API version and cannot be modified through this API version. + Entry version is: ${savedEntry.version}`, + { + status: 409, + }, + ); + } + const links = Dash.gatherLinks(data); Dash.validateData(data); @@ -32,7 +53,7 @@ export const updateDashboardV1 = createTypedAction( return { entry: { - version: 1 as const, + version: CURRENT_DASH_VERSION, data: updateEntryResult.data as DashV1['data'], meta: updateEntryResult.meta as DashV1['meta'], scope: updateEntryResult.scope as EntryScope.Dash, From f9352b0c3cf66b28ea7ebb78643418ecb5c15ad6 Mon Sep 17 00:00:00 2001 From: Vladimir Stepanenko Date: Wed, 26 Nov 2025 16:57:10 +0300 Subject: [PATCH 15/21] Fix --- src/shared/schema/mix/utils/__tests__/editor.test.ts | 4 ++-- src/ui/components/DialogChartWidget/DialogChartWidget.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/shared/schema/mix/utils/__tests__/editor.test.ts b/src/shared/schema/mix/utils/__tests__/editor.test.ts index 3cc2a8878b..b48733db7b 100644 --- a/src/shared/schema/mix/utils/__tests__/editor.test.ts +++ b/src/shared/schema/mix/utils/__tests__/editor.test.ts @@ -1,6 +1,6 @@ import {getEntryLinks, validateData} from '../editor'; -describe('shared/schema/mix/helpers/validateData', () => { +describe('shared/schema/mix/utils/validateData', () => { test.each([ { name: 'valid meta with links', @@ -64,7 +64,7 @@ describe('shared/schema/mix/helpers/validateData', () => { }); }); -describe('shared/schema/mix/helpers/getEntryLinks', () => { +describe('shared/schema/mix/utils/getEntryLinks', () => { test.each([ { name: 'returns empty object when no meta', diff --git a/src/ui/components/DialogChartWidget/DialogChartWidget.tsx b/src/ui/components/DialogChartWidget/DialogChartWidget.tsx index f90c3c18d3..38f8b8411d 100644 --- a/src/ui/components/DialogChartWidget/DialogChartWidget.tsx +++ b/src/ui/components/DialogChartWidget/DialogChartWidget.tsx @@ -37,7 +37,7 @@ import { Feature, ParamsSettingsQA, } from 'shared'; -import {getEntryVisualizationType} from 'shared/schema/mix/helpers'; +import {getEntryVisualizationType} from 'shared/schema/mix/utils'; import {Collapse} from 'ui/components/Collapse/Collapse'; import {Interpolate} from 'ui/components/Interpolate'; import {TabMenu} from 'ui/components/TabMenu/TabMenu'; From fcee742d2d97a9558d981a856e19f43f2ee7b5ff Mon Sep 17 00:00:00 2001 From: Vladimir Stepanenko Date: Mon, 1 Dec 2025 14:34:34 +0300 Subject: [PATCH 16/21] Fix --- src/shared/schema/mix/actions/dash/create-dashboard-v1.ts | 2 +- src/shared/schema/mix/actions/dash/delete-dashboard.ts | 4 ++-- src/shared/schema/mix/actions/dash/get-dashboard-v1.ts | 6 +++--- src/shared/schema/mix/actions/dash/update-dashboard-v1.ts | 7 +++++-- src/shared/schema/mix/actions/editor.ts | 4 ++-- src/shared/schema/mix/actions/entries.ts | 8 ++++++-- src/ui/components/DialogChartWidget/DialogChartWidget.tsx | 2 +- .../DialogPublic/useDialogPublicState.ts | 2 +- 8 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/shared/schema/mix/actions/dash/create-dashboard-v1.ts b/src/shared/schema/mix/actions/dash/create-dashboard-v1.ts index 265bda9acc..4c2b86cffa 100644 --- a/src/shared/schema/mix/actions/dash/create-dashboard-v1.ts +++ b/src/shared/schema/mix/actions/dash/create-dashboard-v1.ts @@ -10,7 +10,7 @@ export const createDashboardV1 = createTypedAction( paramsSchema: createDashArgsSchema, resultSchema: createDashResultSchema, }, - async (api, args) => { + async (api, args): Promise => { const typedApi = getTypedApi(api); const links = Dash.gatherLinks(args.data); diff --git a/src/shared/schema/mix/actions/dash/delete-dashboard.ts b/src/shared/schema/mix/actions/dash/delete-dashboard.ts index 4310601286..f00bc8768b 100644 --- a/src/shared/schema/mix/actions/dash/delete-dashboard.ts +++ b/src/shared/schema/mix/actions/dash/delete-dashboard.ts @@ -1,6 +1,6 @@ +import {getTypedApi} from '../../..'; import {EntryScope} from '../../../..'; import {createTypedAction} from '../../../gateway-utils'; -import {getTypedApi} from '../../../simple-schema'; import {deleteDashArgsSchema, deleteDashResultSchema} from '../../schemas/dash'; export const deleteDashboard = createTypedAction( @@ -8,7 +8,7 @@ export const deleteDashboard = createTypedAction( paramsSchema: deleteDashArgsSchema, resultSchema: deleteDashResultSchema, }, - async (api, {lockToken, dashboardId}) => { + async (api, {lockToken, dashboardId}): Promise => { const typedApi = getTypedApi(api); await typedApi.us._deleteUSEntry({ diff --git a/src/shared/schema/mix/actions/dash/get-dashboard-v1.ts b/src/shared/schema/mix/actions/dash/get-dashboard-v1.ts index 059f050926..12c9159341 100644 --- a/src/shared/schema/mix/actions/dash/get-dashboard-v1.ts +++ b/src/shared/schema/mix/actions/dash/get-dashboard-v1.ts @@ -1,13 +1,13 @@ import z from 'zod'; +import {getTypedApi} from '../../..'; import {EntryScope} from '../../../..'; import {registerComponentId} from '../../../../components/public-api/utils'; import {ServerError} from '../../../../constants/error'; import {permissionsSchema} from '../../../../zod-schemas/permissions'; import {createTypedAction} from '../../../gateway-utils'; -import {getTypedApi} from '../../../simple-schema'; +import {migrateDashToV1} from '../../helpers/dash/migrate-dash-to-v1'; import {dashSchemaV1} from '../../schemas/dash'; -import {migrateDashToV1} from '../../utils/dash/migrate-dash-to-v1'; export const getDashArgsSchema = z .object({ @@ -38,7 +38,7 @@ export const getDashboardV1 = createTypedAction( paramsSchema: getDashArgsSchema, resultSchema: getDashResultSchema, }, - async (api, args) => { + async (api, args): Promise => { const { dashboardId, includePermissions, diff --git a/src/shared/schema/mix/actions/dash/update-dashboard-v1.ts b/src/shared/schema/mix/actions/dash/update-dashboard-v1.ts index 1ee203a013..d47c5f0b5d 100644 --- a/src/shared/schema/mix/actions/dash/update-dashboard-v1.ts +++ b/src/shared/schema/mix/actions/dash/update-dashboard-v1.ts @@ -1,8 +1,8 @@ +import {getTypedApi} from '../../..'; import {EntryScope, EntryUpdateMode} from '../../../..'; import {Dash} from '../../../../../server/components/sdk'; import {ServerError} from '../../../../constants/error'; import {createTypedAction} from '../../../gateway-utils'; -import {getTypedApi} from '../../../simple-schema'; import {updateDashArgsSchema, updateDashResultSchema} from '../../schemas/dash'; import type {DashV1} from '../../types'; @@ -13,7 +13,10 @@ export const updateDashboardV1 = createTypedAction( paramsSchema: updateDashArgsSchema, resultSchema: updateDashResultSchema, }, - async (api, {entryId, mode = EntryUpdateMode.Publish, meta, data, revId, annotation}) => { + async ( + api, + {entryId, mode = EntryUpdateMode.Publish, meta, data, revId, annotation}, + ): Promise => { const typedApi = getTypedApi(api); const savedEntry = await typedApi.us.getEntry({entryId}); diff --git a/src/shared/schema/mix/actions/editor.ts b/src/shared/schema/mix/actions/editor.ts index 3cba9f1824..be9bd506c1 100644 --- a/src/shared/schema/mix/actions/editor.ts +++ b/src/shared/schema/mix/actions/editor.ts @@ -7,8 +7,8 @@ import type { UpdateEditorChartArgs, UpdateEditorChartResponse, } from '../../us/types'; -import {getEntryLinks} from '../utils'; -import {validateData} from '../utils/editor/validation'; +import {getEntryLinks} from '../helpers'; +import {validateData} from '../helpers/editor/validation'; export const editorActions = { createEditorChart: createAction( diff --git a/src/shared/schema/mix/actions/entries.ts b/src/shared/schema/mix/actions/entries.ts index 9119875c43..8fcbec7410 100644 --- a/src/shared/schema/mix/actions/entries.ts +++ b/src/shared/schema/mix/actions/entries.ts @@ -9,6 +9,12 @@ import type { GetRelationsEntry, SwitchPublicationStatusResponse, } from '../../us/types'; +import { + checkEntriesForPublication, + escapeStringForLike, + getEntryMetaStatusByError, +} from '../helpers'; +import {isValidPublishLink} from '../helpers/validation'; import type { DeleteEntryArgs, DeleteEntryResponse, @@ -26,8 +32,6 @@ import type { ResolveEntryByLinkArgs, ResolveEntryByLinkResponse, } from '../types'; -import {checkEntriesForPublication, escapeStringForLike, getEntryMetaStatusByError} from '../utils'; -import {isValidPublishLink} from '../utils/validation'; export const entriesActions = { deleteEntry: createAction( diff --git a/src/ui/components/DialogChartWidget/DialogChartWidget.tsx b/src/ui/components/DialogChartWidget/DialogChartWidget.tsx index 7e094582f4..a5879470f4 100644 --- a/src/ui/components/DialogChartWidget/DialogChartWidget.tsx +++ b/src/ui/components/DialogChartWidget/DialogChartWidget.tsx @@ -37,7 +37,7 @@ import { Feature, ParamsSettingsQA, } from 'shared'; -import {getEntryVisualizationType} from 'shared/schema/mix/utils'; +import {getEntryVisualizationType} from 'shared/schema/mix/helpers'; import {Collapse} from 'ui/components/Collapse/Collapse'; import {Interpolate} from 'ui/components/Interpolate'; import {TabMenu} from 'ui/components/TabMenu/TabMenu'; diff --git a/src/ui/components/EntryDialogues/DialogSwitchPublic/DialogPublic/useDialogPublicState.ts b/src/ui/components/EntryDialogues/DialogSwitchPublic/DialogPublic/useDialogPublicState.ts index 9f328ceb47..c1c9388a07 100644 --- a/src/ui/components/EntryDialogues/DialogSwitchPublic/DialogPublic/useDialogPublicState.ts +++ b/src/ui/components/EntryDialogues/DialogSwitchPublic/DialogPublic/useDialogPublicState.ts @@ -3,7 +3,7 @@ import React from 'react'; import {toaster} from '@gravity-ui/uikit/toaster-singleton'; import {i18n} from 'i18n'; import {useDispatch} from 'react-redux'; -import {isValidPublishLink} from 'shared/schema/mix/utils/validation'; +import {isValidPublishLink} from 'shared/schema/mix/helpers/validation'; import {showToast} from 'store/actions/toaster'; import type {DataLensApiError} from 'typings'; import {CounterName, GoalId, reachMetricaGoal} from 'ui/libs/metrica'; From e3e977e86582ce8aac5126f4a08a95bd7264c889 Mon Sep 17 00:00:00 2001 From: Vladimir Stepanenko Date: Mon, 1 Dec 2025 15:03:59 +0300 Subject: [PATCH 17/21] Fix --- .../mix/actions/dash/create-dashboard-v1.ts | 13 ++-- .../mix/actions/dash/delete-dashboard.ts | 5 +- .../mix/actions/dash/get-dashboard-v1.ts | 37 ++------- .../mix/actions/dash/update-dashboard-v1.ts | 13 ++-- src/shared/schema/mix/schemas/dash.ts | 76 ------------------- .../mix/schemas/dash/create-dashboard-v1.ts | 24 ++++++ src/shared/schema/mix/schemas/dash/dash-v1.ts | 32 ++++++++ .../mix/schemas/dash/delete-dashboard.ts | 8 ++ .../mix/schemas/dash/get-dashboard-v1.ts | 30 ++++++++ .../mix/schemas/dash/update-dashboard-v1.ts | 25 ++++++ .../mix/types/{dash.ts => dash/index.ts} | 20 +++-- 11 files changed, 157 insertions(+), 126 deletions(-) delete mode 100644 src/shared/schema/mix/schemas/dash.ts create mode 100644 src/shared/schema/mix/schemas/dash/create-dashboard-v1.ts create mode 100644 src/shared/schema/mix/schemas/dash/dash-v1.ts create mode 100644 src/shared/schema/mix/schemas/dash/delete-dashboard.ts create mode 100644 src/shared/schema/mix/schemas/dash/get-dashboard-v1.ts create mode 100644 src/shared/schema/mix/schemas/dash/update-dashboard-v1.ts rename src/shared/schema/mix/types/{dash.ts => dash/index.ts} (61%) diff --git a/src/shared/schema/mix/actions/dash/create-dashboard-v1.ts b/src/shared/schema/mix/actions/dash/create-dashboard-v1.ts index 4c2b86cffa..29f43a6c15 100644 --- a/src/shared/schema/mix/actions/dash/create-dashboard-v1.ts +++ b/src/shared/schema/mix/actions/dash/create-dashboard-v1.ts @@ -2,15 +2,18 @@ import {getTypedApi} from '../../..'; import {EntryScope} from '../../../..'; import {Dash} from '../../../../../server/components/sdk'; import {createTypedAction} from '../../../gateway-utils'; -import {createDashArgsSchema, createDashResultSchema} from '../../schemas/dash'; -import type {DashV1} from '../../types'; +import { + createDashV1ArgsSchema, + createDashV1ResultSchema, +} from '../../schemas/dash/create-dashboard-v1'; +import type {CreateDashV1Result, DashV1} from '../../types'; export const createDashboardV1 = createTypedAction( { - paramsSchema: createDashArgsSchema, - resultSchema: createDashResultSchema, + paramsSchema: createDashV1ArgsSchema, + resultSchema: createDashV1ResultSchema, }, - async (api, args): Promise => { + async (api, args): Promise => { const typedApi = getTypedApi(api); const links = Dash.gatherLinks(args.data); diff --git a/src/shared/schema/mix/actions/dash/delete-dashboard.ts b/src/shared/schema/mix/actions/dash/delete-dashboard.ts index f00bc8768b..c845ac97f1 100644 --- a/src/shared/schema/mix/actions/dash/delete-dashboard.ts +++ b/src/shared/schema/mix/actions/dash/delete-dashboard.ts @@ -1,14 +1,15 @@ +import type {DeleteDashResult} from '../../..'; import {getTypedApi} from '../../..'; import {EntryScope} from '../../../..'; import {createTypedAction} from '../../../gateway-utils'; -import {deleteDashArgsSchema, deleteDashResultSchema} from '../../schemas/dash'; +import {deleteDashArgsSchema, deleteDashResultSchema} from '../../schemas/dash/delete-dashboard'; export const deleteDashboard = createTypedAction( { paramsSchema: deleteDashArgsSchema, resultSchema: deleteDashResultSchema, }, - async (api, {lockToken, dashboardId}): Promise => { + async (api, {lockToken, dashboardId}): Promise => { const typedApi = getTypedApi(api); await typedApi.us._deleteUSEntry({ diff --git a/src/shared/schema/mix/actions/dash/get-dashboard-v1.ts b/src/shared/schema/mix/actions/dash/get-dashboard-v1.ts index 12c9159341..afd536e940 100644 --- a/src/shared/schema/mix/actions/dash/get-dashboard-v1.ts +++ b/src/shared/schema/mix/actions/dash/get-dashboard-v1.ts @@ -1,44 +1,17 @@ -import z from 'zod'; - +import type {GetDashV1Result} from '../../..'; import {getTypedApi} from '../../..'; import {EntryScope} from '../../../..'; -import {registerComponentId} from '../../../../components/public-api/utils'; import {ServerError} from '../../../../constants/error'; -import {permissionsSchema} from '../../../../zod-schemas/permissions'; import {createTypedAction} from '../../../gateway-utils'; import {migrateDashToV1} from '../../helpers/dash/migrate-dash-to-v1'; -import {dashSchemaV1} from '../../schemas/dash'; - -export const getDashArgsSchema = z - .object({ - dashboardId: z.string(), - revId: z.string().optional(), - includePermissions: z.boolean().optional().default(false), - includeLinks: z.boolean().optional().default(false), - includeFavorite: z.boolean().optional().default(false), - branch: z.enum(['published', 'saved']).optional(), - workbookId: z.string().optional(), - }) - .openapi(registerComponentId('GetDashboardArgs'), { - title: 'GetDashboardArgs', - }); - -export const getDashResultSchema = z - .object({ - entry: dashSchemaV1, - isFavorite: z.boolean().optional(), - permissions: permissionsSchema.optional(), - }) - .openapi(registerComponentId('GetDashboardResult'), { - title: 'GetDashboardResult', - }); +import {getDashV1ArgsSchema, getDashV1ResultSchema} from '../../schemas/dash/get-dashboard-v1'; export const getDashboardV1 = createTypedAction( { - paramsSchema: getDashArgsSchema, - resultSchema: getDashResultSchema, + paramsSchema: getDashV1ArgsSchema, + resultSchema: getDashV1ResultSchema, }, - async (api, args): Promise => { + async (api, args): Promise => { const { dashboardId, includePermissions, diff --git a/src/shared/schema/mix/actions/dash/update-dashboard-v1.ts b/src/shared/schema/mix/actions/dash/update-dashboard-v1.ts index d47c5f0b5d..e6c5b077c4 100644 --- a/src/shared/schema/mix/actions/dash/update-dashboard-v1.ts +++ b/src/shared/schema/mix/actions/dash/update-dashboard-v1.ts @@ -3,20 +3,23 @@ import {EntryScope, EntryUpdateMode} from '../../../..'; import {Dash} from '../../../../../server/components/sdk'; import {ServerError} from '../../../../constants/error'; import {createTypedAction} from '../../../gateway-utils'; -import {updateDashArgsSchema, updateDashResultSchema} from '../../schemas/dash'; -import type {DashV1} from '../../types'; +import { + updateDashV1ArgsSchema, + updateDashV1ResultSchema, +} from '../../schemas/dash/update-dashboard-v1'; +import type {DashV1, UpdateDashV1Result} from '../../types'; const CURRENT_DASH_VERSION = 1 as const; export const updateDashboardV1 = createTypedAction( { - paramsSchema: updateDashArgsSchema, - resultSchema: updateDashResultSchema, + paramsSchema: updateDashV1ArgsSchema, + resultSchema: updateDashV1ResultSchema, }, async ( api, {entryId, mode = EntryUpdateMode.Publish, meta, data, revId, annotation}, - ): Promise => { + ): Promise => { const typedApi = getTypedApi(api); const savedEntry = await typedApi.us.getEntry({entryId}); diff --git a/src/shared/schema/mix/schemas/dash.ts b/src/shared/schema/mix/schemas/dash.ts deleted file mode 100644 index 848c751aa3..0000000000 --- a/src/shared/schema/mix/schemas/dash.ts +++ /dev/null @@ -1,76 +0,0 @@ -import z from 'zod'; - -import {EntryScope, EntryUpdateMode} from '../../..'; -import {dataSchema} from '../../../zod-schemas/dash'; - -export const deleteDashArgsSchema = z.strictObject({ - dashboardId: z.string(), - lockToken: z.string().optional(), -}); - -export const deleteDashResultSchema = z.object({}); - -export const updateDashArgsSchema = z.strictObject({ - key: z.string().min(1), - workbookId: z.string().optional(), - data: dataSchema, - meta: z.record(z.any(), z.any()), - entryId: z.string(), - revId: z.string(), - mode: z.enum(EntryUpdateMode), - annotation: z - .object({ - description: z.string(), - }) - .optional(), -}); - -export const createDashArgsSchema = z.strictObject({ - key: z.string().min(1), - data: dataSchema, - meta: z.record(z.string(), z.string()), - workbookId: z.string().optional(), - lockToken: z.string().optional(), - mode: z.enum(EntryUpdateMode), - annotation: z - .object({ - description: z.string(), - }) - .optional(), -}); - -export const dashSchemaV1 = z.object({ - annotation: z - .object({ - description: z.string().optional(), - }) - .nullable() - .optional(), - createdAt: z.string(), - createdBy: z.string(), - data: dataSchema, - entryId: z.string(), - hidden: z.boolean(), - key: z.union([z.null(), z.string()]), - links: z.record(z.string(), z.string()).nullable().optional(), - meta: z.record(z.string(), z.string()), - public: z.boolean(), - publishedId: z.string().nullable(), - revId: z.string(), - savedId: z.string(), - scope: z.literal(EntryScope.Dash), - tenantId: z.string(), - type: z.literal(''), - updatedAt: z.string(), - updatedBy: z.string(), - version: z.literal(1), - workbookId: z.union([z.null(), z.string()]), -}); - -export const createDashResultSchema = z.object({ - entry: dashSchemaV1, -}); - -export const updateDashResultSchema = z.object({ - entry: dashSchemaV1, -}); diff --git a/src/shared/schema/mix/schemas/dash/create-dashboard-v1.ts b/src/shared/schema/mix/schemas/dash/create-dashboard-v1.ts new file mode 100644 index 0000000000..2310c84d55 --- /dev/null +++ b/src/shared/schema/mix/schemas/dash/create-dashboard-v1.ts @@ -0,0 +1,24 @@ +import z from 'zod'; + +import {EntryUpdateMode} from '../../../..'; +import {dataSchema} from '../../../../zod-schemas/dash'; + +import {dashSchemaV1} from './dash-v1'; + +export const createDashV1ArgsSchema = z.strictObject({ + key: z.string().min(1), + data: dataSchema, + meta: z.record(z.string(), z.string()), + workbookId: z.string().optional(), + lockToken: z.string().optional(), + mode: z.enum(EntryUpdateMode), + annotation: z + .object({ + description: z.string(), + }) + .optional(), +}); + +export const createDashV1ResultSchema = z.object({ + entry: dashSchemaV1, +}); diff --git a/src/shared/schema/mix/schemas/dash/dash-v1.ts b/src/shared/schema/mix/schemas/dash/dash-v1.ts new file mode 100644 index 0000000000..a19cfbeb4c --- /dev/null +++ b/src/shared/schema/mix/schemas/dash/dash-v1.ts @@ -0,0 +1,32 @@ +import z from 'zod'; + +import {EntryScope} from '../../../..'; +import {dataSchema} from '../../../../zod-schemas/dash'; + +export const dashSchemaV1 = z.object({ + annotation: z + .object({ + description: z.string().optional(), + }) + .nullable() + .optional(), + createdAt: z.string(), + createdBy: z.string(), + data: dataSchema, + entryId: z.string(), + hidden: z.boolean(), + key: z.union([z.null(), z.string()]), + links: z.record(z.string(), z.string()).nullable().optional(), + meta: z.record(z.string(), z.string()), + public: z.boolean(), + publishedId: z.string().nullable(), + revId: z.string(), + savedId: z.string(), + scope: z.literal(EntryScope.Dash), + tenantId: z.string(), + type: z.literal(''), + updatedAt: z.string(), + updatedBy: z.string(), + version: z.literal(1), + workbookId: z.union([z.null(), z.string()]), +}); diff --git a/src/shared/schema/mix/schemas/dash/delete-dashboard.ts b/src/shared/schema/mix/schemas/dash/delete-dashboard.ts new file mode 100644 index 0000000000..d71c29f717 --- /dev/null +++ b/src/shared/schema/mix/schemas/dash/delete-dashboard.ts @@ -0,0 +1,8 @@ +import z from 'zod'; + +export const deleteDashArgsSchema = z.strictObject({ + dashboardId: z.string(), + lockToken: z.string().optional(), +}); + +export const deleteDashResultSchema = z.object({}); diff --git a/src/shared/schema/mix/schemas/dash/get-dashboard-v1.ts b/src/shared/schema/mix/schemas/dash/get-dashboard-v1.ts new file mode 100644 index 0000000000..7426315048 --- /dev/null +++ b/src/shared/schema/mix/schemas/dash/get-dashboard-v1.ts @@ -0,0 +1,30 @@ +import z from 'zod'; + +import {registerComponentId} from '../../../../components/public-api/utils'; +import {permissionsSchema} from '../../../../zod-schemas/permissions'; + +import {dashSchemaV1} from './dash-v1'; + +export const getDashV1ArgsSchema = z + .object({ + dashboardId: z.string(), + revId: z.string().optional(), + includePermissions: z.boolean().optional().default(false), + includeLinks: z.boolean().optional().default(false), + includeFavorite: z.boolean().optional().default(false), + branch: z.enum(['published', 'saved']).optional(), + workbookId: z.string().optional(), + }) + .openapi(registerComponentId('GetDashboardArgs'), { + title: 'GetDashboardArgs', + }); + +export const getDashV1ResultSchema = z + .object({ + entry: dashSchemaV1, + isFavorite: z.boolean().optional(), + permissions: permissionsSchema.optional(), + }) + .openapi(registerComponentId('GetDashboardResult'), { + title: 'GetDashboardResult', + }); diff --git a/src/shared/schema/mix/schemas/dash/update-dashboard-v1.ts b/src/shared/schema/mix/schemas/dash/update-dashboard-v1.ts new file mode 100644 index 0000000000..7d6fddd880 --- /dev/null +++ b/src/shared/schema/mix/schemas/dash/update-dashboard-v1.ts @@ -0,0 +1,25 @@ +import z from 'zod'; + +import {EntryUpdateMode} from '../../../..'; +import {dataSchema} from '../../../../zod-schemas/dash'; + +import {dashSchemaV1} from './dash-v1'; + +export const updateDashV1ArgsSchema = z.strictObject({ + key: z.string().min(1), + workbookId: z.string().optional(), + data: dataSchema, + meta: z.record(z.any(), z.any()), + entryId: z.string(), + revId: z.string(), + mode: z.enum(EntryUpdateMode), + annotation: z + .object({ + description: z.string(), + }) + .optional(), +}); + +export const updateDashV1ResultSchema = z.object({ + entry: dashSchemaV1, +}); diff --git a/src/shared/schema/mix/types/dash.ts b/src/shared/schema/mix/types/dash/index.ts similarity index 61% rename from src/shared/schema/mix/types/dash.ts rename to src/shared/schema/mix/types/dash/index.ts index 68834b9c25..187ff98b32 100644 --- a/src/shared/schema/mix/types/dash.ts +++ b/src/shared/schema/mix/types/dash/index.ts @@ -1,9 +1,13 @@ import type z from 'zod'; -import type {WizardVisualizationId} from '../../../constants'; -import type {ChartsStats, DashStats, WorkbookId} from '../../../types'; -import type {GetEntriesEntryResponse} from '../../us/types'; -import type {createDashResultSchema, dashSchemaV1, updateDashResultSchema} from '../schemas/dash'; +import type {WizardVisualizationId} from '../../../../constants'; +import type {ChartsStats, DashStats, WorkbookId} from '../../../../types'; +import type {GetEntriesEntryResponse} from '../../../us/types'; +import type {createDashV1ResultSchema} from '../../schemas/dash/create-dashboard-v1'; +import type {dashSchemaV1} from '../../schemas/dash/dash-v1'; +import type {deleteDashResultSchema} from '../../schemas/dash/delete-dashboard'; +import type {getDashV1ResultSchema} from '../../schemas/dash/get-dashboard-v1'; +import type {updateDashV1ResultSchema} from '../../schemas/dash/update-dashboard-v1'; export type CollectDashStatsResponse = { status: string; @@ -55,8 +59,12 @@ export type GetWidgetsDatasetsFieldsArgs = { workbookId: WorkbookId; }; -export type UpdateDashResponse = z.infer; +export type UpdateDashV1Result = z.infer; -export type CreateDashResponse = z.infer; +export type CreateDashV1Result = z.infer; + +export type DeleteDashResult = z.infer; + +export type GetDashV1Result = z.infer; export type DashV1 = z.infer; From 88befce49a0075e1ca89ec6db55eea235e487636 Mon Sep 17 00:00:00 2001 From: Vladimir Stepanenko Date: Mon, 1 Dec 2025 15:28:30 +0300 Subject: [PATCH 18/21] Fix --- src/shared/schema/mix/helpers/__tests__/editor.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shared/schema/mix/helpers/__tests__/editor.test.ts b/src/shared/schema/mix/helpers/__tests__/editor.test.ts index b48733db7b..3cc2a8878b 100644 --- a/src/shared/schema/mix/helpers/__tests__/editor.test.ts +++ b/src/shared/schema/mix/helpers/__tests__/editor.test.ts @@ -1,6 +1,6 @@ import {getEntryLinks, validateData} from '../editor'; -describe('shared/schema/mix/utils/validateData', () => { +describe('shared/schema/mix/helpers/validateData', () => { test.each([ { name: 'valid meta with links', @@ -64,7 +64,7 @@ describe('shared/schema/mix/utils/validateData', () => { }); }); -describe('shared/schema/mix/utils/getEntryLinks', () => { +describe('shared/schema/mix/helpers/getEntryLinks', () => { test.each([ { name: 'returns empty object when no meta', From 3efd8e9d43492c8f636f27658cea86dfc394bf7c Mon Sep 17 00:00:00 2001 From: Vladimir Stepanenko Date: Mon, 1 Dec 2025 16:45:29 +0300 Subject: [PATCH 19/21] Fix --- src/server/nodekit.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/server/nodekit.ts b/src/server/nodekit.ts index 4e86f50a75..ff91fedb36 100644 --- a/src/server/nodekit.ts +++ b/src/server/nodekit.ts @@ -1,7 +1,11 @@ import * as path from 'path'; +import {extendZodWithOpenApi} from '@asteasolutions/zod-to-openapi'; import {NodeKit} from '@gravity-ui/nodekit'; import dotenv from 'dotenv'; +import {z} from 'zod'; + +extendZodWithOpenApi(z); dotenv.config(); import {authSchema, schema} from '../shared/schema'; From 1833426a636b27296daef22c2624a2446571c4e1 Mon Sep 17 00:00:00 2001 From: Vladimir Stepanenko Date: Mon, 1 Dec 2025 17:19:07 +0300 Subject: [PATCH 20/21] Fix --- .../mix/actions/dash/create-dashboard-v1.ts | 54 +++++++++++++++++-- .../mix/actions/dash/update-dashboard-v1.ts | 7 ++- .../mix/helpers/dash/migrate-dash-to-v1.ts | 10 ++-- src/shared/schema/mix/schemas/dash/dash-v1.ts | 4 +- 4 files changed, 63 insertions(+), 12 deletions(-) diff --git a/src/shared/schema/mix/actions/dash/create-dashboard-v1.ts b/src/shared/schema/mix/actions/dash/create-dashboard-v1.ts index 29f43a6c15..36842067aa 100644 --- a/src/shared/schema/mix/actions/dash/create-dashboard-v1.ts +++ b/src/shared/schema/mix/actions/dash/create-dashboard-v1.ts @@ -1,3 +1,6 @@ +import type {AppContext} from '@gravity-ui/nodekit'; +import Hashids from 'hashids'; + import {getTypedApi} from '../../..'; import {EntryScope} from '../../../..'; import {Dash} from '../../../../../server/components/sdk'; @@ -6,22 +9,65 @@ import { createDashV1ArgsSchema, createDashV1ResultSchema, } from '../../schemas/dash/create-dashboard-v1'; +import {DASH_VERSION_1} from '../../schemas/dash/dash-v1'; import type {CreateDashV1Result, DashV1} from '../../types'; +const DEFAULT_COUNTER_VALUE = 2; + +const DEFAULT_SETTINGS = { + autoupdateInterval: null, + maxConcurrentRequests: null, + silentLoading: false, + dependentSelectors: true, + hideTabs: false, + hideDashTitle: false, + expandTOC: false, +}; + +const getDefaultTabs = ({ctx, salt}: {ctx: AppContext; salt: string}) => { + const I18n = ctx.get('i18n'); + const i18n = I18n.keyset('dash.tabs-dialog.edit'); + + const hashids = new Hashids(salt); + + return [ + { + id: hashids.encode(1), + title: i18n('value_default', {index: 1}), + items: [], + layout: [], + aliases: {}, + connections: [], + }, + ]; +}; + export const createDashboardV1 = createTypedAction( { paramsSchema: createDashV1ArgsSchema, resultSchema: createDashV1ResultSchema, }, - async (api, args): Promise => { + async (api, args, {ctx}): Promise => { const typedApi = getTypedApi(api); - const links = Dash.gatherLinks(args.data); + const salt = args.data.salt ?? Math.random().toString(); + + const data = { + ...args.data, + counter: args.data.counter ?? DEFAULT_COUNTER_VALUE, + salt, + tabs: args.data.tabs ?? getDefaultTabs({ctx, salt}), + settings: args.data.settings ?? DEFAULT_SETTINGS, + schemeVersion: 8, + }; + + const links = Dash.gatherLinks(data); - Dash.validateData(args.data); + Dash.validateData(data); const createEntryResult = await typedApi.us._createEntry({ ...args, + data, type: '', scope: EntryScope.Dash, links, @@ -29,7 +75,7 @@ export const createDashboardV1 = createTypedAction( return { entry: { - version: 1 as const, + version: DASH_VERSION_1, data: createEntryResult.data as DashV1['data'], meta: createEntryResult.meta as DashV1['meta'], scope: createEntryResult.scope as EntryScope.Dash, diff --git a/src/shared/schema/mix/actions/dash/update-dashboard-v1.ts b/src/shared/schema/mix/actions/dash/update-dashboard-v1.ts index e6c5b077c4..99c9b17869 100644 --- a/src/shared/schema/mix/actions/dash/update-dashboard-v1.ts +++ b/src/shared/schema/mix/actions/dash/update-dashboard-v1.ts @@ -3,14 +3,13 @@ import {EntryScope, EntryUpdateMode} from '../../../..'; import {Dash} from '../../../../../server/components/sdk'; import {ServerError} from '../../../../constants/error'; import {createTypedAction} from '../../../gateway-utils'; +import {DASH_VERSION_1} from '../../schemas/dash/dash-v1'; import { updateDashV1ArgsSchema, updateDashV1ResultSchema, } from '../../schemas/dash/update-dashboard-v1'; import type {DashV1, UpdateDashV1Result} from '../../types'; -const CURRENT_DASH_VERSION = 1 as const; - export const updateDashboardV1 = createTypedAction( { paramsSchema: updateDashV1ArgsSchema, @@ -30,7 +29,7 @@ export const updateDashboardV1 = createTypedAction( }); } - if (savedEntry.version && savedEntry.version > CURRENT_DASH_VERSION) { + if (savedEntry.version && savedEntry.version > DASH_VERSION_1) { throw new ServerError( `The entry was created or updated using a newer API version and cannot be modified through this API version. Entry version is: ${savedEntry.version}`, @@ -59,7 +58,7 @@ export const updateDashboardV1 = createTypedAction( return { entry: { - version: CURRENT_DASH_VERSION, + version: DASH_VERSION_1, data: updateEntryResult.data as DashV1['data'], meta: updateEntryResult.meta as DashV1['meta'], scope: updateEntryResult.scope as EntryScope.Dash, diff --git a/src/shared/schema/mix/helpers/dash/migrate-dash-to-v1.ts b/src/shared/schema/mix/helpers/dash/migrate-dash-to-v1.ts index c23e1274b9..9fdae7836a 100644 --- a/src/shared/schema/mix/helpers/dash/migrate-dash-to-v1.ts +++ b/src/shared/schema/mix/helpers/dash/migrate-dash-to-v1.ts @@ -2,6 +2,7 @@ import type {DashData} from '../../../..'; import {DashSchemeConverter} from '../../../..'; import {ServerError} from '../../../../constants/error'; import type {DashV1, GetEntryResponse} from '../../../types'; +import {DASH_VERSION_1} from '../../schemas/dash/dash-v1'; export const migrateDashToV1 = ({ version, @@ -25,7 +26,7 @@ export const migrateDashToV1 = ({ annotation, links, }: GetEntryResponse): DashV1 => { - if (version && version > 1) { + if (version && version > DASH_VERSION_1) { throw new ServerError(`Unsupported dashboard version: ${version}`, { status: 500, }); @@ -46,14 +47,17 @@ export const migrateDashToV1 = ({ let migratedData: DashData; - if (version === 1 || !DashSchemeConverter.isUpdateNeeded(data as unknown as DashData)) { + if ( + version === DASH_VERSION_1 || + !DashSchemeConverter.isUpdateNeeded(data as unknown as DashData) + ) { migratedData = data as unknown as DashData; } else { migratedData = DashSchemeConverter.update(data as unknown as DashData); } return { - version: 1, + version: DASH_VERSION_1, scope, entryId, key, diff --git a/src/shared/schema/mix/schemas/dash/dash-v1.ts b/src/shared/schema/mix/schemas/dash/dash-v1.ts index a19cfbeb4c..5ae5caa5e4 100644 --- a/src/shared/schema/mix/schemas/dash/dash-v1.ts +++ b/src/shared/schema/mix/schemas/dash/dash-v1.ts @@ -3,6 +3,8 @@ import z from 'zod'; import {EntryScope} from '../../../..'; import {dataSchema} from '../../../../zod-schemas/dash'; +export const DASH_VERSION_1 = 1; + export const dashSchemaV1 = z.object({ annotation: z .object({ @@ -27,6 +29,6 @@ export const dashSchemaV1 = z.object({ type: z.literal(''), updatedAt: z.string(), updatedBy: z.string(), - version: z.literal(1), + version: z.literal(DASH_VERSION_1), workbookId: z.union([z.null(), z.string()]), }); From bab53b118106a74b9cc38b0ebcd46fadc598bbb6 Mon Sep 17 00:00:00 2001 From: Vladimir Stepanenko Date: Mon, 1 Dec 2025 17:20:46 +0300 Subject: [PATCH 21/21] Fix --- src/shared/schema/mix/schemas/dash/get-dashboard-v1.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/shared/schema/mix/schemas/dash/get-dashboard-v1.ts b/src/shared/schema/mix/schemas/dash/get-dashboard-v1.ts index 7426315048..7d0f7ed804 100644 --- a/src/shared/schema/mix/schemas/dash/get-dashboard-v1.ts +++ b/src/shared/schema/mix/schemas/dash/get-dashboard-v1.ts @@ -9,9 +9,9 @@ export const getDashV1ArgsSchema = z .object({ dashboardId: z.string(), revId: z.string().optional(), - includePermissions: z.boolean().optional().default(false), - includeLinks: z.boolean().optional().default(false), - includeFavorite: z.boolean().optional().default(false), + includePermissions: z.boolean().optional(), + includeLinks: z.boolean().optional(), + includeFavorite: z.boolean().optional(), branch: z.enum(['published', 'saved']).optional(), workbookId: z.string().optional(), })