Skip to content

[Feature]: regenerate-metadata CLI (refresh Metadata sheet from DHIS2) - #409

Merged
MiquelAdell merged 21 commits into
developmentfrom
feature/regenerate-metadata
Jul 30, 2026
Merged

[Feature]: regenerate-metadata CLI (refresh Metadata sheet from DHIS2)#409
MiquelAdell merged 21 commits into
developmentfrom
feature/regenerate-metadata

Conversation

@gqcorneby

@gqcorneby gqcorneby commented Jul 10, 2026

Copy link
Copy Markdown

📌 References

📝 Implementation

  • Add a regenerate-metadata use case + CLI script that refreshes only the Metadata sheet of an existing template from fresh DHIS2 metadata, leaving every other sheet (custom form, dropdowns, VBA) untouched.

  • SheetBuilder.generateMetadataOnly clears and refills the Metadata sheet in place (not deleted) so the sheet's codeName / VBA binding survives.

  • CLI flags: --dhis2-url, --auth, --input, --output, --form-id, --form-type, --language, plus the metadata-generation toggles --include-codes, --use-codes, --org-unit-short-name (all default off).

  • yarn regenerate-metadata alias; README documentation.

  • Bumps @eyeseetea/d2-api to 1.21.0.

  • Extracted MaintenanceSection from Settings page and added regenerate option there

🔥 Notes for the reviewer

📹 Screenshots/Screen capture

For testing, i used
PHSM - Policy Tracker.xlsm
this file where these metadata rows are removed

image
2026-07-29.12-27-11.0.02.-.1.11.mp4

📑 Others

Per-template flag (default false) that adds a "Code" column after
"Metadata version" in the generated Metadata sheet, populated with
item.code. Additive: existing Metadata columns and the =_<id> defined
names are unchanged when off. Enables looking up option codes by name
from the Metadata sheet (e.g. for building composite identifiers).

Mirrors the generateMetadata wiring: entity -> TemplateWebRepository ->
DownloadTemplateUseCase -> SheetBuilder. Adds the first SheetBuilder
unit test covering the column on/off behaviour.
Regenerate only the Metadata sheet of a custom template from fresh DHIS2
metadata (with the Code column), leaving every other sheet — custom form,
dropdowns, VBA — untouched, so it can be refreshed without rebuilding the
whole template.

- SheetBuilder.generateMetadataOnly(): loads the input workbook, clears the
  Metadata sheet IN PLACE (not delete, so its codeName/VBA binding survives)
  and refills it via fillMetadataSheet with includeMetadataCodes on.
- Workbook.clear() and a cross-environment base64 loader/writer
  (fromBase64Data / writeToBase64) that work in both Node and the browser.
- RegenerateTemplateMetadataUseCase wired in CompositionRoot; reusable from
  the web app.
- src/scripts/regenerate-metadata.ts CLI (cmd-ts), same pattern as
  import-multiple-files.ts.
- Unit test covering the refresh + preservation of other sheets.
Bakes in the ts-node --transpile-only --project src/scripts/tsconfig.json flags so the script runs with: yarn regenerate-metadata --dhis2-url ... --input ... --output ... --form-id ...
Expose the three SheetBuilder flags that affect Metadata-sheet output as
CLI flags on regenerate-metadata, instead of hardcoding them in the use
case: --include-codes (Code column), --use-codes (codes instead of names),
--org-unit-short-name (short vs regular OU name). All default off.
Other SheetBuilder params only affect full generate(), not metadata-only.
@gqcorneby gqcorneby changed the title feat(scripts): regenerate-metadata CLI (refresh Metadata sheet from DHIS2) [Feature]: regenerate-metadata CLI (refresh Metadata sheet from DHIS2) Jul 10, 2026
@gqcorneby
gqcorneby requested a review from MiquelAdell July 10, 2026 11:53
@MiquelAdell
MiquelAdell requested a review from anagperal July 28, 2026 06:39

@anagperal anagperal left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks @gqcorneby ! Onle a few comments

Comment thread src/domain/usecases/RegenerateTemplateMetadataUseCase.ts
Comment thread src/scripts/regenerate-metadata.ts Outdated
console.debug(`Regenerating Metadata for ${args.input} (form ${args.formId})`);

const outputBase64 = await compositionRoot.templates.regenerateMetadata(api, {
type: args.formType as DataFormType,

@anagperal anagperal Jul 28, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of as you can declare:

formType: option({
    type: oneOf(dataFormTypes),
    long: "form-type",
    defaultValue: () => dataFormTypeMap.trackerPrograms,
   description: "Data form type: trackerPrograms | programs | dataSets (default: trackerPrograms)",
})

Base automatically changed from feature/metadata-code-column to development July 28, 2026 11:59
Use cmd-ts oneOf(dataFormTypes) so --form-type is validated at parse
time and the DataFormType cast at the call site disappears.

Also document why the use case imports Settings and SheetBuilder from
webapp/logic; fixing the layering needs a wider refactor.
The Code column is written for org units too, but the fixture passed an
empty organisationUnits list so that branch was never exercised.

Addresses review feedback on #408.
Buffer does not exist in the browser bundle, so the helper threw
"Buffer is not defined" for any caller outside Node. It had no callers
until now, which is why this went unnoticed.
Shared by more than one use case now, so keep a single definition.
The import page's drop area is now a component, so other screens can
reuse it instead of copying the render prop and its styles.
Identifies the template and its data form from the file alone, so a
caller does not have to ask the user which template it is.

AnalyzeTemplateUseCase resolves the same way but then reads every data
value, which is unnecessary when only the template is wanted.
Adds a Regenerate template metadata row to the Maintenance list. The
dropped file identifies the template, the generation flags are editable,
and the regenerated workbook is downloaded; nothing is persisted.

Until now this was only possible through the regenerate-metadata CLI,
which needs a checkout and DHIS2 credentials on the command line.

The Maintenance section moves out of SettingsFields into its own
component to host the new row.
@gqcorneby

Copy link
Copy Markdown
Author

Thanks @anagperal! Addressed the comments. The scope of the PR increased a bit 😅 After discussing with Miquel, we'll expose this regenerate in settings page as a maintenance function.

image

…rate-metadata

# Conflicts:
#	i18n/en.pot
#	i18n/es.po
#	i18n/fr.po
#	i18n/pt.po
#	i18n/ru.po
#	src/test/sheetBuilder.spec.ts
#	src/webapp/logic/sheetBuilder.ts
@gqcorneby
gqcorneby requested a review from anagperal July 29, 2026 04:48
@bundlemon

bundlemon Bot commented Jul 29, 2026

Copy link
Copy Markdown

BundleMon

No change in files bundle size

Groups updated (1)
Status Path Size Limits
Build Folder
./**/*
1.74MB (+2.63KB +0.15%) +20%

Final result: ✅

View report in BundleMon website ➡️


Current branch size history | Target branch size history

@anagperal anagperal left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks @gqcorneby ! a lot of work done here! Just a few comments

const loading = useLoading();
const classes = useStyles();

const [resolution, setResolution] = useState<ResolutionState>({ type: "empty" });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This component has also the logic presentation here, move it to a custom hook to have only the render logic

private excelRepository: ExcelRepository
) {}

public async execute(file: File): Promise<ResolvedTemplate> {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method shares some code with AnalyzeTemplateUseCase. Maybe you can extract it to a helper

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ruled out reusing AnalyzeTemplateUseCase itself but I missed the shared code. Thanks Ana!

AnalyzeTemplateUseCase and ResolveTemplateFromFileUseCase opened with
the same load-template and resolve-data-form steps.

Kept as two helpers rather than one so the resolve use case can wrap
only the lookup in its friendly error, leaving the data form errors
distinct. Neither helper handles errors, so both use cases surface the
same messages as before.

Addresses review feedback on #409.
The dialog is now render-only; the drop handling, flags, regeneration
and dismissal guard live in useRegenerateMetadata, following the shape
of useMaintenanceCleanup.

Addresses review feedback on #409.
Moving strings between files reorders the extraction; msgmerge also
re-fuzzed one entry onto an unrelated translation, cleared here so it
falls back to English.
@gqcorneby
gqcorneby requested a review from anagperal July 29, 2026 11:58

@anagperal anagperal left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks @gqcorneby !!

@MiquelAdell
MiquelAdell merged commit 14feb4e into development Jul 30, 2026
11 checks passed
@MiquelAdell
MiquelAdell deleted the feature/regenerate-metadata branch July 30, 2026 07:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants