Skip to content

Conversation

@XingY
Copy link
Contributor

@XingY XingY commented Jan 12, 2026

Rationale

This PR enables "Multi Choice" data type in domain designer and supports editing/sorting multi values in the app.

Related Pull Requests

Changes

  • Added new MULTI_CHOICE_RANGE_URI for defining MVTC fields and updated utils to check against data type
  • renamed joinValues prop for SelectInput to skipJoinValues to align with its actual usage
  • Modified FilterFacetedSelector and QueryFilterPanel to handle array value selecting for MVTC

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

This comment was marked as resolved.

@XingY XingY requested a review from labkey-alan January 14, 2026 21:28
textChoiceValidator: undefined,
valueExpression: undefined,
textChoiceValidator: isTextChoice ? field.textChoiceValidator: undefined,
valueExpression: isTextChoice ? field.valueExpression: undefined,
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: run the linter/formatter to clean these up.

regexValidators: [],
scale: MAX_TEXT_LENGTH,
}) as DomainField;
if (!wasTextChoice) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Couldn't we move this if statement logic into the if statement above so it looks something like:

if ((field.isTextChoiceField() || field.isMultiChoiceField()) && !wasTextChoice) {

or maybe to be a little more verbose, but easier to read:

const isChoiceField = field.isTextChoiceField() || field.isMultiChoiceField();

if (isChoiceField && !wasTextChoice) {

Comment on lines 530 to 544
{expanded &&
!isFieldFullyLocked(field.lockType) &&
!appPropertiesOnly &&
DomainField.allowAdvancedSettings(field) && (
<button
className="domain-row-button btn btn-default"
disabled={isFieldFullyLocked(field.lockType)}
id={createFormInputId(DOMAIN_FIELD_ADV, domainIndex, index)}
name={createFormInputName(DOMAIN_FIELD_ADV)}
onClick={this.onShowAdvanced}
type="button"
>
Advanced Settings
</button>
)}
Copy link
Contributor

Choose a reason for hiding this comment

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

If we move the logic for showing this button to a variable above:

        const showAdvancedSettingsButton =
            expanded &&
            !isFieldFullyLocked(field.lockType) &&
            !appPropertiesOnly &&
            DomainField.allowAdvancedSettings(field);

Then we can remove the added level of nesting in the TSX:

Suggested change
{expanded &&
!isFieldFullyLocked(field.lockType) &&
!appPropertiesOnly &&
DomainField.allowAdvancedSettings(field) && (
<button
className="domain-row-button btn btn-default"
disabled={isFieldFullyLocked(field.lockType)}
id={createFormInputId(DOMAIN_FIELD_ADV, domainIndex, index)}
name={createFormInputName(DOMAIN_FIELD_ADV)}
onClick={this.onShowAdvanced}
type="button"
>
Advanced Settings
</button>
)}
{showAdvancedSettingsButton && (
<button
className="domain-row-button btn btn-default"
disabled={isFieldFullyLocked(field.lockType)}
id={createFormInputId(DOMAIN_FIELD_ADV, domainIndex, index)}
name={createFormInputName(DOMAIN_FIELD_ADV)}
onClick={this.onShowAdvanced}
type="button"
>
Advanced Settings
</button>
)}

It's a small change but I think it's more readable because the variable is named and there's less logic spread out over multiple lines in an already highly nested TSX block.

Comment on lines 816 to 819
if (
(type === 'TextChoice' || type === 'MultiChoice') &&
!rawPropertyValidator[i]?.properties?.validValues
) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I would define a variable isChoice below the hasExpressionStr variable above:

const isChoice = type === 'TextChoice' || type === 'MultiChoice';

Then the if statement will fit on one line instead of 4, and be a little easier to read.

Suggested change
if (
(type === 'TextChoice' || type === 'MultiChoice') &&
!rawPropertyValidator[i]?.properties?.validValues
) {
if (isChoice && !rawPropertyValidator[i]?.properties?.validValues) {


async function convertRowToEditorModelData(
data: boolean | number | string,
data: [] | boolean | number | string,
Copy link
Contributor

Choose a reason for hiding this comment

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

Could this type be more specific? Is it really an array of anything or is it more specific like an array of strings? Even if it's an array of primitive values we could do something like:

data: boolean | boolean[] | number | number[] | string | string[],

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point. For now we only support string[]

allowRelativeDateFilter={allowRelativeDateFilter}
disabled={hasNotInQueryFilter}
field={activeField}
fieldFilters={currentFieldFilters?.map(filter => filter.filter)}
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be memoized above, otherwise it's going to produce a new array every render cycle, and force a rerender.

}
key={activeFieldKey}
onFieldFilterUpdate={(newFilters, index) =>
onFilterUpdate(activeField, newFilters, index)
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be memoized above, via useCallback, otherwise it's going to produce a new method every render cycle, and force a rerender.

<div className="field-modal__col-sub-title">
Find values for {activeField.caption}
</div>
<FilterFacetedSelector
Copy link
Contributor

Choose a reason for hiding this comment

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

Like above, this <FilterFacetedSelector has several props being passed to it need to be memoized via useMemo or useCallback.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

options={filterOptions}
placeholder="Select a filter type..."
required={true}
value={fieldFilters?.[0]?.getFilterType()?.getURLSuffix() || 'arraycontainsall'}
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this the correct use of ?. If fieldFilters[0] isn't null/undefined, then wouldn't it be guaranteed that getFilterType would have a non-null return? Basically, could we go from:

fieldFilters?.[0]?.getFilterType()?.getURLSuffix()

to

fieldFilters?.[0]?.getFilterType().getURLSuffix()

Copy link
Contributor

Choose a reason for hiding this comment

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

There's an unused import of Fragment at the top of this file that should probably be cleaned up

@XingY XingY requested a review from labkey-alan January 16, 2026 02:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants