diff --git a/frontend/src/lib/components/workspaceSettings/projectMigrations.test.ts b/frontend/src/lib/components/workspaceSettings/projectMigrations.test.ts index 631cef8c55..e0deebca6f 100644 --- a/frontend/src/lib/components/workspaceSettings/projectMigrations.test.ts +++ b/frontend/src/lib/components/workspaceSettings/projectMigrations.test.ts @@ -92,6 +92,36 @@ describe('detectDatatableTables', () => { // public-schema ref keeps the bare name; non-public keeps schema.table. expect([...(usage.get('main') ?? [])].sort()).toEqual(['app1.orders', 'customers']) }) + + it('reads the DB-table component config of a low-code app', async () => { + const appValue = { + grid: [ + { + data: { + type: 'dbexplorermulticomponent', + configuration: { + type: { + type: 'oneOf', + selected: 'datatable', + configuration: { + postgresql: { + resource: { type: 'static', value: '' }, + table: { type: 'static', value: undefined } + }, + datatable: { + datatable: { type: 'static', value: 'datatable://main' }, + table: { type: 'static', value: 'customers' } + } + } + } + } + } + } + ] + } + const usage = await detectDatatableTables([{ kind: 'app', path: 'f/p/app', value: appValue }]) + expect([...(usage.get('main') ?? [])]).toEqual(['customers']) + }) }) describe('generateDatatableMigrations', () => { diff --git a/frontend/src/lib/components/workspaceSettings/projectMigrations.ts b/frontend/src/lib/components/workspaceSettings/projectMigrations.ts index 9ff80c32eb..3eebf4e5ea 100644 --- a/frontend/src/lib/components/workspaceSettings/projectMigrations.ts +++ b/frontend/src/lib/components/workspaceSettings/projectMigrations.ts @@ -59,6 +59,32 @@ function addUsage(map: Map>, path: string): void { addDatatableTable(map, datatable, table) } +// Low-code apps declare data table usage explicitly on the DB-table component: +// a `oneOf` `type` config with `selected === 'datatable'` holding the data table +// (as `datatable://`) and the table. Walk the app value for those instead +// of parsing assets (low-code inline scripts don't carry a persisted asset list). +function collectAppDatatableConfigs(node: any, map: Map>): void { + if (node == null || typeof node !== 'object') return + if (Array.isArray(node)) { + for (const v of node) collectAppDatatableConfigs(v, map) + return + } + if (node.selected === 'datatable' && node.configuration?.datatable) { + const dtRaw = node.configuration.datatable.datatable?.value + const tableRaw = node.configuration.datatable.table?.value + if (typeof dtRaw === 'string' && dtRaw.trim() !== '') { + const datatable = dtRaw + .trim() + .replace(/^\$res:/, '') + .replace(/^datatable:\/\//, '') + const table = + typeof tableRaw === 'string' && tableRaw.trim() !== '' ? tableRaw.trim() : undefined + addDatatableTable(map, datatable, table) + } + } + for (const k of Object.keys(node)) collectAppDatatableConfigs(node[k], map) +} + /** * Scan a project's fetched items for data table usage and return * `datatable -> set of table refs` (a table ref is `table` or `schema.table`). @@ -66,6 +92,7 @@ function addUsage(map: Map>, path: string): void { * - flows: read each module's stored `assets` * - full-code (raw) apps: read the explicit `data.tables` declaration; fall back * to `runnables[key].inlineScript.assets` for older apps + * - low-code apps: read the DB-table component's explicit datatable/table config */ export async function detectDatatableTables( items: FetchedItem[] @@ -116,6 +143,8 @@ export async function detectDatatableTables( for (const a of assets) if (a?.kind === 'datatable' && typeof a.path === 'string') addUsage(map, a.path) } + } else if (item.kind === 'app') { + collectAppDatatableConfigs(item.value, map) } } return map