Conversation
… for org units On /api/metadata, un-prefixed fields/filter/order params are applied to every exportable schema, not only to the type being queried. Since DHIS2 43 the query engine resolves translatable order properties against each schema (displayName -> name, displayShortName -> shortName), so the request failed with a 400 on schemas that do not map those columns, e.g. ApiToken and OptionGroupSet. Query /api/organisationUnits instead, so fields/filter/order can only ever affect a single schema. paging: false is required: /api/metadata never paginates, but /api/organisationUnits defaults to 50 per page and the chunks here are up to 400 ids, so omitting it would silently truncate the generated templates.
Pre-existing formatting drift picked up by `yarn prettify`. Whitespace only, no behaviour change.
BundleMonNo change in files bundle size Unchanged groups (1)
Final result: ✅ View report in BundleMon website ➡️ |
xurxodev
left a comment
There was a problem hiding this comment.
Thanks @anagperal
@adrianq — approving, the fix is correct and well scoped.
Unrelated to this PR, but worth mentioning while we're in this file: DownloadTemplateUseCase uses D2Api directly, which breaks the Clean Architecture dependency rule. It's not just an import — the api is the first parameter of execute:
public async execute(api: D2Api, options: DownloadTemplateProps): Promise<void>Because it sits in the public signature, the coupling propagates upwards: the two webapp callers pull api from useAppContext() solely to forward it. The use case already receives 6 repositories through its constructor in CompositionRoot.ts:110, so the right pattern is already there; api is the only one travelling through the wrong channel.
A clear candidate for a refactor. Leaving the details here so they don't get lost:
Remove the d2-api usage from the use case and apply the dependency rule properly using the repository pattern.
Callers — only two, and trivial since they just forward api: DownloadTemplatePage.tsx:69 and TemplateListTable.tsx:154. On top of that, getExecute (CompositionRoot.ts:170) derives the public type from the execute signature, so removing the parameter makes both fail to compile — none can be missed.
📌 References
📝 Implementation
Why the 400 happened
The request went to the wrong endpoint. On
/api/metadata, un-prefixedfields,filterandorderare defaults applied to every exportable schema, not to the type you care about. The v43 docs say it literally — "Default order to apply to all types" — and the wording is identical in the 2.41 docs, so this call was always incorrect. It was simply harmless before:filter=id:in:[...]matched nothing in the other schemas, so they came back as empty arrays and onlyorganisationUnitswas visible in the response.Why it broke in v43 specifically
Comparing
JpaCriteriaQueryEngine.javaacross the dhis2-core release branches:root.get(property.getFieldName())with nodisplay*handling at all. Ordering bydisplayNamenever reached the database.tryGetTranslatableOrder/handleDisplayProperty(absent in both earlier branches) to push translatable ordering down into JPA.The final fallback in
handleDisplayPropertycallsgetRegularOrderwithout checking that the entity actually maps the column.displayNameresolves tonameanddisplayShortNametoshortName, so applying it across all schemas ends up callingroot.get("shortName")onOptionGroupSetandroot.get("name")onApiToken, which don't map those columns. DHIS2'sSchemaclaims the property exists; the Hibernate entity doesn't have it.v43 also upgraded Hibernate 5 → 6, whose
AbstractManagedType.checkNotNullthrows where the old metamodel tolerated the mismatch. That's the top frame of the stack trace, and the reason this surfaces as a 400 rather than being silently ignored.🔥 Notes for the reviewer
🎨 Screenshots
ERROR:
FIXED:
📑 Others