PromucFlow_constructor/app/client/src/widgets/SelectWidget/widget/propertyUtils.ts

268 lines
6.1 KiB
TypeScript
Raw Normal View History

feat: select and multiselect label value (#24964) ## Description - Select and multi-select widgets now have two new properties under the data section label and value. - The existing options have been renamed to Source data. - Users can set the label and value of the options through these new properties. - We have written migrations to make this work for existing select widgets. #### PR fixes following issue(s) Fixes https://github.com/appsmithorg/appsmith/issues/24022 > #### 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 > Please delete options that are not relevant. - Bug fix (non-breaking change which fixes an issue) - New feature (non-breaking change which adds functionality) - Breaking change (fix or feature that would cause existing functionality to not work as expected) - Chore (housekeeping or task changes that don't impact user perception) - This change requires a documentation update > > > ## 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 - [x] Jest - [x] 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 --------- Co-authored-by: Rishabh Rathod <rishabh.rathod@appsmith.com>
2023-07-25 04:56:33 +00:00
import type { LoDashStatic } from "lodash";
import type { SelectWidgetProps } 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: SelectWidgetProps,
_: LoDashStatic,
): ValidationResponse {
let isValid;
let parsed;
let message = { name: "", message: "" };
/*
* Function to check if the object has `label` and `value`
*/
const hasLabelValue = (obj: any) => {
return (
_.isPlainObject(value) &&
obj.hasOwnProperty("label") &&
obj.hasOwnProperty("value") &&
_.isString(obj.label) &&
(_.isString(obj.value) || _.isFinite(obj.value))
);
};
/*
* When value is "{label: 'green', value: 'green'}"
*/
if (typeof value === "string") {
try {
const parsedValue = JSON.parse(value);
if (_.isObject(parsedValue)) {
value = parsedValue;
}
} catch (e) {}
}
if (_.isString(value) || _.isFinite(value) || hasLabelValue(value)) {
/*
* When value is "", "green", 444, {label: "green", value: "green"}
*/
isValid = true;
parsed = value;
} else {
isValid = false;
parsed = undefined;
message = {
name: "TypeError",
message:
'value does not evaluate to type: string | number | { "label": "label1", "value": "value1" }',
};
}
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: SelectWidgetProps,
_: 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: SelectWidgetProps,
_: 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",
},
],
};
}