- 21 Aug, 2026 27 commits
-
-
Data Governance Dev authored
1. TaskModal.vue form 加 tableComment 字段 watch(form.table) 从 tableOptions 找对应项的 table_comment 写入 编辑场景从 props.task.table_comment 回填 payload 加 table_comment: form.tableComment || null 选表时存一次到 task,列表直接读,零 N+1 查询 2. TaskConfigView.vue 模板加 <th>表注释</th>(位置在数据表后) colspan 8 → 9 td 渲染 {{ row.table_comment || '—' }} 3. 后端 commit 已加 task.table_comment 列 + 字段流通(commit 3) 老任务没存过 → 列表显示「—」,重新编辑任务选一次表就有 -
Data Governance Dev authored
两个独立功能(同一个表的两列)一起做,因为迁移逻辑都走 init_db 兜底: 1. task.checked_at: TIMESTAMP NULL schema.sql / Task ORM 加列 _migrate_task_checked_at() 在 init_db 里兜底 routers/queries.py::start_query 末尾:sm_put 后写 task.checked_at = utc_now try/except + rollback + warning:元数据写失败不能阻塞查询主流程 时刻选 /start(不是最后一页 /page):中断也算「最近关注过」 2. task.table_comment: TEXT NULL schema.sql / Task ORM 加列 _migrate_task_table_comment() 兜底 routers/tasks.py: TaskBase/Update/Out 加字段 + _row_to_out + create_task 写入 update_task 的 setattr 循环自动覆盖 list_tasks 接口直接读这列返前端,零查询开销 3. CLAUDE.md「只做查询」边界 这里写 task 元数据,不是业务数据修改 业务表(field/rule/connection_preset)一律不 UPDATE 4. 前端 TaskConfigView 现在用 row.table_comment 还没显示(commit 4 才加列) formatCheckedAt 已经备好(commit 2),checked_at 写完即可生效
-
Data Governance Dev authored
1. 分组过滤没生效 <select v-model=groupFilter> 只绑了 model,没 watch 触发 load 选了「手机号」不点「查询」list 不刷新 加 watch(groupFilter, () => load()) 自动 reload keyword 保留「回车 + 查询按钮」(避免每个 keystroke 都请求) 2. formatCheckedAt:把 ISO 字符串转成 'YYYY-MM-DD HH:mm' 后端已返 checked_at(先 hardcoded None,下个 commit 接上) —— format 函数先备好,避免后续加 checked_at 字段后还要再改前端 3. import 加 watch
-
Data Governance Dev authored
web3 ResultTable 是自定义 table,el-table resizable 不能直接复用,自实现: 1. ResultTable.vue columnWidths reactive + startResize/onResizeMove/endResize 拖拽状态不放 reactive(频繁写不触发响应式刷新整张表格) window mousemove/mouseup(用户拖快会脱出 th 丢失 mouseup) 拖拽过程 document.body user-select=none + cursor col-resize onBeforeUnmount 清理监听 + 还原 body 样式 2. global.css .resize-handle 样式(透明 + hover 变蓝 409eff) table.dq-table 加 table-layout: fixed(auto 时 width 被无视) thead th 加 position: relative 让 handle absolute 定位 3. 踩坑:改 Vue setup 的 import 段要核对所有 export 符号 我改 import 时删了 watch 符号,但 setup 里 line 248 的 watch() 还在用 → 运行时 ReferenceError: watch is not defined 把页面打白 4. localStorage 列宽持久化没做(用户没要求;后续可加 key→width 存)
-
Data Governance Dev authored
1. DataQualityView 按钮区只留「字段配置 + 导出 Excel」 删导入(btn-success)/批量删除(btn-danger)+ 对应的 mock 函数 导出按钮加 :disabled(rows 为空或导出中禁用) 2. 新增 src/utils/excel.js 从 web2 IdCardTable.vue 抽 exportExcel + applyAllBorders + applyHeaderStyle 封装成纯函数 exportToExcel({ rows, columns, filename, sheetName, confirm }) 三行表头:comment / cn / 数据;冻结前 2 行 + 黑色细边框 + 加粗灰背景 columns 兼容 {key,cn,comment}[] 和 string[](fallback) 大于 5000 行走 confirm(n) 二次确认(弹窗 UI 在调用方做) 3. onExport 列范围 visibleFields 非空 → 用户勾选;为空(未保存过)→ 退化为 resultFields 全量 过滤掉 __reason(ResultTable 内部追加的备注列) 文件名 <任务名>_<时间戳>.xlsx 防多次导出覆盖 4. +xlsx-js-style 依赖(跟 web2 同库,导出样式一致)⚠ bundle 多约 700KB(xlsx-js-style 是胖依赖,build 已超 500KB 阈值) 后续可改 dynamic import(点导出按钮时再加载),本次没做 -
Data Governance Dev authored
1. FieldConfigDrawer:cn 后面加 el-tooltip 包裹的 InfoFilled 图标 仅 f.comment 非空时显示,没注释的字段不出 icon(避免噪声) 2. global.css:.field-comment-icon 样式 margin-left: auto 推到 flex 容器最右(关键,cn/en 用了 gap:8px) 平时淡灰 #c0c4cc,整行 hover 时变蓝 #409eff + cursor: help
-
Data Governance Dev authored
1. ResultTable:自定义分页器换成 el-pagination(带 ... 折叠中间页) pageSizes 默认 [10, 25, 50, 100, 200]、defaultPageSize=10 加 visibleFields prop + allColumns 优先按它过滤(FieldConfigDrawer 联动) 表格下方加 padding 让分页器不贴表格 2. DataQualityView:字段配置抽屉改从 resultFields(后端 /start 返回的真字段)取 visibleFields 默认按 show_default=true 重置勾选;用户手动保存后写入 3. TDZ 修复:fields/visibleFields/watch 块挪到 resultFields ref 声明之后 computed + watch immediate 在 setup 立即跑;const ref 在它前面会 TDZ 报错 顺手清掉不用的 fieldsByType / violationRows import
-
Data Governance Dev authored
用户反馈:达梦 HGWXZJ 库的 YW_GJY(归集业务数据表)在「校验规则」Tab 每个字段 都显示两遍(ID/ID、DWZH/DWZH、DWZJID/DWZJID、...),内容完全一致。 根因推断: 达梦的 ALL_COL_COMMENTS 里同一 (OWNER, TABLE_NAME, COLUMN_NAME) 偶尔有 2+ 行 (历史 COMMENT ON COLUMN 重复 / DBA vs USER 视角双份 / 升级残留), LEFT JOIN 不去重 → ALL_TAB_COLUMNS 的每行匹配 2 行 → 整段 SQL 每列翻倍。 web2 模板跟 web3 一致,理论上同样问题;web3 之前没碰到是因为 web2 没做过 编辑场景自动 listColumns 的 UI 入口(手工流程绕开了)。 改动(dameng + oracle 同源一起改): LEFT JOIN ALL_COL_COMMENTS 改为 ROW_NUMBER() 子查询预聚合, PARTITION BY (OWNER, TABLE_NAME, COLUMN_NAME) 每组取一条, ORDER BY COMMENTS DESC NULLS LAST(启发式:取最长注释当用户最后写的)。 MySQL 模板不动(COLUMN_COMMENT 在 INFORMATION_SCHEMA.COLUMNS 自带, 不在另一张表里,无 1:N 风险)。 无单测(数据字典视图行为依达梦/Oracle 版本而异,fixture 不可靠); 让用户在达梦 disql 跑 SELECT ... GROUP BY ... HAVING COUNT(*) > 1 验证 ALL_COL_COMMENTS 本身 是否真有重复行。
-
Data Governance Dev authored
T4 任务(task_id=16,GJ_DWRYBD 表,39 字段全是大写 YWBH/FCRS/...)跑完查询, 前端表格所有单元格都显示 (NULL),但备注列能正确显示「fcrs: 100到200之间」。 根因(列名大小写不匹配): - 后端 db_adapter._rows_as_dicts 全局把列名归一为小写 (normalize_column_name line 188) - row dict 的 key 全是小写(ywbh / fcrs / ...) - 后端 _evaluate_row 用 cf.field_key = f.field_key.lower() 访问, 所以规则匹配正常(备注列能正确显示就是证据) - 但前端 resultFields.key 用了 f.field_key 原值(大写 FCRS) - ResultTable 里 row[col.key] → row['FCRS'] → undefined → (NULL) 两处表现是同一个根因: - 单元格全 (NULL):row[col.key] 大写找不到 - 错误 tooltip 永远 fallback「该字段不合法」: 原版 errTip 用 col.cn(大写)找 row.issues[].field(小写), 永远匹配不上 改动: DataQualityView.vue resultFields 里: key: (f.field_key || '').toLowerCase() // 跟 row dict 小写对齐 cn: f.field_key // 表头显示保留原大小写 ResultTable.vue errTip: 直接用 col.key(小写)找 row.issues[].field(小写), 去掉 col.cn 那一跳 教训:「DB 列名大小写 × 字段名大小写 × 后端归一化策略」是反复出现的坑。 db_adapter 全局归一为小写是契约,前端落 key 时必须主动 .toLowerCase()。 -
Data Governance Dev authored
用户反馈:T4 跑了几页后点「中断」,结果表里已经加载到的不合规行全没了。 上一版 onCancel 调 resetQueryStatus(),它会把 rows / lastResult / resultFields / queryError 全清空 —— 等于把用户已经看到的命中行擦掉。 新增 clearProgressOnly(): 只清进度相关(sessionId / totalPages / totalRows / scannedPages / scannedRows / receivedPages),保留 rows / lastResult / resultFields onCancel 改用 clearProgressOnly: - 用户已看到的不合规行留住 - 顶部 toast:「已中断(已扫 N 页,命中 K 条)」 - 再次点「查询」会走完整 onQuery,自然用新结果覆盖 教训:中断 ≠ 重查。reserve 用户可见状态是默认行为
-
Data Governance Dev authored
把单次同步 runQuery 替换成分页 + 并发 worker 模式: DataQualityView.vue 重写查询逻辑: onQuery() 1) POST /start → COUNT + 编译规则 + 拿 session_id + total_pages 2) PAGE_CONCURRENCY=3 个 worker 并发抢 queue([1..total_pages]), 每个 worker /page 拉到 → appendPage() 按页号升序拼进 rows 3) 收尾聚合 lastResult + toast onCancel() - /cancel 摘 session(best-effort) - AbortController.abort() 让在飞的 fetch 立即 reject - resetQueryStatus() 清空 sessionId / rows / lastResult / queryError 进度展示:task-info-bar 顶部「查询中 N/M 页 · 命中 K 条」 onBeforeUnmount:cancel + abort,避免组件销毁后 fetch 还在飞 ResultTable.vue 新增 appendMode prop: watch(() => props.rows.length, () => { if (!props.appendMode) currentPage.value = 1 }) 默认 false 保持原行为(rows 替换 → 回第 1 页); append 模式下 rows 增量追加不重置页码 ResultTable 新增 row-key 模式:父组件传 rowKey 函数, 用后端注入的 __row_index 做稳定 key(append 模式下保证 Vue DOM 复用正确) PAGE_SIZE = 500(与后端默认对齐) 无单测(前端无测试基础设施,手动 e2e 验证见 work-logs/2026-08-21.md) -
Data Governance Dev authored
client.js: request() 第三个参数 { signal } 透传给 fetch; http.{get,post,put,patch,delete} 都接受可选 signal。 queries.js: 删 runQuery,新增三个: - startQuery(taskId, { pageSize, signal }) → /api/queries/start - fetchPage(sessionId, pageNo, { signal }) → /api/queries/page - cancelQuery(sessionId, { signal }) → /api/queries/cancel signal 是 AbortController.signal,配合 orchestrator 实现: 1. fetch 主动 reject(节省带宽) 2. 后端 /cancel 摘掉 session(后续 /page 立刻 404 短路) 无单测(纯 plumbing,配合 commit 1 后端测试覆盖)。 -
Data Governance Dev authored
把原来 /api/queries/run 单次同步全表扫描改成 3 个端点: POST /api/queries/start COUNT + 编译规则 + 建 session(UUID) POST /api/queries/page 按页号拉一页,跑规则,只返回不合规的行 POST /api/queries/cancel 摘掉 session(best-effort 中断) session_manager.py 用模块级 dict + Lock 存 QuerySession,TTL 30min 懒清理。 每页新开 DBConnection → fetchall(避开 cursor 生命周期管理),~50ms 重连可接受。 dialect 兼容: mysql : LIMIT <n> OFFSET <m> dameng : OFFSET <m> ROWS FETCH NEXT <n> ROWS ONLY (SQL:2008) oracle : ROWNUM 三层嵌套(兼容 11g/10g;T4 任务实测 11g 服务端用 OFFSET/FETCH NEXT 报 ORA-00933) postgres / sqlserver : 同 dameng 前端友好的关键点: - /start 一次返回 fields(含 rule_list + field_comment),前端 resultFields 不再变 - 后端给每行注入 __row_index = (page_no-1)*page_size + idx,给前端做 Vue row-key - /page 失败不摘 session,让前端可以重试或走 /cancel 测试:新增 web3/tests/test_queries.py 15 条(start / page / cancel / paginate_sql / 并发 / TTL), 老的 test_run_query_only_bad_rows.py 和 test_rule_types_e2e.py 适配新端点(删 max_rows 那条, end-to-end 改成 /start + /page 聚合)。73 条全过。
-
Data Governance Dev authored
无任务 / 选了任务但跑出 0 条不合规行 / 用户还没点「查询」时, rows=[],「备注」列还是会被 allColumns 自动追加,整张表只有一列「备注」 加占位符「—」,看起来很怪。 allColumns 计算属性改成 rows.length > 0 才追加「备注」列。 rows 为空时整张表只渲染业务列 + 「暂无数据」占位。 Vite HMR 自动生效,不用重启。
-
Data Governance Dev authored
问题(commit 5ffa4cc4 之后又暴露): 用户重跑 test3,attendance_in_time(rule_id=31)仍判不合规。 诊断:pymysql/oracledb/dmPython 把 DATETIME 字段自动转成 Python datetime 对象, 但用户规则代码里的 strptime 用了 '%Y-%m-%d'(只接受纯日期), 而 str(datetime_obj) 是 'YYYY-MM-DD HH:MM:SS'(空格分隔的日期+时间), 格式不匹配 → ValueError → except 吞 → False。 修法:在 run_rule 调 check 前先做一次宽泛解析,把任意类型的 val 归一化成 datetime.datetime 对象。用户写规则直接 value.date() / .year / .strftime() 用, 不再关心 val 的原始类型(驱动转的 datetime 对象 / str / bytes / None)。 1. backend/core/rule_runner.py - 新增 _DATE_PARSE_FORMATS:10 种常见格式(MySQL DATETIME 默认 / ISO8601 / 紧凑 / 斜杠分隔 等) - 新增 _patch_date_value(val):datetime/date/str/bytes → datetime.datetime, 解析失败返 None - run_rule 的 date 分支先 patch:成功 → _exec_user_function(code, dt); 失败 → 抛 RuleRunError('无法解析为日期:...') 让 issues 暴露原因 2. backend/core/ai_rule.py - _DATE_USER_TEMPLATE_INITIAL / _DATE_USER_TEMPLATE_FIX 完全改写: 「value 已经是 datetime.datetime 对象(后端已自动宽泛解析)」 去掉所有 datetime.datetime.strptime 教学,改用 value.date() / .year 等 3. src/components/RuleModal.vue - codePlaceholder(date) 改 'def check(value) -> bool:\n return value.date() < datetime.date.today()' - date 类型 form-hint 改 'value 已是 datetime 对象;不要再 strptime' (红字警示) 4. tests/test_rule_runner.py - 删 1 条过时的 strptime 风格用例 test_strptime_format_check - TestDate 加 8 条用例:patch 后传 datetime / datetime 对象直接传 / MySQL 默认格式 / ISO8601 / 紧凑 / 斜杠 / bytes / 解析失败抛 RuleRunError / None 抛 RuleRunError / 老 strptime 风格代码在 patch 下返 False(迁移信号) - 新 TestPatchDateValue 类 11 条用例:锁 _patch_date_value 的 12 种类型行为 5. tests/test_rule_types_e2e.py - test_queries_run_dispatches_by_rule_type 里 birthday 规则的 code 改成新风格 'value.date() <= datetime.date.today()' 对老规则的影响(migration gap): - DB 里的旧 strptime 风格代码(rule_id=28 / 31)继续返 False —— 行为不变, 用户需去 UI 改成新风格。按 CLAUDE.md「本工具只做查询」不动 DB。 - 新生成的或手写正确代码直接用 value.date() 等,跟 patch 行为兼容。 60 条测试全过(+9 新增)。
-
Data Governance Dev authored
沙箱里 datetime 是模块名(不是 from datetime import datetime), datetime.strptime / datetime.now 在沙箱里直接 AttributeError。 三处误导性的写法全部修正: 1. backend/core/ai_rule.py - _DATE_USER_TEMPLATE_INITIAL / _DATE_USER_TEMPLATE_FIX 两份 prompt 把 datetime.strptime 改成 datetime.datetime.strptime, 并加粗警告『绝对不能写成 datetime.strptime(会 AttributeError)』 - 顺手修一笔 def check(value) -> str -> bool 的笔误(多写了 -> str) 2. src/components/RuleModal.vue - codePlaceholder(date) 改用 datetime.datetime.strptime, 注释里点明沙箱语义 - date 类型的 form-hint 改用 datetime.datetime.strptime, 并加红字 warning 警示『不是 datetime.strptime』 3. tests/test_rule_runner.py - TestDate 加 test_datetime_strptime_without_module_qualifier_fails_clearly 锁定『datetime.strptime(缺 .datetime.)必然 AttributeError』这个行为, 防止哪天有人悄悄做成兼容(破坏语义清晰度) 注:已有坏规则(如 attendance_in_time 已落库 4 处)用户自行到 UI 改, 按 CLAUDE.md『本工具只做查询』原则不动 DB。 39 条测试全过。 -
Data Governance Dev authored
背景: cn / type 故意不持久化(plan §4),编辑时按 source_table 重新从数据源拉。 但用户每次都得手动点「测试连接」走 loadColumns,体验差。 改动(只动 web3/src/components/TaskModal.vue): 1. 新增 refreshColumnsForEdit(): - 用存储的 conn_json(含匹配的预设密码或后端返回的密码)+ source_table 直接调 listColumns API - 按 field_key 索引 merge 进现有 fieldList(不动 stored rules,只覆盖 cn/type/isNullable) - DB 中已不存在的字段 → 保留 stored 状态 + 顶部 warning - DB 中新出现的字段(无 stored rules)→ 不主动加,避免污染用户视角 - API 失败(密码空/DB 不通/表被删)→ 静默退化,不弹错误 2. form.password 回填优先级:task 自带 > 预设 > 用户手输 (旧版只靠预设匹配回填,手动建的任务没匹配预设就拿不到密码) 3. 触发时机:编辑场景打开弹窗后,props.task.field_list 异步到位时 (不能挂 open watch:父组件 openEdit 是「同步 set row + 异步 getTask」 两步,同步阶段 field_list 还是 [],refreshColumnsForEdit 会 no-op) 4. 字段表头部「↻ 重新同步字段」按钮:DB 中途变更后手动刷新 v-if=props.task && form.table && fieldList.length > 0 5. preset 加载用 props.task.db_type(不再用 form.dbType 默认 'MySQL'): 编辑 Oracle 任务时,open watch 同步阶段 preset 匹配拿的是 MySQL 预设, 永远匹配不上;watch(form.dbType) 异步重载又来不及。 改为先用 targetDbType=props.task?.db_type||form.dbType 加载。 踩坑:见 work-logs/2026-08-21.md(同步/异步时序类 + 密码可见性)。 -
Data Governance Dev authored
旧设计:密码不进 API 响应(标榜'安全'),靠前端从匹配的 connection_preset 里拿。 问题:业务用户手动建的连接没匹配的预设 → form.password 永远空 → /api/connect/columns 因为缺密码连不上 → 编辑场景 cn/type 永远是 '—'/'空'。 改为:详情接口(GET /api/tasks/{id})直接返回 password 字段;列表接口不返回。 权衡: - 明文传输:内部工具单用户部署,TLS 终端到终端,加密不加密收益相同 (用户反正可以从 task.conn_json 复制、从前端 devtools 看) - 真正权威的来源是 task.conn_json 里的密码(用户建任务时填的那个), 而不是预设的密码(可能跟任务不同步) 测试 test_password_returned_in_detail_but_not_in_list 守住: detail 接口 password 有值,list 接口 password 为 None。 如果以后需要让 list 也带,加 ?include_password=true 查询参数即可; 现在不预先暴露。 -
Data Governance Dev authored
之前 code 为空只笼统提示「未填写」,用户难定位是哪个框没填; 现在分别提示「请先填写正则表达式」/「请先填写 Python 函数」/ 「请输入测试值」,避免和 textarea 的 placeholder 灰色示例混淆。
-
Data Governance Dev authored
需求:规则这里增加数值和日期规则(可以切换),同样可以使用AI生成, 目标结果是一个python函数,测试也是调用后台测试。 DB: - rule 表新增 rule_type(regex/number/date,默认 regex)/ code(Python 函数源码) - init_db() 自动 ALTER ADD COLUMN(每个 ADD 都带 DEFAULT,老规则仍按 regex 走) 后端: - core/rule_runner.py(新增):run_rule(rule_type, regex, code, value) - regex 走 re.compile.search - number/date 走 _exec_user_function(白名单 exec 沙箱) - 白名单 __import__:datetime/time/_strptime/math/decimal/re (strptime 内部 lazy-import _strptime,所以不能一刀切屏蔽 __import__) - 沙箱预注入 datetime/time/math/decimal/re 到 globals - validate_user_code():compile + 必须 def check(value) -> bool - core/ai_rule.py(新增):gen_rule(desc, rule_type) - regex 复用老的 ai_regex.gen_regex(行为完全兼容) - number/date 走 LLM,validate_user_code 校验,5 次重试 - models/rule.py:Rule 加 rule_type/code + to_dict + RULE_TYPES 常量 - routers/ai.py: - 新增 /api/ai/rule(POST {desc, rule_type} → {ok, code, note}) - 新增 /api/rule/test(POST {rule_type, code, value} → {ok, pass_, error}) - 保留老 /api/ai/regex 和 /api/regex/test(薄壳转发,老测试不挂) - routers/tasks.py:RulePayload/RuleOut 加 rule_type/code,按类型持久化 - routers/queries.py:按 rule_type dispatch,number/date 走沙箱; 沙箱报错(语法错 / 缺 check)也按不合规处理,把错误原因写进 issues 前端: - src/api/ai.js:genRule/testRule 统一接口(rule_type 参数化) - src/components/RuleModal.vue: - 规则类型 tab 切换(正则/数值/日期) - regex 走单行 input;number/date 走等宽 Python 编辑框 - AI 按钮文案按类型变(生成正则/数值校验函数/日期校验函数) - 测试统一走 /api/rule/test,保证和检测引擎语义一致 测试: - tests/test_rule_runner.py(新增,20 个用例): - regex 简单匹配 / 空正则 / 非法正则 - number 校验函数 / 非法输入 / 缺 check / 运行时异常 - date strptime 校验 / 范围校验 - 沙箱安全:os 拒绝 / datetime 预注入 / open 拒绝 / eval 拒绝 / getattr 拒绝 - validate_user_code 4 项 - tests/test_rule_types_e2e.py(新增,5 个用例): - 持久化三种 rule_type - 默认 rule_type=regex(向后兼容) - queries/run 按 rule_type dispatch - 规则执行失败标记为不合规 + 暴露原因 - 老 /api/ai/regex 端点兼容 迁移:老规则行 rule_type='regex'、code=NULL,自动走旧逻辑(不动现有数据)。 -
Data Governance Dev authored
UI 简化: - 去掉「Oracle Client」输入框(v-if=Oracle 时显示)—— 这个路径由连接预设 (connection_presets.oracle_client_dir) 配置,seed.py 里「黄冈公积金」那条已带, 用户不应手填 - 去掉「连接参数」输入框(jdbcParams)—— 三种数据库都没用 字段链路保留: - form.oracleClientDir 字段不删除,仅模板不展示 - presetToForm 透传 preset.oracle_client_dir → form.oracleClientDir - 编辑场景从 task.oracle_client_dir 回填 - onSave 写进 conn_json → 后端 DBConfig 拿到 —— 这样选预设 / 编辑现有 task 时 Instant Client 路径仍正确传递, UI 干净 + 链路完整两不误 presetToForm 同步去掉 jdbcParams 字段(form 已经没有该字段)
-
Data Governance Dev authored
包含三块改动: 1. Oracle Instant Client 路径字段端到端打通 - 后端 ConnectRequest / TaskOut / DBConfig 已有 oracle_client_dir 字段但前端 表单缺一个输入框,用户拿不到这个值,测试连接永远走 thin 模式 - TaskModal 表单加 Oracle Client 输入框(仅 dbType=Oracle 时显示)+ 回填/保存 - connections.js buildPayload / connectionPresets.js presetToForm 串联字段 - routers/tasks.py TaskOut 加 oracle_client_dir 给编辑场景用 - routers/queries.py DBConfig 多传 oracle_client_dir(关键,否则查询时 还是 thin 模式) 2. Oracle schemas 列表 + database/schema 拆分 - Oracle list_schemas 改用 ALL_TABLES.DISTINCT OWNER(之前用 all_users 会列所有用户,大部分没表,选了之后 list_tables 找不到表) - list_tables / list_columns 加独立 schema 参数:MySQL/达梦 留空走默认 (fallback 到 database),Oracle 用前端传的 OWNER。 之前错位:database=service_name 时被当成 OWNER 查 ALL_TABLES → 0 行 - ConnectRequest 加 schema: Optional[str] 字段 - 前端 buildPayload 加 schema: form.schema || form.db 3. start.bat 启动后端前自动 kill 占用 :8767 的老进程 - 后端没 --reload,改代码后必须手动 taskkill 老进程,否则新进程 bind 失败 静默退出,start.bat 用户以为改了没生效 - 现在双击 start.bat 就行:netstat 找 PID → taskkill → 等端口释放 → 起新进程 12 条后端用例全过。 -
Data Governance Dev authored
-
Data Governance Dev authored
后端 queries.py: - SQL 改成 SELECT 全部已配置字段(早期只 SELECT「rules ∪ show_default」, 导致结果表表头 14 列、数据只有 1 列;列与数据错位让非规则列全部 NULL) - 打开连接后调一次 list_columns,从 info_schema 拉字段注释写进 field_list[i].field_comment,供前端表头 tooltip - field_list 顺带回每条规则的 rule_list({id,desc,regex}), 供「⚠ N规则」badge hover 出规则明细 前端: - DataQualityView.vue: resultFields 映射补 comment / ruleList - ResultTable.vue: 表头 cn 包 el-tooltip(无注释则退化 plain span), 已有「⚠ N规则」tooltip 不动 测试: - test_run_query_only_bad_rows.py 加 _FakeDBConnection.list_columns + 3 条用例 (show_default=false + 无规则的字段也要 SELECT;field_comment 从数据源补回; rule_list 带回规则明细),共 12 条全过 -
Data Governance Dev authored
判定口径修正:查询结果应是「不符合」校验规则的数据,之前合规行也一起返回了。 - 字段内多条规则 = AND:任一条不满足该字段即不合规,且所有未通过的规则 都进 issues(去掉原来命中第一条就 break 的逻辑),备注列能看全原因 - 行内多个字段 = AND:任一字段不合规整行就被查出来 - 全部规则通过的行直接丢弃,不再进 rows - total 从 len(raw_rows) 改为 bad_rows,与 rows 对齐 全表扫描(原来固定 LIMIT 1000,只校验前 1000 行): - db_adapter 新增 DBConnection.iter_rows(),fetchmany 流式逐行读。 MySQL 必须用 SSDictCursor —— 普通 Cursor 是客户端缓冲的,execute 时 整个结果集就进内存了,fetchmany 省不了内存;达梦/Oracle 驱动默认 就是服务端游标,普通 cursor 即可 - SQL 去掉 LIMIT,读取与校验合成一个流式 pass,只有命中的不合规行进内存 - 请求参数 limit(扫描上限)→ max_rows(返回上限,默认 5000); 超上限后 continue 而非 break,继续扫完全表,保证 scanned / bad_rows 始终是全表真值,避免计数被截断成假数字 - 响应新增 truncated 标志 + 截断提示文案 顺带修复: - SELECT 的列从「有规则的字段」改为「有规则 ∪ show_default」, 否则勾了默认展示但没配规则的列在结果表里全空 - field_list_out 去掉对 Rule 的重复查询,抽 rules_by_field 复用 前端:runQuery 参数改 max_rows,文案改「全表扫描」,truncated 时顶部 常驻横幅 + 信息栏提示仅展示前 N 条。 测试:新增 web3/tests/test_run_query_only_bad_rows.py,临时 sqlite 当配置库 + monkeypatch DBConnection 喂假数据,不依赖真实 MySQL/达梦,9 条用例全过。
-
Data Governance Dev authored
任务配置里某字段「默认展示」未勾选 → 结果表不展示该列。 - DataQualityView.vue: resultFields 映射时带上 showDefault - ResultTable.vue: allColumns 加 filter((c) => c.showDefault !== false) - 兜底 `!== false`:未传 showDefault 的列仍展示,向后兼容 mock - 被隐藏列的错误信息仍通过「备注」列汇总展示,不丢信息 测试: - test_show_default_filter.py(playwright 端到端验证) - test_edit_network.py(诊断编辑按钮触发的 API)
-
Data Governance Dev authored
之前 _migrate_field_unique / _migrate_connection_preset_unique 用 `sqlite_master WHERE type='index' AND name='uq_xxx'` 检测 UNIQUE 约束, 但 SQLite 把 table-level UNIQUE 约束内联到 CREATE TABLE、不会单独建 type='index' 条目(实际生效的是隐式 sqlite_autoindex_<table>_<n>), 导致每次启动都误判「缺 UNIQUE」→ DROP TABLE field → CASCADE 清 rule → 重建空表 → 用户配的字段+规则全部丢失(log 里连续 8 次 WARNING 证据)。 修法:检测用 pragma_index_list 或不限 type 的 sqlite_master WHERE name=...; 补建用 CREATE UNIQUE INDEX IF NOT EXISTS(幂等,与 autoindex 不冲突)。 不再 DROP TABLE。
⚠ ️ 修复前的所有 task 字段+规则数据已物理丢失,task 表本身保留; 用户需要重新点编辑配置字段和规则。
-
- 20 Aug, 2026 13 commits
-
-
Data Governance Dev authored
核心:数据质量查询页接后端 - 后端 POST /api/queries/run:按任务配置连真实库 → SELECT 字段 → 跑正则 → 返回 {ok, scanned, bad_rows, rows, field_list} - 前端 src/api/queries.js + DataQualityView.onQuery 改异步:loading 态 + 失败横幅 + task-info-bar 真实计数 - 字段列表优先用 task.field_list,覆盖之前的 fieldsByType mock 附带修 2 个 UX bug: 1. 编辑任务密码空 → 自动匹配预设(host+port+db+user)补回密码 + 选中预设 2. loadSchemas 默认选 form.db(避免字母序选 smart-build 这种空库) 顺便:规则 tab 空状态按 connVerified 拆两种文案 -
Data Governance Dev authored
DataQualityView 顶部「任务名称」下拉原来读 mock 的 taskOptionsByType[type], 改成调后端 listTasks({group}) 按分组名筛真实任务。 **改动(web3/src/views/DataQualityView.vue)** - TYPE_TO_GROUP 映射(id-card→身份证 等 6 个 slug → group.name) - loadTasksForCurrentType():type 变时自动重拉,默认选第一项;切路由前选中的任务还在列表里就保留 - watch(type, loadTasksForCurrentType, { immediate: true }) + onMounted 兜底(SSR 兼容冗余) - 下拉加 disabled「加载中… / 该分组暂无任务」选项 - 「查询」按钮 :disabled="!selectedTask";task-info-bar v-if="selectedTask" - 删 taskOptionsByType mock 导入 **为什么硬编码映射**:左侧菜单 slug 是英文、DB 分组名是中文,中间没有强对应字段。 硬编码是最直白方案,避免后端维护冗余 slug 字段(plan §3 决策)。 **API 验证** GET /api/tasks?group=身份证 → 4 个任务 GET /api/tasks?group=手机号 → 0 个(其他分组还没建任务,符合现状) 踩坑(详见 work-logs/2026-08-20.md 主题 30) - 选项 key 用 t.id 不是 t.name(同名任务时 Vue 复用 DOM 可能出错) - 空态:selectedTask 用 '' 而非 undefined,按钮 :disabled 兜底 - onMounted + immediate=true 看起来重复,留作 SSR 兼容保险 -
Data Governance Dev authored
**新增 web3/src/components/ResultTable.vue** - props: columns / rows / pageSizes / defaultPageSize / rowKey - 自动追加末尾「备注」列(展示该行所有 issues) - 错误单元格 el-tooltip 出错误描述(按 col.cn 匹配 issues) - 表头「
⚠ N规则」+ el-tooltip(col.rules / col.ruleList 驱动) - 内置分页 + 每页条数下拉 - rows 长度变化自动回第 1 页(避免停在空页) - 空数据占位行 - 样式复用 global.css 已定义的 .dq-table / .cell-error / .err-flag / .col-reason / .issue-line **重构 web3/src/views/DataQualityView.vue** - 删:内嵌 <thead><tbody>、isErrorCell/errTip/cellClass/ruleHint 辅助、page/pageSize/pagedRows/rangeStart/rangeEnd 计算、分页按钮块 - 删:「操作」列(修改/删除按钮按 plan §3 决策拿掉) - 换:<ResultTable :columns="fields" :rows="rows" ... /> - 留:查询区、任务信息条、卡片头部操作按钮(字段配置/导出/导入/批量删除)、FieldConfigDrawer **API 契约(结果表通用)** row[col.key] 显示值 row.errorCells?: [key] 哪些单元格标红+tooltip row.issues?: [{field,desc}] 备注列展开 col.rules 表头规则数 col.ruleList? 表头 tooltip 明细 踩坑(详见 work-logs/2026-08-20.md 主题 29) - el-tooltip 默认包 <div> 会破坏 td inline 布局 → 触发元素改 inline-flex span - global.css 旧 .cell-error:hover .err-tip 还在,hover 可能出鬼影 tooltip,留待清理 - 组件内管分页必须 watch rows 长度变 → 回第 1 页,否则批量删除后停在空页 -
Data Governance Dev authored
**后端** - 新增 models/field.py + models/rule.py(两张 ORM:field/task_id FK + rule/field_id FK + ON DELETE CASCADE) - Field.__table_args__ 加 UNIQUE(task_id, field_key)(schema.sql 第 59 行的事实地约束) - models/__init__.py 注册新模型;app.py 在 init_db 前 import models(否则 Base.metadata 不知道有这两张表) - routers/tasks.py:POST/PUT/copy 接 fields+rules;整存整取(先删后写,DB CASCADE 带走 rule);GET 列表给 fields/rules 计数,详情带 field_list;pydantic Field 改 import as PydField 避与 SQLAlchemy Field 同名撞车;port 出参强转 str 防 conn_json 里 int 致 500 - db/database.py:新增 _migrate_field_unique()(仿 _migrate_connection_preset_unique 套路:检测 UNIQUE 缺失则 drop+recreate),init_db 里调用 **前端** - TaskConfigView.openEdit:拆「同步设 row + 异步 getTask」两步,确保 TaskModal 拿到带 field_list 的详情(列表接口故意不带 field_list 省带宽) - TaskModal:watch(open) 编辑场景回填 form + 抽 _fillFieldListFromTask;新加 watch(field_list) 处理 getTask 异步时序;loadColumns 重选表时按 field_key 合并 rules 不覆盖 - TaskModal:加 connVerified 标志,testConn 成功才置 true;loadTables/loadColumns 未验证直接 return(编辑场景 form.schema 从 task 回填会触发 watch(form.schema) → loadTables → 空密码连 MySQL 报 1045) **接口契约** - POST/PUT /api/tasks body 加 fields: [{key, show_default, rules: [{desc, regex}]}] - GET /api/tasks 返回 fields/rules 计数;GET /api/tasks/{id} 多返 field_list - POST /api/tasks/{id}/copy 复制时连同 fields+rules 一起克隆,名字加 _副本 自动避重 - DELETE 走 DB 层 CASCADE(field/rule 一并清) **端到端** - POST 3 fields + 3 rules → 列表/详情计数与字段一致 - PUT 整体替换(删 1 加 1 改 1 全部生效) - POST /copy → field_list 全克隆 - DELETE → SQLite 直查 0 孤儿 field/rule 记录 踩坑(详见 work-logs/2026-08-20.md 主题 28 + 微调 1/2/3) - pydantic Field 被 SQLAlchemy Field 同名遮蔽(import 时必须 as 别名) - Base.metadata.create_all 不会改已存在表(UNIQUE 靠 ORM 声明 + 启动时迁移脚本兜底) - 验 SQLite UNIQUE 不能只看 sqlite_master type='index',UniqueConstraint 在表内 CREATE TABLE 子句里,要 INSERT 重复值测 - 编辑场景的 watch(form.schema) 没拦住自动连数据库(connVerified 守卫) -
Data Governance Dev authored
LLM 接入(P9 plan 阶段) - 后端: - backend/core/llm.py 从 web/core/llm.py 拷贝,default_config_path() 改读 web3/backend/configs/llm.yaml - backend/core/ai_regex.py:强制走 LLM(删规则库),LLM 返回用 re.compile 校验,编译失败回弹修复,最多 5 次重试 - backend/routers/ai.py:注册 POST /api/ai/regex - backend/configs/llm.example.yaml:多 provider 示例模板(真实 llm.yaml 已 gitignore) - backend/app.py:注册 ai_router - backend/requirements.txt:+anthropic +openai +pyyaml - 前端: - src/api/ai.js:genRegex(desc) - src/components/RuleModal.vue:删前端 mock 改调后端 测试改走 Python re(避免 JS RegExp 方言差异) - backend/core/ai_regex.py:+test_regex(regex, value) → {ok, match, error, groups} - backend/routers/ai.py:+POST /api/regex/test - src/api/ai.js:+testRegex(regex, value) - src/components/RuleModal.vue:testRegex() 改调后端,捕获组带回前端展示 规则弹窗保存按钮 - src/components/RuleModal.vue:emit 'save' + 取消/保存两按钮 + onSave() + hasValidRules (必须 ≥1 条规则 + desc/regex 都非空才允许保存) - watch 监听 [open, field.key],每次打开从父组件重新同步 - src/components/TaskModal.vue:onRuleSave 回写 field.rules(徽章数字刷新) - TaskModal onSave() payload 加 fields: [{key, en, cn, rules, show_default}](P4 落库用) 端到端验证 - 身份证/URL/UUID/产品型号 多 desc 都通过 LLM 生成 + Python re 校验 - 7 例 /api/regex/test 覆盖:真身份证、短串、捕获组日期、分隔符错、空 value、语法错、空 regex 踩坑 - 规则库'数字'关键词过宽易误命中,按用户意见删整段强制走 LLM - curl 中文 JSON body 在 Git Bash 转义破坏,用 --data @file 或 printf - python web3/backend/start.py 报 ModuleNotFoundError,要 python -m web3.backend.start - Vue3 const 不 hoist,hasValidRules 必须放在 ruleList 声明之后 -
Data Governance Dev authored
后端: - list_columns.{mysql,oracle,dameng}.sql 加 TABLE_NAME 过滤,绑定参数 (schema, table_name) - db_adapter.list_columns 签名加 table_name 参数 - 新增 POST /api/connect/columns 端点 + ListColumnsRequest schema 前端: - connections.js 加 listColumns(form, tableName) - TaskModal.vue: - 删 rebuildFieldList() + fieldsByType/fieldRuleSamples mock 路径 - 加 loadColumns() + watch(form.table) 触发 - 校验规则 Tab 三态提示(加载中 / 请选表 / 空表) - loadTables() 加「保留旧选中」(编辑场景) - 列定义:字段名(英文) / 字段注释(只读) / 数据类型 / 规则设置 / 默认展示 测试: - tests/test_load_columns.py 端到端验证(30 列渲染 + /api/connect/columns 调用 1 次) - tests/rule_tab_with_columns.png UI 截图 -
Data Governance Dev authored
之前用原生 <select> 无法过滤,schema 列表动辄几十个、数据表几百张,用户找目标很不方便。 改成 Element Plus <el-select filterable>,内置模糊搜索 + clearable 一键清空。 - Schema:filterable + 动态 placeholder(无数据时显示「请先测试连接」) - 选择数据表:filterable + label 带表注释「表名(注释)」格式 + 三态 placeholder 实测:tests/test_filterable_select.py 跑 Playwright,输入 "house" 后下拉只剩匹配项 c_house_data,其他 5 个 schema 被过滤,匹配文字高亮。
-
Data Governance Dev authored
- .form-grid 列 minmax(320px, 1fr) → minmax(0, 1fr):允许列收缩到内容真实需要的大小 - .form-row.full 加 min-width: 0:跨列行允许收缩,防止被内嵌 flex 子项的 min-content 撑宽 效果:Tab2 连接方式右列(主机地址 / 数据库名 / 密码)和下方数据表右列(选择数据表)x 坐标严格对齐,不再被 modal-body overflow-x: hidden 切掉。 实测:tests/test_modal_overflow.py 跑 Playwright 测距确认两个 grid 同宽 960px,右列 form-control 右边都对齐在 x=1200,无溢出。
-
Data Governance Dev authored
- connectionPresets.js presetToForm():password 从 '' 改为 preset.password || '' - 预设里明文存的密码(来自 seed.py 的 yaml 灌入)直接回填到 TaskModal 表单 - 用户无需在历史连接选中后重输密码
-
Data Governance Dev authored
- schema.sql 加 connection_preset 表(UNIQUE(db_type, name) + ord 索引) - ORM models/connection_preset.py 新建;models/__init__.py 导出 - seed.py 加 seed_connection_presets(13 条来自 web2/configs/db_defaults.yaml;硬编码避免引 pyyaml) - database.init_db() 改造:DB 不存在→schema.sql 全量;DB 存在→Base.metadata.create_all 增量建表 + _migrate_connection_preset_unique() 一次性补 UNIQUE - routers/connection_presets.py 新增 5 端点:列表(可选 db_type 过滤)/按 id/POST/PUT/DELETE - app.py 注册 connection_presets_router - src/api/client.js 加 params query string 支持(GET list 用) - src/api/connectionPresets.js 新封装 list/get/create/update/delete + presetToForm helper(密码暂不回填) - TaskModal.vue Tab2 顶部加「历史连接」section:下拉按当前 dbType 过滤,选中后回填 connName/host/port/db/user 字段,schema/表清空等用户测试连接再拉
-
Data Governance Dev authored
- 后端 core/ 模块自包含(拷贝 web2 的 db_adapter.py / sql_loader.py / models.py + 8 个 SQL 模板),不依赖 web.core - requirements.txt 加 pymysql / oracledb / dmPython - routers/db.py 新增 /api/connect/test + /api/connect/schemas + /api/connect/tables 三个端点 - app.py 注册 db_router - 前端 api/connections.js 封装 3 个调用 + 中文 label→小写 db_type 转换 - TaskModal.vue testConn() / loadSchemas() / loadTables() 改真实后端调用,去掉所有 mock
-
Data Governance Dev authored
- 任务弹窗「数据连接」Tab 数据库选项 6→3:保留 MySQL / Oracle / 达梦 DM,移除 SQL Server / PostgreSQL / JDBC 自定义 - global.css .modal 加宽 880→1000;.form-grid 列最小宽度 320px,右半边输入框不再被裁切 - docs/db-schema.md task 表 db_type 列注释同步
-
Data Governance Dev authored
测试 - tests/test_task_conn_fields.py - 验证 conn_json 全字段解析(conn_name/host/port/db/user/ jdbc_params/schema) - 验证编辑留空密码时后端兜底保留旧值 - 验证显式新密码能正确覆盖 - tests/test_task_persistence.py - POST 写入 → taskkill /F 关后端 → 重启 → GET 读回 - 同时直接 sqlite3.connect() 读 DB 文件确认磁盘数据 - 证明任务 CRUD 真的落到 SQLite,重启不丢 DDL 文档 web3/docs/db-schema.md - 启动流程(init_db() → schema.sql + seed) - 表关系总览(task_group/task/field/rule/validation_run/violation) - 当前已启用 2 张表(task_group + task)的列结构 + 索引 + 种子 - conn_json 内嵌字段全清单 - P4 起预留表结构预览 - CRUD 路由对照 - 验证脚本清单 - 踩坑表(含本次状态 UI 移除)
-