Skip to content

Commit f04f3ef

Browse files
authored
Issue 53055: Check for multiple values in single value column (#1794)
1 parent f629ec3 commit f04f3ef

6 files changed

Lines changed: 98 additions & 75 deletions

File tree

packages/components/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/components/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@labkey/components",
3-
"version": "6.43.0",
3+
"version": "6.43.1",
44
"description": "Components, models, actions, and utility functions for LabKey applications and pages",
55
"sideEffects": false,
66
"files": [

packages/components/releaseNotes/components.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# @labkey/components
22
Components, models, actions, and utility functions for LabKey applications and pages
33

4+
### version 6.43.1
5+
*Released*: 22 May 2025
6+
- Issue 53055: Check for multiple values in single value column
7+
48
### version 6.43.0
59
*Released*: 22 May 2025
610
- AppURL

packages/components/src/internal/components/editable/actions.test.ts

Lines changed: 63 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,12 @@ describe('parsePastedLookup', () => {
670670
lookup: new QueryLookup({ isPublic: true }),
671671
required: true,
672672
});
673+
const multiValueLookup = new QueryColumn({
674+
jsonType: 'string',
675+
caption: 'MultiValueLookupCol',
676+
lookup: new QueryLookup({ isPublic: true, multiValued: 'junction' }),
677+
multiValue: true,
678+
});
673679

674680
const intLookupValues = [
675681
{ display: 'A', raw: 1 },
@@ -683,24 +689,20 @@ describe('parsePastedLookup', () => {
683689
];
684690

685691
test('empty', () => {
686-
[undefined, null, '', ' '].forEach(val => {
692+
const emptyValues = [undefined, null, '', ' '];
693+
emptyValues.forEach(val => {
687694
expect(parsePastedLookup(intLookupCol, intLookupValues, val)).toStrictEqual({
688-
valueDescriptors: List([
689-
{
690-
display: val,
691-
raw: val,
692-
},
693-
]),
695+
valueDescriptors: List([{ display: val, raw: val }]),
694696
});
695697
});
696-
[undefined, null, '', ' '].forEach(val => {
698+
emptyValues.forEach(val => {
697699
expect(parsePastedLookup(stringLookupCol, stringLookupValues, val)).toStrictEqual({
698-
valueDescriptors: List([
699-
{
700-
display: val,
701-
raw: val,
702-
},
703-
]),
700+
valueDescriptors: List([{ display: val, raw: val }]),
701+
});
702+
});
703+
emptyValues.forEach(val => {
704+
expect(parsePastedLookup(multiValueLookup, stringLookupValues, val)).toStrictEqual({
705+
valueDescriptors: List([{ display: val, raw: val }]),
704706
});
705707
});
706708
});
@@ -719,12 +721,11 @@ describe('parsePastedLookup', () => {
719721
valueDescriptors: List([{ display: 'value D', raw: 'd' }]),
720722
});
721723
expect(parsePastedLookup(stringLookupCol, stringLookupValues, 'b,C,value D')).toStrictEqual({
722-
message: undefined,
723-
valueDescriptors: List([
724-
{ display: 'b', raw: 'B' },
725-
{ display: 'C', raw: 'C' },
726-
{ display: 'value D', raw: 'd' },
727-
]),
724+
message: {
725+
message:
726+
'Could not find "b,C,value D". Please make sure values that contain commas are properly quoted.',
727+
},
728+
valueDescriptors: List([{ display: 'b,C,value D', raw: 'b,C,value D' }]),
728729
});
729730

730731
expect(parsePastedLookup(stringLookupCol, stringLookupValues, 'abc')).toStrictEqual({
@@ -734,12 +735,9 @@ describe('parsePastedLookup', () => {
734735
expect(parsePastedLookup(stringLookupCol, stringLookupValues, 'abc, valueD')).toStrictEqual({
735736
message: {
736737
message:
737-
'Could not find "abc", "valueD". Please make sure values that contain commas are properly quoted.',
738+
'Could not find "abc, valueD". Please make sure values that contain commas are properly quoted.',
738739
},
739-
valueDescriptors: List([
740-
{ display: 'abc', raw: 'abc' },
741-
{ display: 'valueD', raw: 'valueD' },
742-
]),
740+
valueDescriptors: List([{ display: 'abc, valueD', raw: 'abc, valueD' }]),
743741
});
744742
});
745743

@@ -753,12 +751,10 @@ describe('parsePastedLookup', () => {
753751
valueDescriptors: List([{ display: 'A', raw: 1 }]),
754752
});
755753
expect(parsePastedLookup(intLookupCol, intLookupValues, 'A,B,b')).toStrictEqual({
756-
message: undefined,
757-
valueDescriptors: List([
758-
{ display: 'A', raw: 1 },
759-
{ display: 'b', raw: 2 },
760-
{ display: 'b', raw: 2 },
761-
]),
754+
message: {
755+
message: 'Could not find "A,B,b". Please make sure values that contain commas are properly quoted.',
756+
},
757+
valueDescriptors: List([{ display: 'A,B,b', raw: 'A,B,b' }]),
762758
});
763759

764760
expect(parsePastedLookup(intLookupCol, intLookupValues, 'abc')).toStrictEqual({
@@ -768,12 +764,9 @@ describe('parsePastedLookup', () => {
768764
expect(parsePastedLookup(intLookupCol, intLookupValues, 'abc, valueD')).toStrictEqual({
769765
message: {
770766
message:
771-
'Could not find "abc", "valueD". Please make sure values that contain commas are properly quoted.',
767+
'Could not find "abc, valueD". Please make sure values that contain commas are properly quoted.',
772768
},
773-
valueDescriptors: List([
774-
{ display: 'abc', raw: 'abc' },
775-
{ display: 'valueD', raw: 'valueD' },
776-
]),
769+
valueDescriptors: List([{ display: 'abc, valueD', raw: 'abc, valueD' }]),
777770
});
778771
});
779772

@@ -784,18 +777,43 @@ describe('parsePastedLookup', () => {
784777
});
785778
[undefined, null, ''].forEach(val => {
786779
expect(parsePastedLookup(requiredLookupCol, stringLookupValues, val)).toStrictEqual({
787-
message: {
788-
message: 'ReqLookCol is required.',
789-
},
790-
valueDescriptors: List([
791-
{
792-
display: val,
793-
raw: val,
794-
},
795-
]),
780+
message: { message: 'ReqLookCol is required.' },
781+
valueDescriptors: List([{ display: val, raw: val }]),
796782
});
797783
});
798784
});
785+
786+
test('multi-value column', () => {
787+
expect(parsePastedLookup(multiValueLookup, stringLookupValues, 'A')).toStrictEqual({
788+
message: undefined,
789+
valueDescriptors: List([{ display: 'A', raw: 'a' }]),
790+
});
791+
expect(parsePastedLookup(multiValueLookup, stringLookupValues, 'a')).toStrictEqual({
792+
message: undefined,
793+
valueDescriptors: List([{ display: 'A', raw: 'a' }]),
794+
});
795+
expect(parsePastedLookup(multiValueLookup, stringLookupValues, 'value D')).toStrictEqual({
796+
message: undefined,
797+
valueDescriptors: List([{ display: 'value D', raw: 'd' }]),
798+
});
799+
expect(parsePastedLookup(multiValueLookup, stringLookupValues, 'b,C,value D')).toStrictEqual({
800+
message: undefined,
801+
valueDescriptors: List([
802+
{ display: 'b', raw: 'B' },
803+
{ display: 'C', raw: 'C' },
804+
{ display: 'value D', raw: 'd' },
805+
]),
806+
});
807+
expect(parsePastedLookup(multiValueLookup, stringLookupValues, 'b,C,value D,404')).toStrictEqual({
808+
message: { message: 'Could not find "404"' },
809+
valueDescriptors: List([
810+
{ display: 'b', raw: 'B' },
811+
{ display: 'C', raw: 'C' },
812+
{ display: 'value D', raw: 'd' },
813+
{ display: '404', raw: '404' },
814+
]),
815+
});
816+
});
799817
});
800818

801819
describe('insertPastedData', () => {

packages/components/src/internal/components/editable/actions.ts

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1015,11 +1015,11 @@ export function generateFillCellKeys(
10151015
end = initialMinRow - 1;
10161016
}
10171017

1018-
const fillCellKeys = [];
1018+
const fillCellKeys: string[][] = [];
10191019

10201020
// Construct arrays of columns, because we're going to generate fill sequences for columns
10211021
for (let colIdx = minCol; colIdx <= maxCol; colIdx++) {
1022-
const columnKeys = [];
1022+
const columnKeys: string[] = [];
10231023

10241024
for (let rowIdx = start; rowIdx <= end; rowIdx++) {
10251025
columnKeys.push(genCellKey(editorModel.orderedColumns.get(colIdx), rowIdx));
@@ -1036,12 +1036,7 @@ export function parsePastedLookup(
10361036
descriptors: ValueDescriptor[],
10371037
value: string[] | string
10381038
): CellData {
1039-
const originalValues = List([
1040-
{
1041-
display: value,
1042-
raw: value,
1043-
},
1044-
]);
1039+
const originalValues = List([{ display: value, raw: value }]);
10451040

10461041
if (column.required && (value == null || value === '')) {
10471042
return {
@@ -1057,25 +1052,31 @@ export function parsePastedLookup(
10571052
}
10581053

10591054
let message: CellMessage;
1055+
let values: ValueDescriptor[];
10601056
const unmatched: string[] = [];
10611057

1062-
// parse pasted strings to split properly around quoted values.
1058+
// Parse pasted strings to split properly around quoted values.
10631059
// Remove the quotes for storing the actual values in the grid.
1064-
const values = parseCsvString(value, ',', true)
1065-
.map(v => {
1060+
const parsedValues = parseCsvString(value, ',', true);
1061+
1062+
// Issue 53055: Do not attempt to resolve multiple values for a single-value column
1063+
if (!column.isJunctionLookup() && parsedValues.length > 1) {
1064+
const vt = value.trim();
1065+
unmatched.push(vt);
1066+
values = [{ display: vt, raw: vt }];
1067+
} else {
1068+
values = parsedValues.flatMap(v => {
10661069
const vt = v.trim();
1067-
if (vt.length > 0) {
1068-
const vl = vt.toLowerCase();
1069-
const vd = descriptors.find(d => d.display && d.display.toString().toLowerCase() === vl);
1070-
if (!vd) {
1071-
unmatched.push(vt);
1072-
return { display: vt, raw: vt };
1073-
} else {
1074-
return vd;
1075-
}
1076-
}
1077-
})
1078-
.filter(v => v !== undefined);
1070+
if (!vt) return [];
1071+
1072+
const vl = vt.toLowerCase();
1073+
const vd = descriptors.find(d => d.display && d.display.toString().toLowerCase() === vl);
1074+
if (vd) return [vd];
1075+
1076+
unmatched.push(vt);
1077+
return [{ display: vt, raw: vt }];
1078+
});
1079+
}
10791080

10801081
if (unmatched.length) {
10811082
const valueStr = unmatched
@@ -1085,10 +1086,7 @@ export function parsePastedLookup(
10851086
message = { message: lookupValidationErrorMessage(valueStr, true) };
10861087
}
10871088

1088-
return {
1089-
message,
1090-
valueDescriptors: List(values),
1091-
};
1089+
return { message, valueDescriptors: List(values) };
10921090
}
10931091

10941092
type LookupValueCache = Record<string, Promise<ValueDescriptor[]>>;

packages/components/src/internal/util/utils.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1203,6 +1203,9 @@ describe('parseCsvString', () => {
12031203
expect(parseCsvString('"a,"123', ',', true)).toStrictEqual(['"a', '"123']);
12041204
expect(parseCsvString('"a,"123', ', ', true)).toStrictEqual(['"a,"123']);
12051205
expect(parseCsvString('"a, "123', ',', true)).toStrictEqual(['"a', ' "123']);
1206+
expect(parseCsvString('"sam"', ',', true)).toStrictEqual(['sam']);
1207+
expect(parseCsvString('"a""b"', ',', true)).toStrictEqual(['a"b']);
1208+
expect(parseCsvString('"a"b"', ',', true)).toStrictEqual(['"a"b"']);
12061209
});
12071210
});
12081211

0 commit comments

Comments
 (0)