feat(data-collections): tag samples from a metadata sheet during import#840
Open
diegomayorga-dept wants to merge 1 commit into
Open
feat(data-collections): tag samples from a metadata sheet during import#840diegomayorga-dept wants to merge 1 commit into
diegomayorga-dept wants to merge 1 commit into
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Title*
[EGV-215] Tag samples from a metadata sheet during import
Type of Change*
Description
Adds a new, optional "Tags" step to the sample-import wizard (
EGImportDataWizard.vue), between "Group files" and "Review samples", so a lab tech can tag a batch of samples from their own metadata sheet at import time instead of tagging one-by-one afterward.Flow: upload a sheet → map which column holds the sample name and which holds the tag(s) → rows are joined to the file-derived proposed samples by normalized name → the preview shows tags to be applied/created, per-tag sample counts, typo warnings, and two unmatched reports (sheet rows with no matching sample, samples with no sheet row) → on confirm, any missing tags are created (
createTag) and the resolvedTagIds are passed into the existingbulkCreateSamplesrequest's per-sampleTagIdsfield.Notably, this required no new backend endpoint or route —
bulkCreateSamplesalready accepted and applied a per-sampleTagIdsarray (laboratory-sample-service.ts), but nothing in the wizard populated it. This PR wires that existing path up from the UI.New files:
shared-lib/src/app/utils/delimited-text.ts— small RFC-4180-style CSV/TSV parser (quoted fields, escaped quotes, CRLF/LF, trailing blank lines). No parsing library was in use anywhere in the repo, and the existingfile.text()+splitconvention can't handle quoted commas, so this is new but intentionally minimal.front-end/src/app/utils/levenshtein.ts— edit-distance helper for "possible typo" warnings.front-end/src/app/utils/sheet-tag-matching.ts— the core pure logic: name-matching, tag-cell parsing/dedup, existing-vs-new tag resolution, rejectingworkflow/batch/permanent-kind tags with a per-tag reason, and typo detection.Modified:
EGImportDataWizard.vue— new step, file upload + column-mapping UI, preview panel, and wiringresolveSampleTagIds()intoconfirmImport(). Inserting the step renumbered the two steps after it (Review samples3→4,Confirm4→5); all step-index references (watch(step, ...), templatev-else-ifs, footer nav buttons) were updated accordingly.laboratory-data-tagging-service.ts— added apermanent-kind guard toapplyTagsToSamples(add and remove loops). The service already rejectedworkflowandbatchtags on samples;permanentwas not blocked. Defense-in-depth: the front-end also excludes permanent tags from creation/application in the preview.EGSamplesTab.vue,EGSampleTagSidebar.vue— refactored to use a new sharedTAG_PRESET_COLORS/DEFAULT_TAG_COLORconstant (shared-lib/constants/data-collections.ts) instead of each keeping its own copy of the same hardcoded palette. This PR's new "create missing tag" code is a third consumer of that palette, so it was extracted per the project's DRY rule. No visible behavior change — same colors, same default.Scope decisions (from ticket grooming — see design doc): samples only (files deferred), import-time only (a "reconcile already-imported samples" dialog is deferred to reuse the same matching util), create-then-apply for unmatched tags (no forced vocabulary), and no saved/named column-mapping presets in this PR (deferred as a fast-follow).
Testing*
Automated (all run and passing):
delimited-text.test.ts— 7/7 passing (quoted commas, escaped quotes, CRLF/LF, trailing blank lines, tab delimiter).levenshtein.test.ts— 5/5 passing.sheet-tag-matching.test.ts— 8/8 passing, including a constructed case with tags of every rejected kind (workflow/batch/permanent) mixed with an accepted tag on the same sample.laboratory-data-tagging-service.test.ts— newpermanent-guard test passing; full file re-run 39/39, no regressions.Manual (performed against the local dev server + real AWS, in this test lab):
"E. coli, Needs Review") and one row naming a non-existent sample, mapped columns, confirmed the preview classified both tags correctly and reported the extra row as unmatched, completed the import, and verified on the Samples tab that the sample was persisted with both tags.workflow/permanent-kind tag rejection — this test lab had no pre-existing tag of either kind, so a sheet cell naming one would just be treated as a new tag rather than exercising the guard. That exact branch (including the mixed accepted/rejected case) is covered by the unit test above.superpowers/2026-07-10-bulk-tag-from-sheet-during-import-test-log.md(kept outside the repo per project convention).Not done:
qualityenvironment, not locally, so none was run as part of this change. A follow-up spec mirroring the manual happy-path steps is noted as a TODO in the test log — no existing E2E coverage of the import wizard was found to extend from..vuefiles could not be run in the implementation environment (missingparserOptions.extraFileExtensionsfor.vue) — confirmed as a pre-existing gap reproducible on untouched.vuefiles, and the repo's actuallintscript only targets.ts/.jsanyway, so this is not a gap introduced by this PR.Impact
createTagcalls (N = distinct new tag names in the sheet, not sample count), then the existing singlebulkCreateSamplescall — no new endpoint, no new chunking logic.EGSamplesTab.vueandEGSampleTagSidebar.vuenow import their preset colors from a shared constant instead of a local literal array — same values, no visible change.Additional Information
This PR resulted from reconciling an initial ticket draft (fixed
Identifier,TagNamesCSV, match-existing-only, new backend endpoint) against actual grooming feedback, which asked for format-agnostic column mapping with tag creation and a preview — a materially different and smaller-backend design. The full design rationale, the corrected technical premises, the implementation plan, and the manual test log are kept outside the repo undersuperpowers/per this project's CLAUDE.md convention (2026-07-10-bulk-tag-from-sheet-during-import-{design,plan,test-log}.md).Known pre-existing issues surfaced during manual testing, out of scope here: the default file-grouping regex preset doesn't match filenames with a trailing underscore before the extension (worked around via the wizard's existing editable regex field); and the wizard's auto-generated batch name (
upload-<date>) can collide with a batch tag already created earlier the same day, throwing a 409 fromsetBatchForSamples(per-sample tag application happens earlier in the same request, so this does not prevent tags from being applied).Checklist*
.vuefiles could not be verified in the implementation sandbox — pre-existing environment gap, not introduced here; the repo's ownlintscript doesn't target.vuefiles)