ENG-3300: Add label column to MessagingTemplate for multi-template support#7900
ENG-3300: Add label column to MessagingTemplate for multi-template support#7900
Conversation
…pport Add a `label` column to the MessagingTemplate model enabling multiple named templates per MessagingActionType. Includes migration with backfill from DEFAULT_MESSAGING_TEMPLATES, unique constraint on (type, label), dedup logic for existing rows, and label wiring through all CRUD paths. - Model: add label column + UniqueConstraint(type, label) - Migration: 3-phase (nullable add → backfill → NOT NULL + constraint) - Schemas: label on all response schemas, optional with default on request - CRUD: new get_templates_by_type(), _validate_unique_label(), label wired through create/update/patch/defaults paths - Backward compatible: label defaults to "Default" if not provided Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. 2 Skipped Deployments
|
- Add label to all 5 messaging template test fixtures (NOT NULL constraint) - Use template.label from DB instead of hardcoded dict lookup in endpoints - Fix migration docstring revision ID mismatch Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Column default="Default" ensures existing code paths that create MessagingTemplate without explicitly providing a label still work. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove tests that relied on multiple templates sharing the same (type, label), which is now prevented by uq_messaging_template_type_label. Rewrite conflicting-template test to use distinct labels inline. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #7900 +/- ##
==========================================
- Coverage 85.06% 85.05% -0.01%
==========================================
Files 629 629
Lines 40860 40900 +40
Branches 4748 4756 +8
==========================================
+ Hits 34759 34789 +30
- Misses 5029 5037 +8
- Partials 1072 1074 +2 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
- Use `is not None` instead of `or` for label fallback to avoid silently swallowing empty-string labels - Split test_get_templates_by_type into separate match/no-match tests Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When creating a property-specific messaging template without an explicit label, auto-generate a unique label (e.g. "Subject identity verification (2)") instead of colliding with the DB unique constraint on (type, label). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review: Add label column to MessagingTemplate
This PR is well-structured. The migration follows the safe nullable→backfill→not-null pattern, the service layer provides good backward-compatibility (preserving labels on PUT when clients don't send one), uniqueness is enforced at both the application and DB level, and the test coverage in TestMessagingTemplateLabels is thorough.
A few issues worth addressing before merge:
Medium — Fix before merge
1. Model-level default="Default" is misleading (messaging_template.py:156)
The string "Default" doesn't match any label in DEFAULT_MESSAGING_TEMPLATES. If a row is ever created via ORM without an explicit label, it silently gets "Default" rather than the meaningful human-readable label. The service layer always supplies a label, so this default is redundant and could cause confusion. Recommend removing it entirely or at least aligning it with what the service layer would assign.
2. Empty-string label bypasses uniqueness check in patch_property_specific_template (messaging_crud_service.py:313)
if new_label and ... treats "" as falsy, so a PATCH with {"label": ""} skips _validate_unique_label and would store an empty string (satisfying NOT NULL but semantically invalid). Should be if new_label is not None and .... Should be paired with a Pydantic validator in the schema.
3. No blank-string validation on label in Pydantic schemas (messaging.py:184)
label: str | None = None accepts "" and whitespace-only strings. A min_length=1 or @field_validator at the API boundary would catch this before it reaches the service layer.
Low — Nits / FYI
4. TOCTOU window in create_property_specific_template_by_type (messaging_crud_service.py:355)
_next_available_label and _validate_unique_label are two separate reads. Under concurrent requests for the same type, both could select the same "available" label; one would then hit the DB unique constraint as an IntegrityError instead of the controlled MessagingTemplateValidationException. The DB constraint is the correct hard stop, but consider catching IntegrityError at the endpoint and converting it to a 422 so the API surface stays consistent.
5. DEFAULT_LABELS in migration is intentionally frozen, not "must stay in sync" (migration:61)
The comment "Must stay in sync with DEFAULT_MESSAGING_TEMPLATES" is subtly wrong — migrations are point-in-time and should never be updated. Clarifying the comment to "these values are frozen as of this migration and intentionally do not track future label renames" would prevent a future developer from editing the migration to "sync" it.
6. or fallback in get_all_basic_messaging_templates (messaging_crud_service.py:204)
template_from_db.label or template["label"] silently falls back on an empty-string label. Unlikely to matter given the NOT NULL constraint but worth using if ... is not None for clarity.
Overall the approach is sound. The two medium items (empty-string bypass and misleading ORM default) are worth fixing before merge; the rest are low-priority nits.
🔬 Codegraph: connected (46727 nodes)
💡 Write /code-review in a comment to re-run this review.
|
Thanks for the thorough review. Addressed all findings:
|
- Change model default from "Default" to "Unnamed" for clarity - Fix migration comment to reflect frozen point-in-time values - Add min_length=1 validation on label schema fields - Wrap template create in try/except IntegrityError for race safety - Drop unnecessary `or` fallback in get_all_basic_messaging_templates - Fix falsy check to `is not None` in patch label validation - Add parametrized test for auto-label numbering (covers while loop) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Ticket ENG-3300
Description Of Changes
Enables multiple named templates per
MessagingActionTypeby adding alabelcolumn to theMessagingTemplatemodel. This is the backend foundation for the multi-template selector UI (correspondence templates like "Request for ID", "Status Update", "Case Closure").Key decisions:
labelisNOT NULLwith aUniqueConstraint(type, label)— no two templates of the same type can share a labeldefault="Default"so existing code paths that create templates without a label still workDEFAULT_MESSAGING_TEMPLATESdict labels, with title-case fallback for non-default types and dedup suffixing for duplicateslabelto"Default"for backward compatibility — existing FE code continues to work without changesget_templates_by_type()query returns all templates for a given type (vs existing_basic_messaging_template_by_typewhich returns only one)IntegrityErrorDependency: Part of RMP-142 (Data Subject Correspondence). Requires ENG-3299 to merge first for the
CORRESPONDENCEentry inDEFAULT_MESSAGING_TEMPLATES— this PR's migration backfill will work correctly regardless of merge order.Code Changes
messaging_template.py): Addedlabelcolumn withdefault="Default"andUniqueConstraint("type", "label")d71c7d274c04): 3-phase migration — add nullable column → backfill all rows → set NOT NULL + unique constraint. Handles dedup for multiple templates sharing the same type.messaging.py): AddedlabeltoMessagingTemplateWithPropertiesBase,MessagingTemplateDefault,MessagingTemplateWithPropertiesBodyParams(default"Default"),MessagingTemplateWithPropertiesPatchBodyParams(optional)messaging_crud_service.py): Newget_templates_by_type()and_validate_unique_label(). Wiredlabelthrough all create/update/patch/defaults paths with backward-compatible fallbacks.messaging_endpoints.py): Updatedget_basic_messaging_templatesandget_messaging_template_by_idto returnlabelfrom the DB model instead of hardcoded dict lookup.test_messaging_crud_service.py): Removed tests that relied on multiple templates sharing the same(type, label)— no longer valid with the unique constraint. Rewrote conflicting-template test to use distinct labels.Steps to Confirm
Migration round-trip (OSS fides or fidesplus):
alembic upgrade head— verify thelabelcolumn exists onmessaging_templatetableSELECT type, label FROM messaging_template ORDER BY type;— all rows should have labels matchingDEFAULT_MESSAGING_TEMPLATES(e.g., "Subject identity verification", "Privacy request received")SELECT conname FROM pg_constraint WHERE conname = 'uq_messaging_template_type_label';— should return 1 rowalembic downgrade -1— verify column and constraint are removedalembic upgrade head— verify idempotent backfill (same labels as step 2)Backward compatibility (no label in request):
6.
PUT /api/v1/messaging/templateswith existing payload (nolabelfield, body is a list) — should succeed, templates get label from defaults or existing DB value7.
POST /api/v1/plus/messaging/templates/subject_identity_verificationwith body{"content": {"subject": "test", "body": "test"}, "is_enabled": false}(nolabel) — should succeed, label defaults to "Default"Multi-template support:
8.
POST /api/v1/plus/messaging/templates/subject_identity_verificationwith body{"label": "Custom verification", "content": {"subject": "test", "body": "test"}, "is_enabled": false}— should succeed with label "Custom verification"9. Repeat step 8 with same label — should return 400 with message "A template with type 'subject_identity_verification' and label 'Custom verification' already exists."
10. Repeat step 8 with a different label (e.g., "Another verification") — should succeed (multiple templates, same type, different labels)
Label in responses:
11.
GET /api/v1/messaging/templates— each template in response should have alabelfield12.
GET /api/v1/plus/messaging/templates/summary— each item should includelabel13.
GET /api/v1/messaging/templates/{id}— response should includelabelManual Test Results
All 13 steps verified manually against local OSS fides (steps 1–6) and fidesplus (steps 7–13) by author.
Pre-Merge Checklist
CHANGELOG.mdupdatedmaindowngrade()migration is correct and works🤖 Generated with Claude Code