diff --git a/app/client/cypress/e2e/Regression/ClientSide/OneClickBinding/PropertyControl_spec.ts b/app/client/cypress/e2e/Regression/ClientSide/OneClickBinding/PropertyControl_spec.ts index c2160042f8..4f75f1d220 100644 --- a/app/client/cypress/e2e/Regression/ClientSide/OneClickBinding/PropertyControl_spec.ts +++ b/app/client/cypress/e2e/Regression/ClientSide/OneClickBinding/PropertyControl_spec.ts @@ -100,7 +100,7 @@ describe("excludeForAirgap", "One click binding control", () => { propPane.ToggleJSMode("Table data", false); oneClickBinding.ChooseAndAssertForm( - "New from Users", + "Users", "Users", "public.users", "gender", @@ -111,7 +111,7 @@ describe("excludeForAirgap", "One click binding control", () => { propPane.MoveToTab("Content"); oneClickBinding.ChooseAndAssertForm( - "New from sample Movies", + "sample Movies", "movies", "movies", "status", diff --git a/app/client/cypress/e2e/Regression/ClientSide/OneClickBinding/TableWidget/mongoDB_spec.ts b/app/client/cypress/e2e/Regression/ClientSide/OneClickBinding/TableWidget/mongoDB_spec.ts index 3e20040a29..3dfab339ba 100644 --- a/app/client/cypress/e2e/Regression/ClientSide/OneClickBinding/TableWidget/mongoDB_spec.ts +++ b/app/client/cypress/e2e/Regression/ClientSide/OneClickBinding/TableWidget/mongoDB_spec.ts @@ -26,7 +26,7 @@ describe("one click binding mongodb datasource", function () { entityExplorer.SelectEntityByName("Table1", "Widgets"); oneClickBinding.ChooseAndAssertForm( - `New from ${dsName}`, + `${dsName}`, dsName, "netflix", "creator", diff --git a/app/client/cypress/e2e/Regression/ClientSide/OneClickBinding/TableWidget/postgres_spec.ts b/app/client/cypress/e2e/Regression/ClientSide/OneClickBinding/TableWidget/postgres_spec.ts index 582592ac12..e6accb380e 100644 --- a/app/client/cypress/e2e/Regression/ClientSide/OneClickBinding/TableWidget/postgres_spec.ts +++ b/app/client/cypress/e2e/Regression/ClientSide/OneClickBinding/TableWidget/postgres_spec.ts @@ -25,7 +25,7 @@ describe("Table widget one click binding feature", () => { entityExplorer.SelectEntityByName("Table1", "Widgets"); oneClickBinding.ChooseAndAssertForm( - `New from ${dsName}`, + `${dsName}`, dsName, "public.users", "name", diff --git a/app/client/src/WidgetQueryGenerators/GSheets/index.test.ts b/app/client/src/WidgetQueryGenerators/GSheets/index.test.ts index 7e0822a255..e1d2b1f7b9 100644 --- a/app/client/src/WidgetQueryGenerators/GSheets/index.test.ts +++ b/app/client/src/WidgetQueryGenerators/GSheets/index.test.ts @@ -66,7 +66,7 @@ describe("GSheets WidgetQueryGenerator", () => { expect(expr).toEqual([ { - name: "Find_query", + name: "Find_someSheet", payload: { formData: { command: { @@ -165,7 +165,7 @@ describe("GSheets WidgetQueryGenerator", () => { expect(expr).toEqual([ { - name: "Update_query", + name: "Update_someSheet", payload: { formData: { command: { @@ -227,7 +227,7 @@ describe("GSheets WidgetQueryGenerator", () => { ); expect(expr).toEqual([ { - name: "Insert_query", + name: "Insert_someSheet", payload: { formData: { command: { diff --git a/app/client/src/WidgetQueryGenerators/GSheets/index.ts b/app/client/src/WidgetQueryGenerators/GSheets/index.ts index 692c1a5023..5ca3bef698 100644 --- a/app/client/src/WidgetQueryGenerators/GSheets/index.ts +++ b/app/client/src/WidgetQueryGenerators/GSheets/index.ts @@ -7,6 +7,7 @@ import type { GSheetsFormData, ActionConfigurationGSheets, } from "WidgetQueryGenerators/types"; +import { removeSpecialChars } from "utils/helpers"; enum COMMAND_TYPES { "FIND" = "FETCH_MANY", @@ -51,10 +52,10 @@ export default abstract class GSheets extends BaseQueryGenerator { ) { const { select } = widgetConfig; - if (select) { + if (select && formConfig.sheetName) { return { type: QUERY_TYPE.SELECT, - name: "Find_query", + name: `Find_${removeSpecialChars(formConfig.sheetName)}`, formData: { where: { data: { @@ -109,10 +110,10 @@ export default abstract class GSheets extends BaseQueryGenerator { ) { const { select } = widgetConfig; - if (select) { + if (select && formConfig.sheetName) { return { type: QUERY_TYPE.TOTAL_RECORD, - name: "Total_record_query", + name: `Total_record_${removeSpecialChars(formConfig.sheetName)}`, formData: { where: { data: { @@ -147,10 +148,10 @@ export default abstract class GSheets extends BaseQueryGenerator { ): Record | undefined { const { update } = widgetConfig; - if (update) { + if (update && formConfig.sheetName) { return { type: QUERY_TYPE.UPDATE, - name: "Update_query", + name: `Update_${removeSpecialChars(formConfig.sheetName)}`, formData: { rowObjects: { data: `{{${update.value}}}`, @@ -177,10 +178,10 @@ export default abstract class GSheets extends BaseQueryGenerator { ) { const { create } = widgetConfig; - if (create) { + if (create && formConfig.sheetName) { return { type: QUERY_TYPE.CREATE, - name: "Insert_query", + name: `Insert_${removeSpecialChars(formConfig.sheetName)}`, formData: { rowObjects: { data: `{{${create.value}}}`, diff --git a/app/client/src/WidgetQueryGenerators/MongoDB/index.test.ts b/app/client/src/WidgetQueryGenerators/MongoDB/index.test.ts index dc9a7966de..297f5d2e2a 100644 --- a/app/client/src/WidgetQueryGenerators/MongoDB/index.test.ts +++ b/app/client/src/WidgetQueryGenerators/MongoDB/index.test.ts @@ -44,7 +44,7 @@ describe("Mongo WidgetQueryGenerator", () => { expect(expr).toEqual([ { type: "select", - name: "Find_query", + name: "Find_someTable", dynamicBindingPathList: [ { key: "formData.find.skip.data", @@ -114,7 +114,7 @@ describe("Mongo WidgetQueryGenerator", () => { expect(expr).toEqual([ { - name: "Update_query", + name: "Update_someTable", type: "update", dynamicBindingPathList: [ { @@ -170,7 +170,7 @@ describe("Mongo WidgetQueryGenerator", () => { ); expect(expr).toEqual([ { - name: "Insert_query", + name: "Insert_someTable", type: "create", dynamicBindingPathList: [ { diff --git a/app/client/src/WidgetQueryGenerators/MongoDB/index.ts b/app/client/src/WidgetQueryGenerators/MongoDB/index.ts index 99c34eb2dd..31e37b66ab 100644 --- a/app/client/src/WidgetQueryGenerators/MongoDB/index.ts +++ b/app/client/src/WidgetQueryGenerators/MongoDB/index.ts @@ -7,6 +7,7 @@ import type { ActionConfigurationMongoDB, MongoDBFormData, } from "WidgetQueryGenerators/types"; +import { removeSpecialChars } from "utils/helpers"; enum COMMAND_TYPES { "FIND" = "FIND", @@ -30,7 +31,7 @@ export default abstract class MongoDB extends BaseQueryGenerator { if (select) { return { type: QUERY_TYPE.SELECT, - name: "Find_query", + name: `Find_${removeSpecialChars(formConfig.tableName)}`, formData: { find: { skip: { data: `{{${select["offset"]}}}` }, @@ -73,7 +74,7 @@ export default abstract class MongoDB extends BaseQueryGenerator { if (select) { return { type: QUERY_TYPE.TOTAL_RECORD, - name: "Total_record_query", + name: `Total_record_${removeSpecialChars(formConfig.tableName)}`, formData: { count: { query: { @@ -102,7 +103,7 @@ export default abstract class MongoDB extends BaseQueryGenerator { if (update) { return { type: QUERY_TYPE.UPDATE, - name: "Update_query", + name: `Update_${removeSpecialChars(formConfig.tableName)}`, formData: { updateMany: { query: { data: `{_id: ObjectId('{{${update.where}._id}}')}` }, @@ -131,7 +132,7 @@ export default abstract class MongoDB extends BaseQueryGenerator { if (create) { return { type: QUERY_TYPE.CREATE, - name: "Insert_query", + name: `Insert_${removeSpecialChars(formConfig.tableName)}`, formData: { insert: { documents: { data: `{{${create.value}}}` }, diff --git a/app/client/src/WidgetQueryGenerators/PostgreSQL/index.test.ts b/app/client/src/WidgetQueryGenerators/PostgreSQL/index.test.ts index db82b47771..bbbd0d10d9 100644 --- a/app/client/src/WidgetQueryGenerators/PostgreSQL/index.test.ts +++ b/app/client/src/WidgetQueryGenerators/PostgreSQL/index.test.ts @@ -45,7 +45,7 @@ OFFSET expect(expr).toEqual([ { - name: "Select_query", + name: "Select_someTable", type: "select", dynamicBindingPathList: [ { @@ -97,7 +97,7 @@ OFFSET expect(expr).toEqual([ { - name: "Select_query", + name: "Select_someTable", type: "select", dynamicBindingPathList: [ { @@ -159,7 +159,7 @@ OFFSET expect(expr).toEqual([ { - name: "Update_query", + name: "Update_someTable", type: "update", dynamicBindingPathList: [ { @@ -219,7 +219,7 @@ OFFSET ); expect(expr).toEqual([ { - name: "Insert_query", + name: "Insert_someTable", type: "create", dynamicBindingPathList: [ { diff --git a/app/client/src/WidgetQueryGenerators/PostgreSQL/index.ts b/app/client/src/WidgetQueryGenerators/PostgreSQL/index.ts index c124ee89cf..c5457c5653 100644 --- a/app/client/src/WidgetQueryGenerators/PostgreSQL/index.ts +++ b/app/client/src/WidgetQueryGenerators/PostgreSQL/index.ts @@ -6,6 +6,7 @@ import type { WidgetQueryGenerationFormConfig, ActionConfigurationPostgreSQL, } from "../types"; +import { removeSpecialChars } from "utils/helpers"; export default abstract class PostgreSQL extends BaseQueryGenerator { private static buildSelect( widgetConfig: WidgetQueryGenerationConfig, @@ -88,7 +89,7 @@ export default abstract class PostgreSQL extends BaseQueryGenerator { return { type: QUERY_TYPE.SELECT, - name: "Select_query", + name: `Select_${removeSpecialChars(formConfig.tableName)}`, payload: { body: res, }, @@ -114,7 +115,7 @@ export default abstract class PostgreSQL extends BaseQueryGenerator { return { type: QUERY_TYPE.UPDATE, - name: "Update_query", + name: `Update_${removeSpecialChars(formConfig.tableName)}`, payload: { body: `UPDATE ${formConfig.tableName} SET ${formConfig.columns .map((column) => `"${column}"= '{{${value}.${column}}}'`) @@ -142,7 +143,7 @@ export default abstract class PostgreSQL extends BaseQueryGenerator { return { type: QUERY_TYPE.CREATE, - name: "Insert_query", + name: `Insert_${removeSpecialChars(formConfig.tableName)}`, payload: { body: `INSERT INTO ${formConfig.tableName} (${formConfig.columns.map( (a) => `"${a}"`, @@ -170,7 +171,7 @@ export default abstract class PostgreSQL extends BaseQueryGenerator { return { type: QUERY_TYPE.TOTAL_RECORD, - name: "Total_record_query", + name: `Total_record_${removeSpecialChars(formConfig.tableName)}`, payload: { body: `SELECT COUNT(*) from ${formConfig.tableName}${ formConfig.searchableColumn diff --git a/app/client/src/api/ActionAPI.tsx b/app/client/src/api/ActionAPI.tsx index 0c7628411f..9144b29eca 100644 --- a/app/client/src/api/ActionAPI.tsx +++ b/app/client/src/api/ActionAPI.tsx @@ -71,6 +71,7 @@ export interface ActionApiResponseReq { body: Record | null; httpMethod: HttpMethod | ""; url: string; + requestedAt?: number; } export type ActionExecutionResponse = ApiResponse<{ diff --git a/app/client/src/components/editorComponents/WidgetQueryGeneratorForm/CommonControls/DatasourceDropdown/index.tsx b/app/client/src/components/editorComponents/WidgetQueryGeneratorForm/CommonControls/DatasourceDropdown/index.tsx index 5c604ceeac..2f53aa51d0 100644 --- a/app/client/src/components/editorComponents/WidgetQueryGeneratorForm/CommonControls/DatasourceDropdown/index.tsx +++ b/app/client/src/components/editorComponents/WidgetQueryGeneratorForm/CommonControls/DatasourceDropdown/index.tsx @@ -213,7 +213,7 @@ function DatasourceDropdown() { - New from {option.data.isSample ? "sample " : ""} + {option.data.isSample ? "sample " : ""} {option.label?.replace("sample ", "")} } diff --git a/app/client/src/components/editorComponents/WidgetQueryGeneratorForm/CommonControls/DatasourceDropdown/useDatasource.tsx b/app/client/src/components/editorComponents/WidgetQueryGeneratorForm/CommonControls/DatasourceDropdown/useDatasource.tsx index 0ff61e9836..3e86353f6e 100644 --- a/app/client/src/components/editorComponents/WidgetQueryGeneratorForm/CommonControls/DatasourceDropdown/useDatasource.tsx +++ b/app/client/src/components/editorComponents/WidgetQueryGeneratorForm/CommonControls/DatasourceDropdown/useDatasource.tsx @@ -35,6 +35,53 @@ import { getWidget } from "sagas/selectors"; import type { AppState } from "@appsmith/reducers"; import { DatasourceCreateEntryPoints } from "constants/Datasource"; import { getCurrentWorkspaceId } from "@appsmith/selectors/workspaceSelectors"; +import type { ActionDataState } from "reducers/entityReducers/actionsReducer"; +import { getDatatype } from "utils/AppsmithUtils"; + +enum SortingWeights { + alphabetical = 1, + execution, + datatype, +} + +const SORT_INCREAMENT = 1; + +function sortQueries(queries: ActionDataState, expectedDatatype: string) { + return queries.sort((A, B) => { + const score = { + A: 0, + B: 0, + }; + + if (A.config.name < B.config.name) { + score.A += SORT_INCREAMENT << SortingWeights.alphabetical; + } else { + score.B += SORT_INCREAMENT << SortingWeights.alphabetical; + } + + if (A.data?.request?.requestedAt && B.data?.request?.requestedAt) { + if (A.data.request.requestedAt > B.data.request.requestedAt) { + score.A += SORT_INCREAMENT << SortingWeights.execution; + } else { + score.B += SORT_INCREAMENT << SortingWeights.execution; + } + } else if (A.data?.request?.requestedAt) { + score.A += SORT_INCREAMENT << SortingWeights.execution; + } else if (B.data?.request?.requestedAt) { + score.B += SORT_INCREAMENT << SortingWeights.execution; + } + + if (getDatatype(A.data?.body) === expectedDatatype) { + score.A += SORT_INCREAMENT << SortingWeights.datatype; + } + + if (getDatatype(B.data?.body) === expectedDatatype) { + score.B += SORT_INCREAMENT << SortingWeights.datatype; + } + + return score.A > score.B ? -1 : 1; + }); +} function filterOption(option: DropdownOptionType, searchText: string) { return ( @@ -48,6 +95,7 @@ export function useDatasource(searchText: string) { addBinding, config, errorMsg, + expectedType, isSourceOpen, onSourceClose, propertyName, @@ -293,7 +341,7 @@ export function useDatasource(searchText: string) { const queries = useSelector(getActionsForCurrentPage); const queryOptions = useMemo(() => { - return queries.map((query) => ({ + return sortQueries(queries, expectedType).map((query) => ({ id: query.config.id, label: query.config.name, value: `{{${query.config.name}.data}}`, diff --git a/app/client/src/components/editorComponents/WidgetQueryGeneratorForm/index.tsx b/app/client/src/components/editorComponents/WidgetQueryGeneratorForm/index.tsx index d157bb5012..dbf6d41256 100644 --- a/app/client/src/components/editorComponents/WidgetQueryGeneratorForm/index.tsx +++ b/app/client/src/components/editorComponents/WidgetQueryGeneratorForm/index.tsx @@ -37,6 +37,7 @@ type WidgetQueryGeneratorFormContextType = { isSourceOpen: boolean; onSourceClose: () => void; errorMsg: string; + expectedType: string; }; const DEFAULT_CONFIG_VALUE = { @@ -60,6 +61,7 @@ const DEFAULT_CONTEXT_VALUE = { onSourceClose: noop, errorMsg: "", propertyName: "", + expectedType: "", }; export const WidgetQueryGeneratorFormContext = @@ -73,6 +75,7 @@ type Props = { onUpdate: (snippet?: string, makeDynamicPropertyPath?: boolean) => void; widgetId: string; errorMsg: string; + expectedType: string; }; function WidgetQueryGeneratorForm(props: Props) { @@ -80,7 +83,14 @@ function WidgetQueryGeneratorForm(props: Props) { const [pristine, setPristine] = useState(true); - const { errorMsg, onUpdate, propertyPath, propertyValue, widgetId } = props; + const { + errorMsg, + expectedType, + onUpdate, + propertyPath, + propertyValue, + widgetId, + } = props; const isSourceOpen = useSelector(getIsOneClickBindingOptionsVisibility); @@ -183,6 +193,7 @@ function WidgetQueryGeneratorForm(props: Props) { onSourceClose, errorMsg, propertyName: propertyPath, + expectedType, }; }, [ config, diff --git a/app/client/src/components/editorComponents/WidgetQueryGeneratorForm/styles.tsx b/app/client/src/components/editorComponents/WidgetQueryGeneratorForm/styles.tsx index c8f13c27e5..1b01112430 100644 --- a/app/client/src/components/editorComponents/WidgetQueryGeneratorForm/styles.tsx +++ b/app/client/src/components/editorComponents/WidgetQueryGeneratorForm/styles.tsx @@ -6,7 +6,7 @@ export const Wrapper = styled.div``; export const SelectWrapper = styled.div` display: inline-block; - margin: 5px 0 2px; + margin: 0 0 2px; max-width: ${DROPDOWN_TRIGGER_DIMENSION.WIDTH}; width: 100%; `; @@ -63,6 +63,7 @@ export const ErrorMessage = styled.div` font-size: 12px; line-height: 14px; color: var(--ads-v2-color-fg-error); + margin-top: 5px; `; export const Placeholder = styled.div` diff --git a/app/client/src/components/propertyControls/OneClickBindingControl.tsx b/app/client/src/components/propertyControls/OneClickBindingControl.tsx index 25e82fe458..a0282669e9 100644 --- a/app/client/src/components/propertyControls/OneClickBindingControl.tsx +++ b/app/client/src/components/propertyControls/OneClickBindingControl.tsx @@ -16,10 +16,8 @@ class OneClickBindingControl extends BaseControl { * with default value by platform */ static canDisplayValueInUI(config: ControlData, value: any): boolean { - return [ - /^{{[^.]*\.data}}$/gi, // {{query1.data}} - /^{{}}$/, // {{}} - ].some((d) => d.test(value)); + // {{query1.data}} + return /^{{[^.]*\.data}}$/gi.test(value); } static shouldValidateValueOnDynamicPropertyOff() { @@ -55,6 +53,7 @@ class OneClickBindingControl extends BaseControl { return ( { ]); }); }); + +describe("getDatatype - should test the datatypes", () => { + it("1. String", () => { + expect(getDatatype("test")).toBe(DataType.STRING); + }); + + it("2. Number", () => { + [1, NaN].forEach((d) => { + expect(getDatatype(d)).toBe(DataType.NUMBER); + }); + }); + + it("3. Boolean", () => { + [true, false].forEach((d) => { + expect(getDatatype(d)).toBe(DataType.BOOLEAN); + }); + }); + + it("4. Object", () => { + expect(getDatatype({})).toBe(DataType.OBJECT); + }); + + it("5. Array", () => { + expect(getDatatype([])).toBe(DataType.ARRAY); + }); + + it("6. Rest of the types", () => { + expect(getDatatype(null)).toBe(DataType.NULL); + + expect(getDatatype(undefined)).toBe(DataType.UNDEFINED); + }); +}); diff --git a/app/client/src/utils/AppsmithUtils.tsx b/app/client/src/utils/AppsmithUtils.tsx index b511e8ffb6..fa94ae0bad 100644 --- a/app/client/src/utils/AppsmithUtils.tsx +++ b/app/client/src/utils/AppsmithUtils.tsx @@ -5,7 +5,7 @@ import * as Sentry from "@sentry/react"; import type { Property } from "api/ActionAPI"; import type { AppIconName } from "design-system-old"; import { AppIconCollection } from "design-system-old"; -import _ from "lodash"; +import _, { isPlainObject } from "lodash"; import * as log from "loglevel"; import { osName } from "react-device-detect"; import type { ActionDataState } from "reducers/entityReducers/actionsReducer"; @@ -457,3 +457,31 @@ export function areArraysEqual(arr1: string[], arr2: string[]) { return false; } + +export enum DataType { + OBJECT = "OBJECT", + NUMBER = "NUMBER", + ARRAY = "ARRAY", + BOOLEAN = "BOOLEAN", + STRING = "STRING", + NULL = "NULL", + UNDEFINED = "UNDEFINED", +} + +export function getDatatype(value: unknown) { + if (typeof value === "string") { + return DataType.STRING; + } else if (typeof value === "number") { + return DataType.NUMBER; + } else if (typeof value === "boolean") { + return DataType.BOOLEAN; + } else if (isPlainObject(value)) { + return DataType.OBJECT; + } else if (Array.isArray(value)) { + return DataType.ARRAY; + } else if (value === null) { + return DataType.NULL; + } else if (value === undefined) { + return DataType.UNDEFINED; + } +} diff --git a/app/client/src/widgets/TableWidgetV2/index.ts b/app/client/src/widgets/TableWidgetV2/index.ts index a0d6710056..ac99c44166 100644 --- a/app/client/src/widgets/TableWidgetV2/index.ts +++ b/app/client/src/widgets/TableWidgetV2/index.ts @@ -40,7 +40,7 @@ export const CONFIG = { borderWidth: "1", dynamicBindingPathList: [], primaryColumns: {}, - tableData: undefined, + tableData: "", columnWidthMap: {}, columnOrder: [], enableClientSideSearch: true, diff --git a/app/client/src/widgets/TableWidgetV2/widget/index.tsx b/app/client/src/widgets/TableWidgetV2/widget/index.tsx index fd72fc29b3..c04575038a 100644 --- a/app/client/src/widgets/TableWidgetV2/widget/index.tsx +++ b/app/client/src/widgets/TableWidgetV2/widget/index.tsx @@ -183,6 +183,7 @@ class TableWidgetV2 extends BaseWidget { onSort: queryConfig.select.run, enableClientSideSearch: !formConfig.searchableColumn, primaryColumnId: formConfig.primaryColumn, + isVisibleDownload: false, }); } diff --git a/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/contentConfig.ts b/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/contentConfig.ts index 8fb595f18a..59927cecf6 100644 --- a/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/contentConfig.ts +++ b/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/contentConfig.ts @@ -10,6 +10,7 @@ import type { TableWidgetProps } from "widgets/TableWidgetV2/constants"; import { InlineEditingSaveOptions } from "widgets/TableWidgetV2/constants"; import { composePropertyUpdateHook } from "widgets/WidgetUtils"; import { + tableDataValidation, totalRecordsCountValidation, uniqueColumnNameValidation, updateColumnOrderHook, @@ -35,9 +36,14 @@ export default [ isTriggerProperty: false, isJSConvertible: true, validation: { - type: ValidationTypes.OBJECT_ARRAY, + type: ValidationTypes.FUNCTION, params: { - default: [], + fn: tableDataValidation, + expected: { + type: "Array", + example: `[{ "name": "John" }]`, + autocompleteDataType: AutocompleteDataType.ARRAY, + }, }, }, evaluationSubstitutionType: EvaluationSubstitutionType.SMART_SUBSTITUTE, diff --git a/app/client/src/widgets/TableWidgetV2/widget/propertyUtils.ts b/app/client/src/widgets/TableWidgetV2/widget/propertyUtils.ts index 020b940023..26af146a53 100644 --- a/app/client/src/widgets/TableWidgetV2/widget/propertyUtils.ts +++ b/app/client/src/widgets/TableWidgetV2/widget/propertyUtils.ts @@ -970,3 +970,82 @@ export function selectColumnOptionsValidation( export const getColumnPath = (propPath: string) => propPath.split(".").slice(0, 2).join("."); + +export const tableDataValidation = ( + value: unknown, + props: TableWidgetProps, + _?: any, +) => { + const invalidResponse = { + isValid: false, + parsed: [], + messages: [ + { + name: "TypeError", + message: `This value does not evaluate to type Array}`, + }, + ], + }; + + if (value === "") { + return { + isValid: true, + parsed: [], + }; + } + + if (value === undefined || value === null) { + return { + isValid: false, + parsed: [], + messages: [ + { + name: "ValidationError", + message: "Data is undefined, re-run your query or fix the data", + }, + ], + }; + } + + if (!_.isString(value) && !Array.isArray(value)) { + return invalidResponse; + } + + let parsed = value; + + if (_.isString(value)) { + try { + parsed = JSON.parse(value as string); + } catch (e) { + return invalidResponse; + } + } + + if (Array.isArray(parsed)) { + if (parsed.length === 0) { + return { + isValid: true, + parsed: [], + }; + } + + for (let i = 0; i < parsed.length; i++) { + if (!_.isPlainObject(parsed[i])) { + return { + isValid: false, + parsed: [], + messages: [ + { + name: "ValidationError", + message: `Invalid object at index ${i}`, + }, + ], + }; + } + } + + return { isValid: true, parsed }; + } + + return invalidResponse; +};