Skip to content

fix: regex field-formatters swapping and preserve grouped#8152

Open
grantfitzsimmons wants to merge 6 commits into
v7_12_0_6_cleanfrom
issue-8129-csiro
Open

fix: regex field-formatters swapping and preserve grouped#8152
grantfitzsimmons wants to merge 6 commits into
v7_12_0_6_cleanfrom
issue-8129-csiro

Conversation

@grantfitzsimmons
Copy link
Copy Markdown
Member

@grantfitzsimmons grantfitzsimmons commented Jun 1, 2026

Fixes #8129

  • Fixes issue where regex field-formatters had their value and pattern swapped and where grouping/anchors in regexes were being stripped or rewritten, causing saved values and form results to change.
  • Helpers that trimmed regex strings removed/wrapped parentheses and anchors too aggressively.
  • UI inputs were inverted for regex parts.
  • This also adjusts the RegexField ARIA label/placeholder so the value and pattern inputs are distinct for screen readers.

Changed

  • Regex helpers: Update trim behavior to only remove legacy wrapper tokens and to preserve meaningful grouping; ignore escaped parentheses when detecting an outer wrapper.
  • UI: Ensure the regex value input holds the actual regex (with validation) and the pattern input holds the human-readable example; also fixed ARIA/placeholder so the two inputs are distinct.
  • Tests: Add regression tests covering grouped alternation and example regexes to avoid regressions.

Checklist

  • Add relevant issue to release milestone
  • Add automated tests

Testing Instructions

Compare this against v7.12.0.6. See #8129

  1. Open Field Formatters UI in the app.

  2. Create/edit a regex formatter with value="(CANB|CBG|CNS|JCT|QRS)-([0-9]{4})-([0-9]{3})" and pattern="eg. CANB-2024-001".

      <format system="false" name="LoanNumberTest" class="edu.ku.brc.specify.datamodel.Loan" fieldname="loanNumber">
            <field type="regex" size="13" minsize="12" value="(CANB|CBG|CNS|JCT|QRS)-([0-9]{4})-([0-9]{3})" pattern="eg. CANB-2024-001" />
      </format>
  3. Save and confirm the XML retains the value unchanged.

  4. Enter a sample value such as CNS-2026-503 in a form/query and confirm the formatted/canonicalized value remains the full string. Verify that this appears correctly in the form view, query results, and in Batch Edit.

Summary by CodeRabbit

Release Notes

  • Bug Fixes
    • Fixed regex pattern normalization to preserve grouped and complex patterns correctly
    • Enhanced regex field editing interface with improved validation on input
    • Strengthened handling of specialized regex patterns in formatted fields

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 1, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR fixes a regression in regex field formatter handling introduced in v7.12.0, where outer wrapping parentheses in regex patterns were being incorrectly stripped. The fix improves parentheses detection using bracket-depth scanning, refactors regex field UI rendering to clarify which column handles pattern versus placeholder, and adds test coverage for grouped patterns, numeric decimals, and catalog numbers.

Changes

Regex field formatter fix and refactoring

Layer / File(s) Summary
Regex normalization with bracket-depth scanning
specifyweb/frontend/js_src/lib/components/FieldFormatters/spec.ts
Added hasWrappingParens helper that scans bracket depth to accurately detect single outer wrapping parentheses, replacing naive leading/trailing parentheses stripping. trimRegexString now conditionally removes outer parentheses only when they truly enclose the entire pattern.
Regex field rendering reorganization
specifyweb/frontend/js_src/lib/components/FieldFormatters/Parts.tsx
Value cell now uses RegexField to edit part.placeholder (with resourcesText.value() labels), while next cell uses Input.Text to edit part.regexPlaceholder. RegexField refactored to accept configurable ariaLabel and placeholder props instead of hardcoding values.
Regex formatter preservation tests
specifyweb/frontend/js_src/lib/components/FieldFormatters/__tests__/index.test.ts
Added test cases verifying format(...) preserves full input values for grouped regex patterns (alternation), numeric decimal depth patterns, and catalog number entry book patterns. Updated imports to include UiFormatter and fieldFormatterTypeMapper.
🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Testing Instructions ⚠️ Warning PR includes automated tests but lacks documented manual testing instructions. The PR objectives specify testing against v7.12.0.6 with verification steps, which are not provided in written form. Add testing documentation with manual comparison steps against v7.12.0.6, specific test examples, XML output expectations, and form/query result verification.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main changes: fixing swapped regex field-formatter inputs and preserving grouped patterns.
Linked Issues check ✅ Passed The PR directly addresses issue #8129 by fixing the swapped value/pattern inputs, preserving regex grouping/anchors, and preventing XML alterations.
Out of Scope Changes check ✅ Passed All changes are focused on fixing the regex field-formatter issue. No unrelated modifications detected.
Automatic Tests ✅ Passed PR includes automatic tests: 3 regression tests in index.test.ts covering grouped regex preservation, and tests in spec.test.ts validating regex string helper functions.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-8129-csiro

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 and usage tips.

Copy link
Copy Markdown
Contributor

@alesan99 alesan99 left a comment

Choose a reason for hiding this comment

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

  • Save and confirm the XML retains the value unchanged.
  • Enter a sample value such as CNS-2026-503 in a form/query and confirm the formatted/canonicalized value remains the full string. Verify that this appears correctly in the form view, query results, and in Batch Edit.

Works, I don't experience the original issue 👍👍

value="" does change when you save the formatters in the visual editor, but its because of normalizeRegexString.

Image

@CarolineDenis
Copy link
Copy Markdown
Contributor

@CodeRabbit full review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 2, 2026

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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/FieldFormatters/spec.ts`:
- Around line 183-206: The current unwrapping logic in
hasWrappingParens/trimRegexString removes any outer parentheses (and treats
escaped \( \) as structural), which strips user-authored groups; change the
logic so we only remove the exact legacy alternation wrapper we add in
normalizeRegexString: update hasWrappingParens to ignore escaped paren
characters (skip parens preceded by an odd number of backslashes) when tracking
depth and only return true if the outermost pair encloses the full string AND
there is at least one unescaped '|' at top-level inside that pair (i.e., a '|'
encountered when depth === 1); only then allow trimRegexString to slice off the
outer parens. Ensure you touch hasWrappingParens and any callers
(trimRegexString/normalizeRegexString) to reflect this narrower unwrapping rule.
🪄 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: 4bb52df6-f1d6-49c7-a236-158e5ba541e8

📥 Commits

Reviewing files that changed from the base of the PR and between 218de62 and ff18321.

📒 Files selected for processing (3)
  • specifyweb/frontend/js_src/lib/components/FieldFormatters/Parts.tsx
  • specifyweb/frontend/js_src/lib/components/FieldFormatters/__tests__/index.test.ts
  • specifyweb/frontend/js_src/lib/components/FieldFormatters/spec.ts

Comment on lines +183 to +206
// Only remove parentheses that wrap the entire pattern.
if (hasWrappingParens(pattern)) pattern = pattern.slice(1, -1);
return pattern;
}
function normalizeRegexString(regexString: string): string {
let pattern: string = trimRegexString(regexString);
// Wrap alternations so they remain grouped when re-anchored for legacy XML.
if (pattern.includes('|')) pattern = `(${pattern})`;
return `/^${pattern}$/`;
}

function hasWrappingParens(pattern: string): boolean {
// Detect a single pair of outer parentheses that encloses the full string.
if (!pattern.startsWith('(') || !pattern.endsWith(')')) return false;
let depth = 0;
for (let index = 0; index < pattern.length; index += 1) {
const character = pattern[index];
if (character === '(') depth += 1;
else if (character === ')') {
depth -= 1;
if (depth === 0 && index < pattern.length - 1) return false;
}
}
return depth === 0;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve user-authored outer groups instead of trimming every full wrapper.

normalizeRegexString() only adds outer parentheses for the legacy alternation case, but this helper removes any pair that encloses the whole pattern. That still rewrites valid values like /^([A-Z]{2})$/ to /^[A-Z]{2}$/ on round-trip, which misses the PR’s preservation goal. The depth scan also counts escaped \( / \) as structural, so wrappers around literal parens are misdetected. Please narrow the unwrap to the legacy wrapper shape you generate rather than any outer parens.

🤖 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/frontend/js_src/lib/components/FieldFormatters/spec.ts` around
lines 183 - 206, The current unwrapping logic in
hasWrappingParens/trimRegexString removes any outer parentheses (and treats
escaped \( \) as structural), which strips user-authored groups; change the
logic so we only remove the exact legacy alternation wrapper we add in
normalizeRegexString: update hasWrappingParens to ignore escaped paren
characters (skip parens preceded by an odd number of backslashes) when tracking
depth and only return true if the outermost pair encloses the full string AND
there is at least one unescaped '|' at top-level inside that pair (i.e., a '|'
encountered when depth === 1); only then allow trimRegexString to slice off the
outer parens. Ensure you touch hasWrappingParens and any callers
(trimRegexString/normalizeRegexString) to reflect this narrower unwrapping rule.

@github-project-automation github-project-automation Bot moved this from 📋Back Log to Dev Attention Needed in General Tester Board Jun 2, 2026
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.

3 participants