We are removing the validation check that checks whether the given default value is present in the source data. This validation check creates a dependency which leads to issue mentioned in the attached github issue. We are showing a helper message under the input with a message as follow: `Make sure the default value(s) are present in the source data to have it selected by default in the UI.` #### PR fixes following issue(s) Fixes https://github.com/appsmithorg/appsmith/issues/29222 > if no issue exists, please create an issue and ask the maintainers about this first > > #### Media > A video or a GIF is preferred. when using Loom, don’t embed because it looks like it’s a GIF. instead, just link to the video > > #### Type of change - Bug fix (non-breaking change which fixes an issue) > > > ## Testing > #### How Has This Been Tested? > Please describe the tests that you ran to verify your changes. Also list any relevant details for your test configuration. > Delete anything that is not relevant - [x] Manual - [ ] JUnit - [ ] Jest - [ ] Cypress > > #### Test Plan > Add Testsmith test cases links that relate to this PR > > #### Issues raised during DP testing > Link issues raised during DP testing for better visiblity and tracking (copy link from comments dropped on this PR) > > > ## Checklist: #### Dev activity - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] PR is being merged under a feature flag #### QA activity: - [ ] [Speedbreak features](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#speedbreakers-) have been covered - [ ] Test plan covers all impacted features and [areas of interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#areas-of-interest-) - [ ] Test plan has been peer reviewed by project stakeholders and other QA members - [ ] Manually tested functionality on DP - [ ] We had an implementation alignment call with stakeholders post QA Round 2 - [ ] Cypress test cases have been added and approved by SDET/manual QA - [ ] Added `Test Plan Approved` label after Cypress tests were reviewed - [ ] Added `Test Plan Approved` label after JUnit tests were reviewed <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Enhanced user guidance with the addition of a helper text property in the MultiSelect widget. - Updated Select widget with a helper text property and extended dependencies for server-side filtering. - **Bug Fixes** - Resolved issue where MultiSelect widget selections were cleared during server-side filtering. - **Tests** - Added and modified test cases for MultiSelect and Select widgets to reflect new functionalities and ensure reliability. - **Style** - Improved tooltip display in TableWidgetV2 by increasing the max width and changing the position for better visibility and user experience. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
338 lines
8.0 KiB
TypeScript
338 lines
8.0 KiB
TypeScript
import type { LoDashStatic } from "lodash";
|
|
import type { MultiSelectWidgetProps } from ".";
|
|
import type { ValidationResponse } from "constants/WidgetValidation";
|
|
import { get, isArray, isString, isPlainObject, uniq } from "lodash";
|
|
import type { WidgetProps } from "../../BaseWidget";
|
|
import { EVAL_VALUE_PATH } from "utils/DynamicBindingUtils";
|
|
|
|
export function defaultOptionValueValidation(
|
|
value: unknown,
|
|
props: MultiSelectWidgetProps,
|
|
_: LoDashStatic,
|
|
): ValidationResponse {
|
|
let isValid = false;
|
|
let parsed: any[] = [];
|
|
let message = { name: "", message: "" };
|
|
|
|
const DEFAULT_ERROR_MESSAGE = {
|
|
name: "TypeError",
|
|
message:
|
|
"value should match: Array<string | number> | Array<{label: string, value: string | number}>",
|
|
};
|
|
|
|
/*
|
|
* Function to check if the object has `label` and `value`
|
|
*/
|
|
const hasLabelValue = (obj: any) => {
|
|
return (
|
|
_.isPlainObject(obj) &&
|
|
obj.hasOwnProperty("label") &&
|
|
obj.hasOwnProperty("value") &&
|
|
_.isString(obj.label) &&
|
|
(_.isString(obj.value) || _.isFinite(obj.value))
|
|
);
|
|
};
|
|
|
|
/*
|
|
* Function to check for duplicate values in array
|
|
*/
|
|
const hasUniqueValues = (arr: Array<string>) => {
|
|
const uniqueValues = new Set(arr);
|
|
|
|
return uniqueValues.size === arr.length;
|
|
};
|
|
|
|
/*
|
|
* When value is "['green', 'red']", "[{label: 'green', value: 'green'}]" and "green, red"
|
|
*/
|
|
if (_.isString(value) && value.trim() !== "") {
|
|
try {
|
|
/*
|
|
* when value is "['green', 'red']", "[{label: 'green', value: 'green'}]"
|
|
*/
|
|
const parsedValue = JSON.parse(value);
|
|
// Only parse value if resulting value is an array or string
|
|
if (Array.isArray(parsedValue) || _.isString(parsedValue)) {
|
|
value = parsedValue;
|
|
}
|
|
} catch (e) {
|
|
/*
|
|
* when value is "green, red", JSON.parse throws error
|
|
*/
|
|
const splitByComma = (value as string).split(",") || [];
|
|
|
|
value = splitByComma.map((s) => s.trim());
|
|
}
|
|
}
|
|
|
|
/*
|
|
* When value is "['green', 'red']", "[{label: 'green', value: 'green'}]" and "green, red"
|
|
*/
|
|
if (Array.isArray(value)) {
|
|
if (value.every((val) => _.isString(val) || _.isFinite(val))) {
|
|
/*
|
|
* When value is ["green", "red"]
|
|
*/
|
|
if (hasUniqueValues(value)) {
|
|
isValid = true;
|
|
parsed = value;
|
|
} else {
|
|
parsed = [];
|
|
message = {
|
|
name: "ValidationError",
|
|
message: "values must be unique. Duplicate values found",
|
|
};
|
|
}
|
|
} else if (value.every(hasLabelValue)) {
|
|
/*
|
|
* When value is [{label: "green", value: "red"}]
|
|
*/
|
|
if (hasUniqueValues(value.map((val) => val.value))) {
|
|
isValid = true;
|
|
parsed = value;
|
|
} else {
|
|
parsed = [];
|
|
message = {
|
|
name: "ValidationError",
|
|
message: "path:value must be unique. Duplicate values found",
|
|
};
|
|
}
|
|
} else {
|
|
/*
|
|
* When value is [true, false], [undefined, undefined] etc.
|
|
*/
|
|
parsed = [];
|
|
message = DEFAULT_ERROR_MESSAGE;
|
|
}
|
|
} else if (_.isString(value) && value.trim() === "") {
|
|
/*
|
|
* When value is an empty string
|
|
*/
|
|
isValid = true;
|
|
parsed = [];
|
|
} else if (_.isNumber(value) || _.isString(value)) {
|
|
/*
|
|
* When value is a number or just a single string e.g "Blue"
|
|
*/
|
|
isValid = true;
|
|
parsed = [value];
|
|
} else {
|
|
/*
|
|
* When value is undefined, null, {} etc.
|
|
*/
|
|
parsed = [];
|
|
message = DEFAULT_ERROR_MESSAGE;
|
|
}
|
|
|
|
return {
|
|
isValid,
|
|
parsed,
|
|
messages: [message],
|
|
};
|
|
}
|
|
|
|
export function getLabelValueKeyOptions(widget: WidgetProps) {
|
|
const sourceData = get(widget, `${EVAL_VALUE_PATH}.sourceData`);
|
|
|
|
let parsedValue: Record<string, unknown> | undefined = sourceData;
|
|
|
|
if (isString(sourceData)) {
|
|
try {
|
|
parsedValue = JSON.parse(sourceData);
|
|
} catch (e) {}
|
|
}
|
|
|
|
if (isArray(parsedValue)) {
|
|
return uniq(
|
|
parsedValue.reduce((keys, obj) => {
|
|
if (isPlainObject(obj)) {
|
|
Object.keys(obj).forEach((d) => keys.push(d));
|
|
}
|
|
|
|
return keys;
|
|
}, []),
|
|
).map((d: unknown) => ({
|
|
label: d,
|
|
value: d,
|
|
}));
|
|
} else {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
export function getLabelValueAdditionalAutocompleteData(props: WidgetProps) {
|
|
const keys = getLabelValueKeyOptions(props);
|
|
|
|
return {
|
|
item: keys
|
|
.map((d) => d.label)
|
|
.reduce((prev: Record<string, string>, curr: unknown) => {
|
|
prev[curr as string] = "";
|
|
|
|
return prev;
|
|
}, {}),
|
|
};
|
|
}
|
|
|
|
export function labelKeyValidation(
|
|
value: unknown,
|
|
props: MultiSelectWidgetProps,
|
|
_: LoDashStatic,
|
|
) {
|
|
/*
|
|
* Validation rules
|
|
* 1. Can be a string.
|
|
* 2. Can be an Array of string, number, boolean (only for option Value).
|
|
*/
|
|
|
|
if (value === "" || _.isNil(value)) {
|
|
return {
|
|
parsed: "",
|
|
isValid: false,
|
|
messages: [
|
|
{
|
|
name: "ValidationError",
|
|
message: `value does not evaluate to type: string | Array<string>`,
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
if (_.isString(value)) {
|
|
return {
|
|
parsed: value,
|
|
isValid: true,
|
|
messages: [],
|
|
};
|
|
} else if (_.isArray(value)) {
|
|
const errorIndex = value.findIndex((d) => !_.isString(d));
|
|
|
|
return {
|
|
parsed: errorIndex === -1 ? value : [],
|
|
isValid: errorIndex === -1,
|
|
messages:
|
|
errorIndex !== -1
|
|
? [
|
|
{
|
|
name: "ValidationError",
|
|
message: `Invalid entry at index: ${errorIndex}. This value does not evaluate to type: string`,
|
|
},
|
|
]
|
|
: [],
|
|
};
|
|
} else {
|
|
return {
|
|
parsed: "",
|
|
isValid: false,
|
|
messages: [
|
|
{
|
|
name: "ValidationError",
|
|
message: "value does not evaluate to type: string | Array<string>",
|
|
},
|
|
],
|
|
};
|
|
}
|
|
}
|
|
|
|
export function valueKeyValidation(
|
|
value: unknown,
|
|
props: MultiSelectWidgetProps,
|
|
_: LoDashStatic,
|
|
) {
|
|
/*
|
|
* Validation rules
|
|
* 1. Can be a string.
|
|
* 2. Can be an Array of string, number, boolean (only for option Value).
|
|
* 3. should be unique.
|
|
*/
|
|
|
|
if (value === "" || _.isNil(value)) {
|
|
return {
|
|
parsed: "",
|
|
isValid: false,
|
|
messages: [
|
|
{
|
|
name: "ValidationError",
|
|
message: `value does not evaluate to type: string | Array<string| number | boolean>`,
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
let options: unknown[] = [];
|
|
|
|
if (_.isString(value)) {
|
|
const sourceData = _.isArray(props.sourceData) ? props.sourceData : [];
|
|
|
|
const keys = sourceData.reduce((keys, curr) => {
|
|
Object.keys(curr).forEach((d) => keys.add(d));
|
|
|
|
return keys;
|
|
}, new Set());
|
|
|
|
if (!keys.has(value)) {
|
|
return {
|
|
parsed: value,
|
|
isValid: false,
|
|
messages: [
|
|
{
|
|
name: "ValidationError",
|
|
message: `value key should be present in the source data`,
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
options = sourceData.map((d: Record<string, unknown>) => d[value]);
|
|
} else if (_.isArray(value)) {
|
|
const errorIndex = value.findIndex(
|
|
(d) =>
|
|
!(_.isString(d) || (_.isNumber(d) && !_.isNaN(d)) || _.isBoolean(d)),
|
|
);
|
|
|
|
if (errorIndex !== -1) {
|
|
return {
|
|
parsed: [],
|
|
isValid: false,
|
|
messages: [
|
|
{
|
|
name: "ValidationError",
|
|
message: `Invalid entry at index: ${errorIndex}. This value does not evaluate to type: string | number | boolean`,
|
|
},
|
|
],
|
|
};
|
|
} else {
|
|
options = value;
|
|
}
|
|
} else {
|
|
return {
|
|
parsed: "",
|
|
isValid: false,
|
|
messages: [
|
|
{
|
|
name: "ValidationError",
|
|
message:
|
|
"value does not evaluate to type: string | Array<string | number | boolean>",
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
const isValid = options.every(
|
|
(d: unknown, i: number, arr: unknown[]) => arr.indexOf(d) === i,
|
|
);
|
|
|
|
return {
|
|
parsed: value,
|
|
isValid: isValid,
|
|
messages: isValid
|
|
? []
|
|
: [
|
|
{
|
|
name: "ValidationError",
|
|
message: "Duplicate values found, value must be unique",
|
|
},
|
|
],
|
|
};
|
|
}
|