-
Notifications
You must be signed in to change notification settings - Fork 2
Support field type conversion for Multi Choice fields #1927
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR extends Text Choice support to include conversions to and from multi-value Text Choice (multi-choice) fields, updating the field editor UI, model logic, and confirmation flows around data-type changes.
Changes:
- Introduces
MULTI_CHOICE_TYPEmetadata refinements (hideFromDomainRow,longDisplay) and updates type resolution/eligibility logic so multi-choice behaves as an internal variant of Text Choice rather than a separate visible type. - Enhances
updateDataTypeand text choice usage counting to correctly handle conversions between string, Text Choice, and Multi Choice fields, clearing validators/flags and tracking multi-value usage where appropriate. - Updates the Text Choice options UI to add an “Allow multiple selections” toggle, multi-choice-specific edit restrictions, and improved confirmation messaging/tests for data-type changes involving text/multi-choice.
Reviewed changes
Copilot reviewed 16 out of 17 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
packages/components/src/internal/components/domainproperties/models.tsx |
Adds hideFromDomainRow handling in isPropertyTypeAllowed so types like MULTI_CHOICE_TYPE can be hidden from the main data type dropdown; ensures eligibility logic respects new flag. |
packages/components/src/internal/components/domainproperties/models.test.ts |
Extends PropDescType tests to verify that MULTI_CHOICE_TYPE is not allowed as a visible property type in the domain row under various flag combinations. |
packages/components/src/internal/components/domainproperties/constants.ts |
Adds DOMAIN_FIELD_TEXTCHOICE_MULTI constant for wiring the “Allow multiple selections” checkbox into the domain form field ID namespace. |
packages/components/src/internal/components/domainproperties/actions.ts |
Updates updateDataType to treat text and multi-choice consistently, preserving/adding the text choice validator while clearing incompatible validators/flags when switching to multi-choice; introduces TextChoiceInUseValues with hasMultiValue support and rewrites getTextChoiceInUseValues to track counts/locks for multi-valued fields (note: current non-PHI branch double-counts RowCount per value, inflating usage counts). |
packages/components/src/internal/components/domainproperties/actions.test.ts |
Adds tests covering conversions to multi-choice from Text Choice and string, asserting validator clearing, flag resets, and default text choice validator behavior. |
packages/components/src/internal/components/domainproperties/TextChoiceOptions.tsx |
Extends the Text Choice UI with an “Allow multiple selections” checkbox wired to handleDataTypeChange, disables the toggle when the field is fully locked or has existing multi-value usage, blocks editing of in-use values for multi-choice fields, and adapts to the new TextChoiceInUseValues shape (with hasMultiValue and useCount). Also introduces a few unused imports that should be cleaned up. |
packages/components/src/internal/components/domainproperties/TextChoiceOptions.test.tsx |
New RTL-based test suite replacing the Enzyme spec, exercising the Text Choice options UI including loading state, list rendering, in-use/locked behavior, value editing/validation, add/delete limits, search, and the new multi-choice toggle/disabled-state behavior. |
packages/components/src/internal/components/domainproperties/TextChoiceOptions.spec.tsx |
Removes the legacy Enzyme test suite in favor of the new RTL tests. |
packages/components/src/internal/components/domainproperties/PropDescType.ts |
Adds longDisplay and hideFromDomainRow fields to PropDescType, and updates TEXT_CHOICE_TYPE/MULTI_CHOICE_TYPE so they share the display label “Text Choice” but have distinct longDisplay strings and hide multi-choice from the domain row. |
packages/components/src/internal/components/domainproperties/DomainRowExpandedOptions.tsx |
Threads a required handleDataTypeChange callback into the expanded options panel and passes it down to TextChoiceOptions, enabling the new multi-choice toggle to proxy a type change through the same path as the main data type dropdown. |
packages/components/src/internal/components/domainproperties/DomainRow.tsx |
Refactors data type change handling into handleDataTypeChange, exports shouldShowConfirmDataTypeChange, and updates confirmation wiring to use the new, richer confirm modal props; integrates handleDataTypeChange with expanded options. Currently passes the new type’s rangeURI into shouldShowConfirmDataTypeChange, which means multi-choice → Text Choice conversions will not trigger the confirmation path the helper’s tests are validating. |
packages/components/src/internal/components/domainproperties/DomainRow.test.tsx |
Adjusts DomainRow render tests and adds targeted tests for shouldShowConfirmDataTypeChange across string/number/date/multi-choice/Text Choice transitions (though two test descriptions say “should return false” while asserting true, which is misleading). |
packages/components/src/internal/components/domainproperties/ConfirmDataTypeChangeModal.tsx |
Changes the confirm modal to accept the full original field (via original: Partial<IDomainField>), uses PropDescType (with longDisplay) to produce more descriptive labels, and adds multi-choice-aware warnings about view filters and conditional formatting; updates getDataTypeConfirmDisplayText to work on PropDescType rather than bare URIs. |
packages/components/src/internal/components/domainproperties/ConfirmDataTypeChangeModal.test.tsx |
Expands tests to cover the new getDataTypeConfirmDisplayText API and the updated messaging for datetime⇄date/time and Text Choice⇄multi-choice conversions. |
packages/components/releaseNotes/components.md |
Adds a placeholder “version 7.X” section describing “Multi value text choices: field type conversion,” but leaves the version, release date, and content as placeholders (X and TODO) that should be finalized before release. |
packages/components/package.json |
Bumps @labkey/components version to 7.14.0-fb-mvtc-convert.1 for this feature branch. |
packages/components/package-lock.json |
Syncs the package-lock version metadata with the new 7.14.0-fb-mvtc-convert.1 package version. |
Files not reviewed (1)
- packages/components/package-lock.json: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| test('should return false for converting multichoice to textChoice', () => { | ||
| expect(shouldShowConfirmDataTypeChange(MULTI_CHOICE_RANGE_URI, TEXT_CHOICE_CONCEPT_URI)).toBe(true); | ||
| }); | ||
|
|
||
| test('should return false for converting textChoice to multiChoice', () => { |
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the shouldShowConfirmDataTypeChange test suite, the descriptions for the last two tests say "should return false" but the expectations assert toBe(true). While the assertions are correct for the new multi-choice/Text Choice behavior, the mismatched test names can be misleading when reading test failures; consider updating the descriptions to match the actual expected result.
| test('should return false for converting multichoice to textChoice', () => { | |
| expect(shouldShowConfirmDataTypeChange(MULTI_CHOICE_RANGE_URI, TEXT_CHOICE_CONCEPT_URI)).toBe(true); | |
| }); | |
| test('should return false for converting textChoice to multiChoice', () => { | |
| test('should return true for converting multichoice to textChoice', () => { | |
| expect(shouldShowConfirmDataTypeChange(MULTI_CHOICE_RANGE_URI, TEXT_CHOICE_CONCEPT_URI)).toBe(true); | |
| }); | |
| test('should return true for converting textChoice to multiChoice', () => { |
| ### version 7.X | ||
| *Released*: X January 2026 | ||
| - Multi value text choices: field type conversion | ||
| - TODO |
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new version 7.X entry still uses placeholder values for the version and release date ("7.X", "X January 2026") and a TODO marker in the bullet text. For clarity and consistency with the surrounding release notes, consider updating this entry to the actual version (e.g., 7.14.0-...) and a concrete release date, and replacing the TODO with a brief description of the change before publishing.
| ### version 7.X | |
| *Released*: X January 2026 | |
| - Multi value text choices: field type conversion | |
| - TODO | |
| ### version 7.14.0 | |
| *Released*: 27 January 2026 | |
| - Multi value text choices: field type conversion | |
| - Support converting existing text fields to multi value text choice fields while preserving stored values |
| values.forEach(val => { | ||
| if (!useCount[val]) { | ||
| useCount[val] = { count: 0, locked: false }; | ||
| } | ||
| useCount[val].count++; | ||
| useCount[val].locked = useCount[val].locked || rowLocked; | ||
| }); | ||
|
|
||
| values.forEach(val => { | ||
| if (!useCount[val]) { | ||
| useCount[val] = { count: 0, locked: false }; | ||
| } | ||
| useCount[val].count += rowCount; | ||
| useCount[val].locked = useCount[val].locked || rowLocked; |
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the non-PHI branch of getTextChoiceInUseValues, each distinct value from executeSql is processed twice: once incrementing useCount[val].count++ and then again adding rowCount for the same val. This double counting will inflate the reported count for each text choice value (e.g., count becomes rowCount + 1 per group), which can lead to incorrect "rows to be updated" messaging and potentially impact any logic that relies on these counts. Consider consolidating the two loops so that count is only incremented by the aggregated RowCount (and locked is set once) per distinct value.
| if ( | ||
| shouldShowConfirmDataTypeChange( | ||
| field.original.rangeURI ?? field.original.conceptURI, | ||
| typeConvertingTo.rangeURI ?? typeConvertingTo.conceptURI |
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
handleDataTypeChange calls shouldShowConfirmDataTypeChange with typeConvertingTo.rangeURI ?? typeConvertingTo.conceptURI, which means that for Text Choice conversions the new type is passed as its storage range URI (string) instead of the Text Choice concept URI. As a result, transitions like multi-choice � Text Choice that your shouldShowConfirmDataTypeChange tests expect to prompt for confirmation will never hit the (wasMultiChoice && newTextChoice) branch and won't show a confirmation dialog via this path. To align runtime behavior with the tests and the new confirmation messaging, consider passing the concept URI first (e.g., typeConvertingTo.conceptURI ?? typeConvertingTo.rangeURI) or adjusting shouldShowConfirmDataTypeChange to infer Text Choice based on the PropDescType rather than the raw URI string.
| typeConvertingTo.rangeURI ?? typeConvertingTo.conceptURI | |
| typeConvertingTo.conceptURI ?? typeConvertingTo.rangeURI |
| import { createFormInputId, createFormInputName } from './utils'; | ||
| import { isFieldFullyLocked } from './propertiesUtil'; | ||
| import { MULTI_CHOICE_TYPE, PropDescType, TEXT_CHOICE_TYPE } from './PropDescType'; |
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The imports for TextChoiceOptions.tsx now include both createFormInputName and PropDescType, but neither helper is used anywhere in this file. To keep the module clean and avoid potential lint/type-check warnings, remove these unused imports or wire them up where they are actually needed.
| import { createFormInputId, createFormInputName } from './utils'; | |
| import { isFieldFullyLocked } from './propertiesUtil'; | |
| import { MULTI_CHOICE_TYPE, PropDescType, TEXT_CHOICE_TYPE } from './PropDescType'; | |
| import { createFormInputId } from './utils'; | |
| import { isFieldFullyLocked } from './propertiesUtil'; | |
| import { MULTI_CHOICE_TYPE, TEXT_CHOICE_TYPE } from './PropDescType'; |
| const stringType = { | ||
| rangeURI: 'http://www.w3.org/2001/XMLSchema#boolean', | ||
| dataType: TEXT_TYPE, | ||
| }; | ||
|
|
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused variable stringType.
| const stringType = { | |
| rangeURI: 'http://www.w3.org/2001/XMLSchema#boolean', | |
| dataType: TEXT_TYPE, | |
| }; |
| import { getTextChoiceInUseValues } from './actions'; | ||
| import { createFormInputId } from './utils'; | ||
| import { getTextChoiceInUseValues, TextChoiceInUseValues } from './actions'; | ||
| import { createFormInputId, createFormInputName } from './utils'; |
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused import createFormInputName.
| import { createFormInputId, createFormInputName } from './utils'; | |
| import { createFormInputId } from './utils'; |
| import { getTextChoiceInUseValues, TextChoiceInUseValues } from './actions'; | ||
| import { createFormInputId, createFormInputName } from './utils'; | ||
| import { isFieldFullyLocked } from './propertiesUtil'; | ||
| import { MULTI_CHOICE_TYPE, PropDescType, TEXT_CHOICE_TYPE } from './PropDescType'; |
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused import PropDescType.
| import { MULTI_CHOICE_TYPE, PropDescType, TEXT_CHOICE_TYPE } from './PropDescType'; | |
| import { MULTI_CHOICE_TYPE, TEXT_CHOICE_TYPE } from './PropDescType'; |
|
|
||
| test('with inUse value update info', async () => { | ||
| const replaceValues = jest.fn(); | ||
| const { rerender } = render( |
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused variable rerender.
| const { rerender } = render( | |
| render( |
Rationale
Related Pull Requests
Changes