Repository navigation
Description
Measured on a real app (ObjectStack 17.2.0, console list view of a business object) while walking the intended end-user journey export → edit one column → import back. Two independent console behaviours make that journey unreachable for the role it is meant for.
现象 A — the list toolbar gates 「导入」 on create permission, but the import can update
An object whose permission set grants allowEdit: true and allowCreate: false shows 「导出」 in the list toolbar and no 「导入」 button at all. Measured on three objects for the same user in one session:
| object | allowCreate | allowEdit | allowExport | toolbar |
|---|---|---|---|---|
kpi_entry_line |
false | true | true | 导出 only |
kpi_entry_sheet |
false | true | true | 导出 only |
kpi_adjustment |
true | true | (unset) | 新建, 导入 |
The button tracks allowCreate exactly. But the wizard itself is not create-only — its step 3 offers 「当某行匹配到已有记录时」 with 始终新建 / 更新已有(无匹配则跳过) / 匹配则更新,否则新建, and the update mode works: with match fields selected the run reports 全部 4 行均有效 → 导入完成 · 更新 4 条, and the collection row count is unchanged.
So an update-only import — the exact shape of "bulk-edit one column in Excel and put it back" — is gated behind a permission it does not need. The role that is supposed to do the data entry is the one role that cannot reach it; only an admin can.
Related but not the same: objectstack-ai/objectui#10102 (wizard hardcoding writeMode:"insert") is about the mapping payload; on 17.2.0 the wizard clearly does send update modes. This is the entry point being gated on the wrong verb.
现象 B — the list export omits the object's required lookup columns, so its own output cannot be imported back
Exporting the same list (导出 → 导出为 XLSX) emits exactly the view's visible columns:
指标名称 | 计量单位 | 目标值 | 权重(%) | 实际值 | 完成率(%) | 得分率(%) | 最终得分 | 已调整 | 备注
No record id, and neither of the object's two required lookups. Feeding that file straight back into the wizard auto-maps all 10 columns and then hard-stops on step 2:
无法继续——以下必填字段未映射:所属填报单 (sheet), 来源下达 (plan_indicator)。
请在文件中添加对应的列,或返回上一步重新上传包含该列的文件。
The 「下载模板」 CSV confirms the asymmetry — it carries 28 columns including the two required lookups, but no record id column either, so even the template offers no id-keyed update path; matching is only ever by business columns.
Adding the two lookup columns by hand (display values, e.g. 2026 年第 3 季度考核 · 华南分公司) makes the whole loop work in 9 clicks, and the row-level validation is good: an unmatched row reports 第 4 行:来源下达: 找不到匹配 "…" 的记录, a missing row is simply left untouched, and the run ends 更新 3 条 · 已跳过 1 条 with no stray inserts. So the machinery is sound — the export just does not emit a re-importable file.
最小复现
- Define an object with at least one
requiredlookup, and a list view whose columns do not include it. - Grant a role
allowRead / allowEdit / allowExportbut notallowCreate. - As that role, open the list — toolbar shows 「导出」, no 「导入」 (现象 A).
- As an admin,
导出 → 导出为 XLSX, then导入and upload that exact file — step 2 blocks on the unmapped required lookups (现象 B).
期望能力
- A: gate the import entry point on the permission the chosen write mode actually needs —
allowEditforupdate,allowCreateforinsert, both for upsert. Offering the button and letting the wizard restrict its write modes would be enough; silently hiding it from the edit-capable role is what makes the feature undiscoverable. - B: make the export round-trippable — emit a stable key (record id, ideally as a column the wizard recognises as a match field) or at least the object's
requiredlookups, so the file the console produces is a file the console accepts. Today the two halves of the same toolbar disagree about what a row is.
平台版本
@objectstack/* 17.2.0
Activity
os-zhuang commentedon Sep 8, 2026
分诊:
repo:objectui/Bug/priority:p2/pm:queue域 —— 两个现象都在控制台前端。⚠️ 本席在 objectui 找到了现象 A 的确切机制(下节),现象 B 的导出实现未定位到具体文件(见边界)。⇒
repo:objectui。⭐ 现象 A:这不是一处工具栏疏漏 —— 那个映射是单点声明的,而且有测试钉着
上报说「按钮完全跟着
allowCreate走」。读代码之后,它跟得比这更彻底:⇒ ⭐
import → allowCreate是一条被显式声明、并被一条指名测试钉住的映射,不是某个工具栏组件里随手写的条件。这把本卡的性质改了:始终新建 / 更新已有(无匹配则跳过) / 匹配则更新,否则新建,而更新模式实测可用(全部 4 行均有效→导入完成 · 更新 4 条,集合行数不变)。⇒ 「导入」不再是单一动词,而权限映射还当它是。按类型判据,这是已声明契约与实际能力不符 ⇒
Bug。把按钮从
allowCreate放宽到allowCreate || allowEdit(上报建议 A 的形状),只有在导入的执行路径在服务端独立复核权限时才是安全的。⛔ 在测出答案之前不要动那一行。 本席没有验证这一点 —— 这是本席读数的边界,也是本卡最重要的一条移交。
现象 B:导出的输出不是导入的合法输入 —— 同一个工具栏的两半对「一行是什么」不一致
上报把这一条测得很完整,本席原样保留其判定:
requiredlookup;无法继续——以下必填字段未映射:所属填报单 (sheet), 来源下达 (plan_indicator);下载模板的 CSV 反而证明了不对称:它带 28 列含那两个必填 lookup,但同样没有记录 id 列 ⇒ 连模板都不提供按 id 更新的路径,匹配永远只能靠业务列。⇒ 这一条比 A 更根本:平台产出的文件,平台自己不接受。
⭐ 上报还给出了「机器本身是好的」的证据,这对定级很重要:手工补上那两列(用显示值)之后整个回路 9 次点击走通,行级校验也准确(
第 4 行:来源下达: 找不到匹配 "…" 的记录,缺失行原样跳过,更新 3 条 · 已跳过 1 条,无游离插入)。⇒ 坏的只是导出发什么列。等级
p2交给认领席
两个现象建议同一张卡、可以分两个 PR(它们在不同包:A 在
packages/permissions,B 在导出实现处),⛔ 但不要只做一个就关卡 —— 只修 A,角色能点进去然后在第 2 步撞 B;只修 B,该做这件事的角色仍然看不见按钮。任一单独修复都不能让上报的旅程走通。update要allowEdit,insert要allowCreate,upsert 两者都要)。⭐ 这样把「按哪个动词把关」放在知道写入模式的那一层,而不是在不知道的工具栏层。requiredlookup。export: 'allowRead'(同一处映射,MePermissionsProvider.tsx附近)——本席读到导出映射的是allowRead而不是allowExport,而上报的表里allowExport是显式设过的。⛔ 不在本卡范围,但认领时若发现它是同一处映射表的相邻行,请只记录不顺手改 —— 那是另一个动词的另一次判断。边界
git grep在 objectui 上未收敛到非 CHANGELOG 的实现点)⇒ 那半是上报的读数,不是本席的。本席独立确认的只有上面引的MePermissionsProvider三行。mappingNameand hardcodeswriteMode:"insert", so a named mapping'supsertcan never reach a user objectui#10102(向导硬编码writeMode:"insert",讲的是映射载荷)与本卡(入口被按错的动词把关),并指出 17.2.0 上向导确实发送更新模式 ⇒ 本席同意,不是重复。分诊席声明:本席只分类/定级/路由,⛔ 不认领、⛔ 不派工、⛔ 不写码、⛔ 不合并、⛔ 不裁决决策箱卡。
Generated by Claude Code