Skip to content

Preserve Workbench business rule payloads for clearer validation errors - #8048

Open
acwhite211 wants to merge 29 commits into
mainfrom
issue-8045
Open

Preserve Workbench business rule payloads for clearer validation errors#8048
acwhite211 wants to merge 29 commits into
mainfrom
issue-8045

Conversation

@acwhite211

@acwhite211 acwhite211 commented Apr 30, 2026

Copy link
Copy Markdown
Collaborator

Fixes #8045

Improve Workbench handling of back-end business rule validation errors. The back-end now preserves structured BusinessRuleException payloads in FailedBusinessRule upload results instead of stringifying and discarding them. The Workbench front-end uses those payloads to show uniqueness-rule failures and appends the conflicting record IDs when available. This makes duplicate catalog number errors clearer and avoids exposing raw Python exception tuples in validation tooltips.

Checklist

  • Self-review the PR after opening it to make sure the changes look good and
    self-explanatory (or properly documented)
  • Add relevant issue to release milestone
  • Add pr to documentation list
  • Add automated tests

Testing instructions

  • Open a Workbench dataset that triggers a duplicate catalog number uniqueness error.
  • Run validation.
  • Hover or inspect the errored cell.
  • Confirm the message reads like a normal validation error.
  • Confirm the message includes conflicting record IDs when provided.
  • Confirm the tooltip no longer shows the raw Python tuple/dict payload.
  • Proceed with same steps for other validation error types.

Summary by CodeRabbit

  • Bug Fixes
    • WorkBench upload/update/delete results now consistently convert business-rule and validation failures into localized, JSON-safe messages, including “conflicting record IDs”.
    • Business-rule payloads are sanitized to safely handle malformed or nested data without breaking error rendering.
  • Improvements
    • Standardized exception-to-upload-result conversion across WorkBench flows.
    • Frontend message resolution is centralized and applies correct precedence (parsing/field validation overrides business rules).
  • Tests
    • Added backend and frontend coverage for conversion, sanitization, localization, and precedence.

@coderabbitai

coderabbitai Bot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR preserves structured business-rule exception payloads through WorkBench uploads and resolves them into localized frontend messages. It adds payload sanitization, typed upload-result contracts, resolver registries, conflicting-record formatting, and backend/frontend coverage.

Changes

Business Rule Payload Localization

Layer / File(s) Summary
Structured payload conversion and backend wiring
specifyweb/backend/workbench/upload/upload_result.py, specifyweb/backend/workbench/upload/treerecord.py, specifyweb/backend/workbench/upload/upload_table.py
Converts matching exceptions into sanitized FailedBusinessRule results and applies the helper across upload exception handlers.
Typed upload result contracts
specifyweb/frontend/js_src/lib/components/WorkBench/uploadResultTypes.ts
Defines metadata, record outcome variants, mapped results, and recursive relationship result types.
Localized validation message resolution
specifyweb/frontend/js_src/lib/components/WorkBench/resultMessageResolvers.ts, specifyweb/frontend/js_src/lib/components/WorkBench/resultsParser.ts, specifyweb/frontend/js_src/lib/components/LocalityUpdate/utils.ts, specifyweb/frontend/js_src/lib/localization/backEnd.ts
Adds resolver registries, updates validation precedence and attachment delegation, and formats conflicting record IDs.
Payload and message resolution tests
specifyweb/backend/workbench/upload/tests/test_upload_results_json.py, specifyweb/frontend/js_src/lib/components/WorkBench/__tests__/resultsParser.test.ts
Covers conversion, sanitization, serialization, localization, fallback behavior, conflicting IDs, and parsing precedence.

Sequence Diagram(s)

sequenceDiagram
  participant WorkbenchUpload
  participant BackendUploadHandler
  participant to_failed_business_rule
  participant resolveValidationMessage
  participant resultMessageResolvers
  WorkbenchUpload->>BackendUploadHandler: upload record
  BackendUploadHandler->>to_failed_business_rule: exception and ReportInfo
  to_failed_business_rule-->>WorkbenchUpload: FailedBusinessRule with payload
  WorkbenchUpload->>resolveValidationMessage: validation key and payload
  resolveValidationMessage->>resultMessageResolvers: resolve parsing or business-rule message
  resultMessageResolvers-->>WorkbenchUpload: localized validation message
Loading

Suggested reviewers: bhumikaguptaa, emenslin

🚥 Pre-merge checks | ✅ 6
✅ Passed checks (6 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes preserve structured business-rule payloads and add frontend localization for clearer duplicate-catalog validation errors, matching #8045.
Out of Scope Changes check ✅ Passed The refactor and new tests support the same validation-message goal and do not introduce unrelated functionality.
Automatic Tests ✅ Passed New backend and frontend tests cover structured business-rule conversion and validation-message resolution; upload-flow tests already exercise FailedBusinessRule paths.
Testing Instructions ✅ Passed The steps are specific, reproduce the duplicate-catalog-number path, and verify both the localized message and conflicting IDs without raw payloads.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: preserving Workbench business-rule payloads to produce clearer validation errors.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-8045

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@acwhite211 acwhite211 added this to the 7.12.2 milestone Apr 30, 2026
@acwhite211
acwhite211 marked this pull request as ready for review May 18, 2026 21:47
@acwhite211
acwhite211 requested review from a team May 18, 2026 21:47

@emenslin emenslin left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

  • Confirm the message reads like a normal validation error, for example Collectionobject must have unique catalognumber in collection.
  • Confirm the message includes conflicting record IDs when provided, for example Conflicting record IDs: 3347460.
  • Confirm the tooltip no longer shows the raw Python tuple/dict payload.

Although this looks a lot better I do have one change I think is needed. I think that the error should show either the schema caption set by the user (e.g. CIDA Number instead of catalognumber) or the schema config field name (e.g. catalogNumber instead of catalognumber)

@github-project-automation github-project-automation Bot moved this from 📋Back Log to Dev Attention Needed in General Tester Board May 19, 2026
Comment thread specifyweb/backend/workbench/upload/upload_result.py Fixed
@acwhite211
acwhite211 requested a review from emenslin May 21, 2026 19:19

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
specifyweb/backend/workbench/upload/tests/test_upload_results_json.py (1)

41-70: ⚡ Quick win

Extend this test to cover JSON round-trip of preserved payload.

This asserts conversion correctness, but not serialization/schema compatibility for the preserved payload. Add a small UploadResult(...).to_json() -> json.dumps/json.loads -> from_json assertion in the same test.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@specifyweb/backend/workbench/upload/tests/test_upload_results_json.py` around
lines 41 - 70, Extend the testBusinessRuleExceptionPayload to also verify JSON
round-trip: create the FailedBusinessRule via
to_failed_business_rule(BusinessRuleException(...), info), wrap it in an
UploadResult (use the same FailedBusinessRule instance), call
UploadResult.to_json() then json.loads and pass that into
UploadResult.from_json() (or the project equivalent) and assert the deserialized
UploadResult still contains the original FailedBusinessRule payload (compare the
preserved payload dict and message). Ensure you use the existing symbols:
BusinessRuleException, ReportInfo, to_failed_business_rule, FailedBusinessRule,
UploadResult.to_json and UploadResult.from_json for locating the code.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@specifyweb/backend/workbench/upload/upload_result.py`:
- Around line 253-261: The helper is_business_rule_exception_with_payload
currently accepts any dict as the payload (exception.args[1]) which can contain
non-serializable or invalid values; update the logic in
is_business_rule_exception_with_payload (and the similar check around lines
264-268) to validate and/or sanitize the payload shape before treating it as a
business-rule payload: ensure exception.args[1] is a dict whose keys are strings
and whose values are only JSON-serializable scalar types (str, int, float, bool,
None) or plain lists/dicts that recursively satisfy the same constraint, or else
reject it (or replace with a safe fallback like an empty dict or
{"unserializable": true}) so that constructing/encoding FailedBusinessRule will
not fail at runtime. Include references to
is_business_rule_exception_with_payload and the code path that constructs
FailedBusinessRule when applying this validation.

In `@specifyweb/frontend/js_src/lib/components/WorkBench/resultsParser.ts`:
- Around line 305-336: In resolveBackendBusinessRuleMessage, guard against
missing/empty table by checking the result of getStringPayload(payload, 'table')
(assigned to tableName) and return undefined immediately if it's falsy; this
prevents calling getSchemaTableLabel('') and producing empty localized table
labels. Keep the existing branches that use tableName (fieldNotUnique,
childFieldNotUnique) but only execute them when tableName is non-empty so
callers can fall back to the default message.
- Around line 259-269: The message suffix is hardcoded in English inside
withConflictingRecordIds, causing mixed locales; update withConflictingRecordIds
to fetch a localized prefix from backEndText (e.g., add a conflictingRecordIds
key to the backEndText dictionary) and use that localized string (via
localized/backEndText lookup) instead of the hardcoded "Conflicting record IDs:"
before joining payload.conflicting; ensure IR payload handling and existing
localized(message) wrapping remain unchanged so the full tooltip is entirely
localized.

---

Nitpick comments:
In `@specifyweb/backend/workbench/upload/tests/test_upload_results_json.py`:
- Around line 41-70: Extend the testBusinessRuleExceptionPayload to also verify
JSON round-trip: create the FailedBusinessRule via
to_failed_business_rule(BusinessRuleException(...), info), wrap it in an
UploadResult (use the same FailedBusinessRule instance), call
UploadResult.to_json() then json.loads and pass that into
UploadResult.from_json() (or the project equivalent) and assert the deserialized
UploadResult still contains the original FailedBusinessRule payload (compare the
preserved payload dict and message). Ensure you use the existing symbols:
BusinessRuleException, ReportInfo, to_failed_business_rule, FailedBusinessRule,
UploadResult.to_json and UploadResult.from_json for locating the code.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5f43e892-a787-4b63-a34a-af5fa0eda045

📥 Commits

Reviewing files that changed from the base of the PR and between 66b1266 and 3007e56.

📒 Files selected for processing (5)
  • specifyweb/backend/workbench/upload/tests/test_upload_results_json.py
  • specifyweb/backend/workbench/upload/treerecord.py
  • specifyweb/backend/workbench/upload/upload_result.py
  • specifyweb/backend/workbench/upload/upload_table.py
  • specifyweb/frontend/js_src/lib/components/WorkBench/resultsParser.ts

Comment thread specifyweb/backend/workbench/upload/upload_result.py
Comment thread specifyweb/frontend/js_src/lib/components/WorkBench/resultsParser.ts Outdated
Comment thread specifyweb/frontend/js_src/lib/components/WorkBench/resultsParser.ts Outdated

@emenslin emenslin left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

  • Confirm the message reads like a normal validation error, for example Collectionobject must have unique catalognumber in collection.
  • Confirm the message includes conflicting record IDs when provided, for example Conflicting record IDs: 3347460.
  • Confirm the tooltip no longer shows the raw Python tuple/dict payload.

Looks good, I confirmed with CO and a few other tables and all errors seemed to be formatted correctly.

Image Image

@emenslin
emenslin requested a review from a team May 22, 2026 15:19

@bhumikaguptaa bhumikaguptaa left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

  • Confirm the message reads like a normal validation error, for example Collectionobject must have unique catalognumber in collection.
  • Confirm the message includes conflicting record IDs when provided, for example Conflicting record IDs: 3347460.
  • Confirm the tooltip no longer shows the raw Python tuple/dict payload.

Works as expected, ran into no errors.

Image

@github-actions

github-actions Bot commented Jul 27, 2026

Copy link
Copy Markdown

Warning

One or more dependencies are approaching or past End-of-Life.
Please plan upgrades accordingly.

STATUS=WARNING
NODE_VERSION=20
NODE_CYCLE=20
EOL_DATE=2026-04-30
DAYS_REMAINING=-89

--- Node.js ---
Version: 20
EOL: 2026-04-30
Status: WARNING

STATUS=OK
PYTHON_VERSION=3.12
PYTHON_CYCLE=3.12
EOL_DATE=2028-10-31
DAYS_REMAINING=826

--- Python ---
Version: 3.12
EOL: 2028-10-31
Status: OK

STATUS=WARNING
DJANGO_VERSION=4.2
DJANGO_CYCLE=4.2
EOL_DATE=2026-04-07
DAYS_REMAINING=-112

--- Django ---
Version: 4.2
EOL: 2026-04-07
Status: WARNING


@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@specifyweb/backend/workbench/upload/upload_result.py`:
- Around line 285-317: Update _sanitize_business_rule_payload_value and
_sanitize_business_rule_payload so a valid top-level None is distinguished from
a failed sanitization. Use a dedicated sentinel or equivalent success indicator
for invalid values, preserve None in sanitized payloads, and continue skipping
unsupported values and non-string keys.
- Around line 252-282: Restrict the wrapper fallback in
is_business_rule_exception_with_payload to a canonical business-rule marker,
such as localizationKey, instead of accepting generic keys like table or
fieldName. Preserve the direct exception class identity check, but ensure
unrelated two-argument exceptions with database-style payloads are not
classified as business-rule exceptions.

In `@specifyweb/frontend/js_src/lib/components/WorkBench/resultsParser.ts`:
- Around line 37-42: Update the fallback around localized in resultsParser so
unknown business-rule payloads with payload.localizationKey return the readable
backend key without JSON.stringify(payload). Preserve the JSON payload fallback
for non-business-rule validation payloads, and keep the existing empty-payload
behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 756a5fcb-1f93-456f-bacf-746e06e73177

📥 Commits

Reviewing files that changed from the base of the PR and between 3007e56 and 091fe22.

📒 Files selected for processing (8)
  • specifyweb/backend/workbench/upload/tests/test_upload_results_json.py
  • specifyweb/backend/workbench/upload/upload_result.py
  • specifyweb/backend/workbench/upload/upload_table.py
  • specifyweb/frontend/js_src/lib/components/WorkBench/__tests__/resultsParser.test.ts
  • specifyweb/frontend/js_src/lib/components/WorkBench/resultMessageResolvers.ts
  • specifyweb/frontend/js_src/lib/components/WorkBench/resultsParser.ts
  • specifyweb/frontend/js_src/lib/components/WorkBench/uploadResultTypes.ts
  • specifyweb/frontend/js_src/lib/localization/backEnd.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • specifyweb/backend/workbench/upload/upload_table.py

Comment thread specifyweb/backend/workbench/upload/upload_result.py
Comment thread specifyweb/backend/workbench/upload/upload_result.py
@CarolineDenis

Copy link
Copy Markdown
Contributor

@CodeRabbit full review

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.


Your included review limit is currently reached under our Fair Usage Limits Policy. This review may still proceed through usage-based billing if eligible. Your next included review will be available in 50 minutes.

@CarolineDenis

Copy link
Copy Markdown
Contributor

@CodeRabbit full review

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@specifyweb/frontend/js_src/lib/components/WorkBench/resultMessageResolvers.ts`:
- Around line 146-177: Update fieldNotUnique and childFieldNotUnique to validate
the getStringPayload result for fieldName before resolving localized field
labels, falling back to the safe raw key when it is empty or missing. Apply the
same guard to parentField in childFieldNotUnique, while preserving the existing
table guard and normal localized-label behavior for non-empty values.

In `@specifyweb/frontend/js_src/lib/components/WorkBench/uploadResultTypes.ts`:
- Around line 169-189: Update the WbRecordResult type so it becomes a union of
single-variant objects: wrap each mapped recordResultType member in its
discriminator key, then index the mapped type by RecordResultTypes['type']. Keep
the existing Extract and Omit logic, ensuring record_result requires only the
one runtime variant rather than every possible discriminator key.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 94b978d5-866f-40a7-8838-b65088c496d8

📥 Commits

Reviewing files that changed from the base of the PR and between 714096d and 3de6ff5.

📒 Files selected for processing (10)
  • specifyweb/backend/workbench/upload/tests/test_upload_results_json.py
  • specifyweb/backend/workbench/upload/treerecord.py
  • specifyweb/backend/workbench/upload/upload_result.py
  • specifyweb/backend/workbench/upload/upload_table.py
  • specifyweb/frontend/js_src/lib/components/LocalityUpdate/utils.ts
  • specifyweb/frontend/js_src/lib/components/WorkBench/__tests__/resultsParser.test.ts
  • specifyweb/frontend/js_src/lib/components/WorkBench/resultMessageResolvers.ts
  • specifyweb/frontend/js_src/lib/components/WorkBench/resultsParser.ts
  • specifyweb/frontend/js_src/lib/components/WorkBench/uploadResultTypes.ts
  • specifyweb/frontend/js_src/lib/localization/backEnd.ts

@CarolineDenis
CarolineDenis requested review from a team and emenslin July 28, 2026 13:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Dev Attention Needed

Development

Successfully merging this pull request may close these issues.

Workbench shows raw business rule exception for duplicate catalog number validation

5 participants