Repository navigation
fix(plugin-grid,data-objectstack,types): the import wizard's Download template requests the server's import template - #11556
Conversation
… button requests the server's import template The upload step's "Download template" built a CSV of every field it was handed, system and read-only columns included, which the server strips on import. The template is now the server's own (GET /data/:object/export?template=true), so the client-built copy is gone. - types: DataSource gains the optional downloadImportTemplate(resource). - data-objectstack: ObjectStackAdapter implements it on the export route's request path (shared with exportDownload) and stamps the client locale the way the import request does, so both carry the same Accept-Language. - plugin-grid: buildImportTemplateCsv, exampleForField, firstOptionValue and downloadTextFile are deleted; the button is offered when the data source can fetch the template and the user can create records of the object, and a 403 / 405 / other failure shows its own message with no fallback file. importTemplate.test.ts goes; its autoMapColumns case moves to autoMapColumns.test.ts. - app-shell: the identity-import wrapper withholds the template explicitly. - i18n: the hint no longer says CSV; two failure messages added. Claude-Session: https://claude.ai/code/session_01FjqrwXPfSMkSfkKYDSRkN2 Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FjqrwXPfSMkSfkKYDSRkN2 Co-authored-by: Claude <noreply@anthropic.com>
…ll sites The dead-key census could not see keys returned through a helper; reading them through t() literals puts both under check:i18n-keys. Claude-Session: https://claude.ai/code/session_01FjqrwXPfSMkSfkKYDSRkN2 Co-authored-by: Claude <noreply@anthropic.com>
…mport-template-endpoint Claude-Session: https://claude.ai/code/session_01FjqrwXPfSMkSfkKYDSRkN2 Co-authored-by: Claude <noreply@anthropic.com>
|
changeset-claim-re-read
|
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: Read-only, at the tier: card objectui#9600 (body and all twelve comments, the seat ACCEPT ① Derived judgmentsPublished surface, each change named and judged:
② Semver level
Changeset prose checked against the diff and the published client: "objectstack 17.6.0" (the lockfile at ③ Boundary flagsDev deviations (report
Open questions, each answered:
Out-of-scope findings, acknowledged, not filed: Residual the record names so the seat does not read more into PASS than it says: card 验收 3 (a filled template re-imports clean) is NOT MEASURED live in this PR. What this head pins is each half it depends on: the Implemented-by: VERDICT: PASS |
|
Generated by Claude Code |
Fixes #9600
Clause-②: yes
The import wizard's 「下载模板」 (Download template) button now downloads the server's import template (
GET /api/v1/data/:object/export?template=true, an xlsx workbook) instead of building a CSV of every field it was handed. The CSV listed system and read-only columns that the server strips on import, so users filled in columns that were then dropped without an error. The server template lists only the columns this caller can import, marks required ones with*, and adds dropdowns and an instructions sheet. Net code in the wizard goes down: four helpers and their test file are deleted.Published surface, for the contract review
Added
@object-ui/types: optional memberDataSource.downloadImportTemplate(resource: string), returning a Promise that resolves to aBlob. Its doc says when to offer it (a user who can create records), that the server answers 405 / 403, and that an adapter must send it in the locale itsimportRecordsuses. No client fallback: when it is absent, no template is offered.@object-ui/data-objectstack: public methodObjectStackAdapter.downloadImportTemplate(resource). It reuses the export route's request path. A new private helper,fetchExportBlob, is now shared withexportDownload; it is not published, andexportDownload's wire is unchanged (exportDownload.test.tspasses as is).@object-ui/i18n: keysgrid.import.templateDownloadFailedandgrid.import.templateNotPermitted, in all ten packs.Changed
@object-ui/i18n: the value ofgrid.import.downloadTemplateHintin all ten packs. It said "CSV", which this change makes false.@object-ui/plugin-grid:ImportWizardbehaviour. It offers the button only whendataSource.downloadImportTemplateis a function andusePermissions().can(objectName, 'create')is true. Before, it offered the button wheneverfieldswas non-empty. A failure shows a message inline on the upload step.@object-ui/plugin-grid: the doc comment onImportWizardProps.fields[].optionsno longer says that the template reads it. The member is kept; see Acceptance notes.Deleted
@object-ui/plugin-grid, module-private (not exported from the package):buildImportTemplateCsv,exampleForField,firstOptionValue,downloadTextFile, and the__testables.buildImportTemplateCsvgetter (__testablesis internal).downloadTextFilehad no other caller. A blob-savingdownloadBlobtakes its place for the server's response.Unchanged:
ImportWizardPropshas no prop added or removed. TheImportWizardexport andimportTargetFieldsare unchanged, as the card requires, so the mapping step's targets, including thematchOnlyones, are untouched.Route, and why (PM hypotheses, measured)
readTemplateMode,answerImportTemplate). It refuseslimit,page,filter,search,searchFields,orderbyandheader, and anyformatother than xlsx, on a template request. So the adapter sendstemplate=trueand nothing else.exportDownloadwrites only the query parameters it names. I chose a dedicated optional method over atemplateflag onExportDownloadRequest. With a flag, the type would accept combinations the server refuses with a 400 (templateplusfilter,sort,limitorformat: 'csv'). A method has no parameters to combine, and the wizard can feature-detect it.@objectstack/clientexport used. Neither 17.5.0 nor 17.6.0 has a template request: 17.6.0'sdata.exporthas notemplateoption. So the route did not change whenmainmoved to 17.6.0 (objectui#11531 merged during this run). The request goes through the adapter's ownfetchImpl, the same pathexportDownloaduses.importRecords, throughclient.data.import) gets itsAccept-Languagefrom the client's locale when one is set, and otherwise from the host'sfetch. The console'screateAuthenticatedFetchstampsAccept-Languagefromhtml lang. The adapter is built with that samefetch, and no objectui host sets a client locale. The template request goes throughfetchImpland does not pass through the client, so it now stampsclient.getLocale()the way the client does.downloadImportTemplate.test.tsasserts that the two requests carry the sameAccept-Language, with and without a client locale. Within one wizard session the two requests cannot differ: a language switch remounts the metadata subtree (MetadataProvider key={language}), and that unmounts the wizard. Across sessions they can differ; see the open question in the report.ObjectView, the wizard opens only onobjectCanImport(affordances.importandcan(object, 'create')) or on the identity-import path.ExcelImportBarmounts it with no permission check. The gate now sits in the wizard, so every host gets it, through the sameusePermissions().canthatObjectViewreads. With no PermissionProvider,can()answers true and the server decides; this fail-open default matches every sibling gate. 405 showsgrid.import.notAllowed, the message the import already shows for 405. 403 showsgrid.import.templateNotPermitted. Anything else showsgrid.import.templateDownloadFailed. In every case no file is saved.importTemplate.test.tsis deleted. ItsautoMapColumnscase tests live code: the server template also heads a required column with its label plus*. That case moves toautoMapColumns.test.ts, with a control case.content/,docs/, the package READMEs andskills/). The only false sentence was the i18n hint, which is now updated.Files beyond the claim's declared surface
packages/types/src/data.ts: the newDataSourcemember. The adapter method's contract lives on the interface the wizard feature-detects through; without it, the wizard would call an undeclared member.packages/app-shell/src/views/identityImport.ts: one line,downloadImportTemplate: undefined, in the identity-import wrapper. The server's template describes the generic import door of the object, not the identity pipeline, whose targets are curated. The wrapper already nulls the job surfaces it must not offer. AnObjectStackAdapterbase would not carry the method across the spread anyway, because the method is on the prototype. A plain-object base would carry it, and the new test case pins exactly that.packages/app-shell/src/views/ObjectView.tsxis not touched. The gate moved into the wizard (point 5).Deviations from the dispatch's suggested route
Content-Disposition. The method returns aBlob, the same shape asexportDownload. The wizard keeps its localizedgrid.import.templateFileNameplus.xlsx, for exampleContact-import-template.xlsx.plugin-gridhas no toast dependency, and the card says no new frontend dependencies.Tests and gates (on
20a1b5b, the merge oforigin/main6158e4c93)turbo run build --filter='@object-ui/app-shell^...'exit 0, 28 tasks. The types dist carriesdownloadImportTemplate.plugin-gridtype-checks code that reads the new member through the built@object-ui/typesd.ts, so that d.ts is the rebuilt one.@object-ui/types,@object-ui/data-objectstack,@object-ui/plugin-grid,@object-ui/i18n,@object-ui/app-shell.--listFilesOnlyshows the new test files in the type-check programs.vitest run packages/data-objectstack/ packages/plugin-grid/plus the three app-shell import files (identityImport.test.ts,identityImportSavedMapping.test.tsx,importTargetFields.test.ts): 262 files and 2774 tests passed.vitest run packages/i18n/ packages/types/: 425 files passed (10564 tests passed, 13 skipped). The full app-shell suite was not run locally and is declared to CI: the diff touches one app-shell line and its test.ablation-replace.mjs, each restored with blob equal to HEAD andgit diff HEADempty:can(objectName, 'create')turns exactly the "not offered to a user who cannot create" case red (1 failed, 7 passed).downloadImportTemplate: undefinedturns exactly its new case red (1 failed, 19 passed).check:i18n-keys,check:i18n-drift(every changed en value followed by all nine packs),check:i18n-dead-keys(the two new keys are not candidates),check:unused-deps,check:control-bytes,check:new-line-citations(0 new),check:changeset-claims(two pending bodies nameen.tsanddata.ts; both paragraphs are about other keys and types and are still true),check:pending-changeset-literals,changeset:check,check-changeset-overwrite,check-changeset-presence,check-test-path-roots,check:phantom-deps,check:self-import,check:unreferenced-sources, the threevi-mockgates,check-type-check-coverageandcheck-lint-coverage.eslinton the 18 changed.ts/.tsxfiles that still exist, with the same configpnpm lintuses. The JSON count is 18 files, 0 errors, 0 ignored. The config enables no type-aware linting (noparserOptionsorprojectServiceineslint.config.js), so this diff cannot change a verdict on an untouched file.check:readme-exports: prerequisite not met, because it needs every package's dist. No README or package barrel changed.check-eager-locale-catalogues: prerequisite not met, because it needs the console build.Acceptance (card 验收 1–4)
templateColumns, verified upstream. The wizard builds no columns.importTargetFieldsis untouched, somatchOnlytargets stay mappable for upsert matching.*headers map back to their fields (asserted). The live round trip is NOT MEASURED, as above.package.jsonchanged, andcheck:unused-depsandcheck:phantom-depspass.Acceptance notes (not filed)
ImportWizardProps.fields[].options, and theoptionsthatimportTargetFieldspasses through, now have no reader. The template was their only reader, and the card keepsimportTargetFieldsunchanged. Retiring both together is a follow-up. Carrier: none.exportDownload, and now the template) does not send the client'sX-Environment-Id, whichclient.data.importdoes when an environment is set. No objectui host sets one, so this is dormant. Carrier: none.Session:
https://claude.ai/code/session_01FjqrwXPfSMkSfkKYDSRkN2Generated by Claude Code