diff --git a/src/server/components/public-api/config/v0.ts b/src/server/components/public-api/config/v0.ts index 8754d0da51..6f6975836d 100644 --- a/src/server/components/public-api/config/v0.ts +++ b/src/server/components/public-api/config/v0.ts @@ -113,7 +113,7 @@ export const getPublicApiActionsV0 = < // Dashboard getDashboard: { - resolve: (api) => api.mix.__getDashboard__, + resolve: (api) => api.mix.getDashboardV1, openApi: { summary: 'Get dashboard', tags: [ApiTag.Dashboard], @@ -121,7 +121,7 @@ export const getPublicApiActionsV0 = < }, }, createDashboard: { - resolve: (api) => api.mix.__createDashboard__, + resolve: (api) => api.mix.createDashboardV1, openApi: { summary: 'Create dashboard', tags: [ApiTag.Dashboard], @@ -129,7 +129,7 @@ export const getPublicApiActionsV0 = < }, }, updateDashboard: { - resolve: (api) => api.mix.__updateDashboard__, + resolve: (api) => api.mix.updateDashboardV1, openApi: { summary: 'Update dashboard', tags: [ApiTag.Dashboard], @@ -137,7 +137,7 @@ export const getPublicApiActionsV0 = < }, }, deleteDashboard: { - resolve: (api) => api.mix._deleteDashboard, + resolve: (api) => api.mix.deleteDashboard, openApi: { summary: 'Delete dashboard', tags: [ApiTag.Dashboard], @@ -209,7 +209,7 @@ export const getPublicApiActionsV0 = < }, }, deleteQLChart: { - resolve: (api) => api.mix._deleteQLChart, + resolve: (api) => api.mix.deleteQLChart, openApi: { summary: 'Delete QL chart', tags: [ApiTag.QL], @@ -226,7 +226,7 @@ export const getPublicApiActionsV0 = < }, }, deleteWizardChart: { - resolve: (api) => api.mix._deleteWizardChart, + resolve: (api) => api.mix.deleteWizardChart, openApi: { summary: 'Delete wizard chart', tags: [ApiTag.Wizard], diff --git a/src/server/components/sdk/dash.ts b/src/server/components/sdk/dash.ts index 3b56d02552..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(); @@ -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/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'; 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-v1.ts b/src/shared/schema/mix/actions/dash/create-dashboard-v1.ts new file mode 100644 index 0000000000..2240acbf20 --- /dev/null +++ b/src/shared/schema/mix/actions/dash/create-dashboard-v1.ts @@ -0,0 +1,121 @@ +import type {AppContext} from '@gravity-ui/nodekit'; +import Hashids from 'hashids'; + +import {getTypedApi} from '../../..'; +import {DASH_CURRENT_SCHEME_VERSION, EntryScope} from '../../../..'; +import {Dash} from '../../../../../server/components/sdk'; +import {createTypedAction} from '../../../gateway-utils'; +import {DEFAULT_SETTINGS} from '../../constants/dash'; +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 processTabs = ({ + ctx, + salt = Math.random().toString(), + tabs, +}: { + ctx: AppContext; + salt?: string; + tabs?: DashV1['data']['tabs']; +}): { + salt: string; + counter: number; + tabs: DashV1['data']['tabs']; +} => { + if (!tabs) { + const I18n = ctx.get('i18n'); + const i18n = I18n.keyset('dash.tabs-dialog.edit'); + + const hashids = new Hashids(salt); + + return { + salt, + counter: 2, + tabs: [ + { + id: hashids.encode(1), + title: i18n('value_default', {index: 1}), + items: [], + layout: [], + aliases: {}, + connections: [], + }, + ], + }; + } + + const counter = tabs.reduce((acc, tab) => { + return acc + 1 + (tab.items?.length ?? 0); // + 1 tabId + n items ids + }, 0); + + return { + salt, + counter, + tabs, + }; +}; + +export const createDashboardV1 = createTypedAction( + { + paramsSchema: createDashV1ArgsSchema, + resultSchema: createDashV1ResultSchema, + }, + async (api, args, {ctx}): Promise => { + const typedApi = getTypedApi(api); + + const argsEntry = args.entry; + const argsData = argsEntry.data; + + const data = { + ...argsData, + ...processTabs({ctx, salt: argsData.salt, tabs: argsData.tabs}), + settings: argsData.settings ?? DEFAULT_SETTINGS, + schemeVersion: DASH_CURRENT_SCHEME_VERSION, + }; + + const links = Dash.gatherLinks(data); + + Dash.validateData(data); + + const createEntryResult = await typedApi.us._createEntry({ + key: argsEntry.key, + meta: argsEntry.meta, + workbookId: argsEntry.workbookId, + annotation: argsEntry.annotation, + mode: args.mode, + data, + type: '', + scope: EntryScope.Dash, + links, + }); + + return { + entry: { + version: DASH_VERSION_1, + 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/delete-dashboard.ts b/src/shared/schema/mix/actions/dash/delete-dashboard.ts new file mode 100644 index 0000000000..c845ac97f1 --- /dev/null +++ b/src/shared/schema/mix/actions/dash/delete-dashboard.ts @@ -0,0 +1,23 @@ +import type {DeleteDashResult} from '../../..'; +import {getTypedApi} from '../../..'; +import {EntryScope} from '../../../..'; +import {createTypedAction} from '../../../gateway-utils'; +import {deleteDashArgsSchema, deleteDashResultSchema} from '../../schemas/dash/delete-dashboard'; + +export const deleteDashboard = createTypedAction( + { + paramsSchema: deleteDashArgsSchema, + resultSchema: deleteDashResultSchema, + }, + async (api, {lockToken, dashboardId}): Promise => { + 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-v1.ts b/src/shared/schema/mix/actions/dash/get-dashboard-v1.ts new file mode 100644 index 0000000000..afd536e940 --- /dev/null +++ b/src/shared/schema/mix/actions/dash/get-dashboard-v1.ts @@ -0,0 +1,51 @@ +import type {GetDashV1Result} from '../../..'; +import {getTypedApi} from '../../..'; +import {EntryScope} from '../../../..'; +import {ServerError} from '../../../../constants/error'; +import {createTypedAction} from '../../../gateway-utils'; +import {migrateDashToV1} from '../../helpers/dash/migrate-dash-to-v1'; +import {getDashV1ArgsSchema, getDashV1ResultSchema} from '../../schemas/dash/get-dashboard-v1'; + +export const getDashboardV1 = createTypedAction( + { + paramsSchema: getDashV1ArgsSchema, + resultSchema: getDashV1ResultSchema, + }, + async (api, args): Promise => { + 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 70% rename from src/shared/schema/mix/actions/dash.ts rename to src/shared/schema/mix/actions/dash/index.ts index 0865878a3b..81488d8381 100644 --- a/src/shared/schema/mix/actions/dash.ts +++ b/src/shared/schema/mix/actions/dash/index.ts @@ -1,42 +1,38 @@ import type {DeepNonNullable} from 'utility-types'; -import {getTypedApi} from '../..'; -import {EntryScope} from '../../..'; -import Dash from '../../../../server/components/sdk/dash'; -import type {ChartsStats} from '../../../types/charts'; -import {createAction, createTypedAction} from '../../gateway-utils'; -import {getEntryVisualizationType} from '../helpers'; -import type {DatasetDictResponse, DatasetFieldsDictResponse} from '../helpers/dash'; +import {getTypedApi} from '../../..'; +import type {ChartsStats} from '../../../../types/charts'; +import {createAction} from '../../../gateway-utils'; +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'; +} from '../../helpers/dash'; import type { CollectChartkitStatsArgs, CollectChartkitStatsResponse, CollectDashStatsArgs, CollectDashStatsResponse, - CreateDashResponse, GetEntriesDatasetsFieldsArgs, GetEntriesDatasetsFieldsResponse, GetWidgetsDatasetsFieldsArgs, GetWidgetsDatasetsFieldsResponse, - UpdateDashResponse, -} from '../types'; +} from '../../types'; + +import {createDashboardV1} from './create-dashboard-v1'; +import {deleteDashboard} from './delete-dashboard'; +import {getDashboardV1} from './get-dashboard-v1'; +import {updateDashboardV1} from './update-dashboard-v1'; export const dashActions = { + getDashboardV1, + createDashboardV1, + updateDashboardV1, + deleteDashboard, + collectDashStats: createAction( async (_, args, {ctx}): Promise => { ctx.stats('dashStats', { @@ -209,86 +205,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-v1.ts b/src/shared/schema/mix/actions/dash/update-dashboard-v1.ts new file mode 100644 index 0000000000..44e96bf8e5 --- /dev/null +++ b/src/shared/schema/mix/actions/dash/update-dashboard-v1.ts @@ -0,0 +1,74 @@ +import {getTypedApi} from '../../..'; +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'; + +export const updateDashboardV1 = createTypedAction( + { + paramsSchema: updateDashV1ArgsSchema, + resultSchema: updateDashV1ResultSchema, + }, + async ( + api, + {mode = EntryUpdateMode.Publish, entry: {entryId, meta, data, revId, annotation}}, + ): Promise => { + const typedApi = getTypedApi(api); + + const {entry: savedDash} = await typedApi.mix.getDashboardV1({dashboardId: entryId}); + + if (savedDash.scope !== EntryScope.Dash) { + throw new ServerError('Entry not found', { + status: 404, + }); + } + + 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, + annotation, + skipSyncLinks, + }); + + return { + entry: { + version: DASH_VERSION_1, + 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/ql.ts b/src/shared/schema/mix/actions/ql.ts index c38ecfbfd9..c856c3f1cd 100644 --- a/src/shared/schema/mix/actions/ql.ts +++ b/src/shared/schema/mix/actions/ql.ts @@ -53,7 +53,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 839ca7e77f..fd80c0c0e6 100644 --- a/src/shared/schema/mix/actions/wizard.ts +++ b/src/shared/schema/mix/actions/wizard.ts @@ -61,7 +61,7 @@ export const wizardActions = { }, ), - _deleteWizardChart: createTypedAction( + deleteWizardChart: createTypedAction( { paramsSchema: deleteWizardChartArgsSchema, resultSchema: deleteWizardChartResultSchema, diff --git a/src/shared/schema/mix/constants/dash.ts b/src/shared/schema/mix/constants/dash.ts new file mode 100644 index 0000000000..bc5f7f9866 --- /dev/null +++ b/src/shared/schema/mix/constants/dash.ts @@ -0,0 +1,9 @@ +export const DEFAULT_SETTINGS = { + autoupdateInterval: null, + maxConcurrentRequests: null, + silentLoading: false, + dependentSelectors: true, + hideTabs: false, + hideDashTitle: false, + expandTOC: false, +}; diff --git a/src/shared/schema/mix/helpers/dash.ts b/src/shared/schema/mix/helpers/dash/index.ts similarity index 90% rename from src/shared/schema/mix/helpers/dash.ts rename to src/shared/schema/mix/helpers/dash/index.ts index e3082dcca3..145972abfd 100644 --- a/src/shared/schema/mix/helpers/dash.ts +++ b/src/shared/schema/mix/helpers/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 type {schema} from '../..'; -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 {GetEntryResponse} from '../../us/types'; +import type {schema} from '../../..'; +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 {GetEntryResponse} from '../../../us/types'; export type DatasetDictResponse = {datasetId: string; data: GetEntryResponse | null}; 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 new file mode 100644 index 0000000000..9fdae7836a --- /dev/null +++ b/src/shared/schema/mix/helpers/dash/migrate-dash-to-v1.ts @@ -0,0 +1,81 @@ +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, + 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 > DASH_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 === 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: DASH_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/schemas/dash.ts b/src/shared/schema/mix/schemas/dash.ts deleted file mode 100644 index 46806e2c96..0000000000 --- a/src/shared/schema/mix/schemas/dash.ts +++ /dev/null @@ -1,67 +0,0 @@ -import z from 'zod'; - -import {EntryScope, EntryUpdateMode} from '../../..'; -import {dashSchema, dataSchema} from '../../../zod-schemas/dash'; - -export const deleteDashArgsSchema = z.strictObject({ - dashboardId: z.string(), - lockToken: z.string().optional(), -}); - -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(), - 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), -}); - -export const updateDashResultSchema = dashUsSchema; - -export const createDashArgsSchema = z.strictObject({ - key: z.string().min(1), - data: dataSchema, - meta: z.record(z.any(), z.any()).optional(), - links: z.record(z.string(), z.string()).optional(), - workbookId: z.string().optional(), - lockToken: z.string().optional(), - mode: z.enum(EntryUpdateMode), -}); - -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 getDashResultSchema = dashUsSchema; 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..b2faf91f74 --- /dev/null +++ b/src/shared/schema/mix/schemas/dash/create-dashboard-v1.ts @@ -0,0 +1,29 @@ +import z from 'zod'; + +import {EntryUpdateMode} from '../../../..'; +import {dataSchema} from '../../../../zod-schemas/dash'; + +import {dashSchemaV1} from './dash-v1'; + +const createDashData = dataSchema + .partial({tabs: true, salt: true, settings: true}) + .omit({schemeVersion: true, counter: true}); + +export const createDashV1ArgsSchema = z.strictObject({ + entry: z.strictObject({ + key: z.string().min(1), + data: createDashData, + meta: z.record(z.string(), z.string()).nullable(), + workbookId: z.string().optional(), + annotation: z + .object({ + description: z.string(), + }) + .optional(), + }), + mode: z.enum(EntryUpdateMode), +}); + +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..d2cb2c0fd3 --- /dev/null +++ b/src/shared/schema/mix/schemas/dash/dash-v1.ts @@ -0,0 +1,34 @@ +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({ + 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()).nullable(), + 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(DASH_VERSION_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..7d0f7ed804 --- /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(), + includeLinks: z.boolean().optional(), + includeFavorite: z.boolean().optional(), + 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..72a3e8446f --- /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({ + entry: z.strictObject({ + entryId: z.string(), + data: dataSchema, + meta: z.record(z.string(), z.string()).nullable(), + revId: z.string().optional(), + annotation: z + .object({ + description: z.string(), + }) + .optional(), + }), + mode: z.enum(EntryUpdateMode), +}); + +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 59% rename from src/shared/schema/mix/types/dash.ts rename to src/shared/schema/mix/types/dash/index.ts index cd1c9f8e10..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, 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,6 +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; 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 3fbcf85d06..b5166f3112 100644 --- a/src/shared/schema/us/actions/entries/index.ts +++ b/src/shared/schema/us/actions/entries/index.ts @@ -52,15 +52,19 @@ import type { SwitchPublicationStatusResponse, } from '../../types'; +import {_createEntry} from './create-entry'; import {getEntries} from './get-entries'; import {getEntriesRelations} from './get-entries-relations'; 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, getEntriesRelations, listDirectory, getEntries, 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 6644700806..0d9304d526 100644 --- a/src/shared/schema/us/types/entries.ts +++ b/src/shared/schema/us/types/entries.ts @@ -1,6 +1,12 @@ import type z from 'zod'; -import type {CollectionItemEntities, EntryScope, WorkbookId} from '../../..'; +import type { + CollectionItemEntities, + EntryAnnotationArgs, + EntryScope, + EntryUpdateMode, + WorkbookId, +} from '../../..'; import type {Permissions} from '../../../types'; import type { getEntriesEntryResponseSchema, @@ -16,6 +22,7 @@ import type {EntriesCommonArgs} from './common'; import type { EntryFieldData, EntryFieldLinks, + EntryFieldMeta, EntryFields, EntryMetaFields, EntryNavigationFields, @@ -268,6 +275,40 @@ export interface GetEntriesAnnotationArgs { 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; +} + export interface EntityBindingsResponse { sourceId: string; targetId: string; 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(), -}); 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; }