From fa052a0052b7654c68723bf8bb33722eda22156e Mon Sep 17 00:00:00 2001 From: ThearchyHelios Date: Sun, 4 Oct 2026 02:25:12 +0800 Subject: [PATCH] =?UTF-8?q?=E6=95=B0=E6=8D=AE=E7=9B=AE=E5=BD=95=20F2b=20/?= =?UTF-8?q?=20C2=EF=BC=9A=E7=89=88=E6=9C=AC=E5=8E=86=E5=8F=B2=E7=9A=84?= =?UTF-8?q?=E3=80=8C=E5=8F=91=E5=B8=83=E6=97=B6=E7=9A=84=E7=9B=AE=E5=BD=95?= =?UTF-8?q?=E7=89=88=E6=9C=AC=E3=80=8D=E6=8C=89=E8=BF=90=E8=A1=8C=E6=A8=AA?= =?UTF-8?q?=E5=B9=85=E7=9A=84=E5=86=99=E6=B3=95=E5=88=97=E6=AF=8F=E5=BC=A0?= =?UTF-8?q?=E8=A1=A8=EF=BC=9A=E3=80=8C=E6=95=B0=E6=8D=AE=E6=BA=90=E3=80=8D?= =?UTF-8?q?=E4=B8=AD=E6=96=87=E5=90=8D=EF=BC=88=E8=A1=A8=E5=90=8D=EF=BC=89?= =?UTF-8?q?=EF=BC=8C=E6=B2=A1=E6=94=B9=E8=BF=87=E7=9A=84=E8=A1=A8=E4=B9=9F?= =?UTF-8?q?=E5=86=99=E4=B8=AD=E6=96=87=E5=90=8D=EF=BC=88=E6=8C=89=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E6=BA=90=E7=9A=84=E7=9B=AE=E5=BD=95=E6=B8=85=E5=8D=95?= =?UTF-8?q?=E8=A1=A5=EF=BC=8C=E5=8F=96=E4=B8=8D=E5=88=B0=E6=97=B6=E5=8F=AA?= =?UTF-8?q?=E5=86=99=E8=A1=A8=E5=90=8D=EF=BC=89=EF=BC=9B=E8=A1=A5=E6=A3=80?= =?UTF-8?q?=E6=9F=A5=E8=84=9A=E6=9C=AC=E7=9A=84=E6=96=AD=E8=A8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/canvas/VersionsSheet.tsx | 44 +++++++++++++++++++++++++-- scripts/check-studio.mjs | 12 ++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/frontend/src/canvas/VersionsSheet.tsx b/frontend/src/canvas/VersionsSheet.tsx index 27ce45e3..36e15df5 100644 --- a/frontend/src/canvas/VersionsSheet.tsx +++ b/frontend/src/canvas/VersionsSheet.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from 'react' +import { useEffect, useMemo, useRef, useState } from 'react' import { ChevronLeft, History, RotateCcw, ShieldCheck } from 'lucide-react' import clsx from 'clsx' import { api } from '../api/client' @@ -6,6 +6,7 @@ import { EDIT_LOCK_TEXT, diffGraphs, toFlow, useEditLock, useStudio, type EditLock, type FlowNode, type GraphDiff, } from '../store/studio' import { ErrorState, Skeleton, toast } from '../components/ui' +import { useDatasources } from '../store/catalog' import { formatDateTime, formatTime } from '../lib/format' import { CATALOG_DRIFT_TEXT, WORKFLOW_STATUS_LABEL } from '../lib/terms' import { hintOf } from './shortcuts' @@ -218,11 +219,15 @@ const DIFF_COLOR = { add: 'var(--ok)', del: 'var(--err)', mod: 'var(--warn)' } a * 发布时的目录版本:这一版用到的表在发布时是数据目录的第几版,之后改过的标出来——下次从这一版发起正式运行时会 * 提醒「数据目录有变化」,这里先让人看到是哪几张表。 * + * 每张表和运行横幅同一个写法:「数据源」中文名(表名)。发布记录里只有数据源名和表名,中文名按数据源的目录清单补 + * (变了的表服务端带着现在的中文名);取不到清单时只写表名。 + * * 两组分开列:SQL 里写着的表在前;Agent 可能查询的表(Agent 绑定了数据源的查询工具,发布时记下数据源中所有有 * 目录的表)在小标题下面。可能涉及的数据源里发布之后新建了目录的表,记录里没有它,按变化里给的补进来(发布时 * 「尚无目录」) */ function CatalogVersions({ versions, changes }: { versions: CatalogVersionsRecord; changes: CatalogDriftTable[] }) { + const labelOf = useCatalogLabels(versions, changes) // 老后端给的变化没有 impact:按直接引用 const impactOf = (c: CatalogDriftTable) => (c.impact === 'possible' ? 'possible' : 'direct') const changed = new Map(changes.map((c) => [`${impactOf(c)}/${c.source}/${c.table}`, c])) @@ -246,7 +251,9 @@ function CatalogVersions({ versions, changes }: { versions: CatalogVersionsRecor return (
  • - {table} + + 「{source}」{CATALOG_DRIFT_TEXT.table(labelOf(source, table) ?? c?.label ?? null, table)} + {CATALOG_DRIFT_TEXT.version(v)} {c && ( @@ -285,6 +292,39 @@ function CatalogVersions({ versions, changes }: { versions: CatalogVersionsRecor ) } +/** 数据源 id → 表名 → 中文名。几个版本、几次展开共用,同一个数据源只取一次清单 */ +const labelCache = new Map>>() + +/** + * 发布记录里各表现在的中文名:按数据源名找到 id(变化里带着 source_id,否则查数据源列表),取那个数据源的目录清单。 + * 取不到(数据源删了、没权限、断网)就只写表名,不报错——这一栏是附带的说明 + */ +function useCatalogLabels(versions: CatalogVersionsRecord, changes: CatalogDriftTable[]): (source: string, table: string) => string | null { + const { list: sources } = useDatasources() + const [labels, setLabels] = useState>>({}) + const got = useRef(labels) + got.current = labels + const names = useMemo(() => [...new Set([...Object.keys(versions.direct ?? {}), ...Object.keys(versions.possible ?? {}), + ...changes.map((c) => c.source)])], [versions, changes]) + useEffect(() => { + let alive = true + for (const name of names) { + const id = changes.find((c) => c.source === name && c.source_id)?.source_id ?? sources.find((x) => x.name === name)?.id + if (!id || got.current[name]) continue + let pending = labelCache.get(id) + if (!pending) { + pending = api.dataCatalog.list(id).then((d) => Object.fromEntries(d.tables.map((t) => [t.table_name, t.label]))) + labelCache.set(id, pending) + // 清单会变(有人补了中文名):只缓存这一阵子,失败的不缓存 + pending.then(() => setTimeout(() => labelCache.delete(id), 60_000), () => labelCache.delete(id)) + } + pending.then((m) => { if (alive) setLabels((cur) => ({ ...cur, [name]: m })) }, () => {}) + } + return () => { alive = false } + }, [names, changes, sources]) + return (source, table) => labels[source]?.[table] ?? null +} + /** * 缩略图:这一版的节点按真实坐标缩进一个小框,类型色填充;恢复后会被拿掉的 * (只在画布上有)画成红色虚框叠在同一个坐标系里,改过配置的描琥珀边 diff --git a/scripts/check-studio.mjs b/scripts/check-studio.mjs index 50a32d52..78fdeb15 100644 --- a/scripts/check-studio.mjs +++ b/scripts/check-studio.mjs @@ -1011,6 +1011,12 @@ await section('分析失败不静默', async () => { // ================================================================ 5 await section('版本历史:预览、恢复成一次可撤销的改动', async () => { const { ctx, page, state, errors } = await open() + // 发布时的目录版本按横幅的写法列:「数据源」中文名(表名)。中文名从数据源的目录清单补(只读,这里答掉) + const catRow = (table, label) => ({ table_name: table, qualified: table, is_view: false, in_schema: true, label, label_status: label ? 'confirmed' : null, + kind: null, counts: { proposed: 0, verified: 0, confirmed: label ? 1 : 0, rejected: 0 }, relations: 0, usage: 0, version: 1, updated_at: null, updated_by: null }) + await page.route(/\/api\/datasources\/src-shop\/catalog$/, (route) => route.fulfill({ json: { system_notes: false, schema_note: null, + schema_truncated: false, schema_total: 5, tables: [catRow('parks', '景区'), catRow('visits', '入园记录'), catRow('orders', '订单'), + catRow('channels', '渠道'), catRow('gates', null)] } })) // 记下按版本号取详情的请求:版本历史开着时换工作流,不能拿上一张选中的版本去请求这一张 const asked = [] page.on('request', (r) => { @@ -1044,6 +1050,12 @@ await section('版本历史:预览、恢复成一次可撤销的改动', async (await possible.locator('[data-version-catalog-table="gates"]').getAttribute('data-changed')) === 'true' && (await possible.locator('[data-version-catalog-table="gates"]').innerText()).includes('尚无目录')) check('……发布时一张有目录的表都没有的源写一句', possibleText.includes('「crm」发布时还没有目录'), possibleText.replace(/\s+/g, ' ')) + // 和运行横幅同一个写法:「数据源」中文名(表名);没改过的表也写中文名,没有中文名的只写表名 + const nameOf = (table) => cat.locator(`[data-version-catalog-table="${table}"] [data-version-catalog-name]`).first().innerText().catch(() => '') + check('……每张表写「数据源」中文名(表名),没改过的也写', await until(async () => (await nameOf('parks')) === '「shop」景区(parks)') + && (await nameOf('visits')) === '「shop」入园记录(visits)' && (await nameOf('channels')) === '「shop」渠道(channels)', + [await nameOf('parks'), await nameOf('visits'), await nameOf('channels')].join(' | ')) + check('……没有中文名的表只写表名', (await nameOf('gates')) === '「shop」gates', await nameOf('gates')) await page.locator('ol[aria-label="版本"] > li').nth(2).locator('button').first().click() await page.waitForTimeout(300) check('没发布过的版本没有目录版本这一栏', await count(page, '[data-version-catalog]') === 0)