Skip to content

Commit

Permalink
feat: Add configurable field limit to JSONFormWidget (appsmithorg#38856)
Browse files Browse the repository at this point in the history
  • Loading branch information
rahulbarwal authored Jan 30, 2025
1 parent 3ab237d commit ae21fa0
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 9 deletions.
4 changes: 3 additions & 1 deletion app/client/src/widgets/JSONFormWidget/component/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export interface JSONFormComponentProps<TValues = any> {
updateWidgetProperty: (propertyName: string, propertyValue: any) => void;
widgetId: string;
showConnectDataOverlay?: boolean;
maxAllowedFields?: number;
}

const StyledContainer = styled(WidgetStyleContainer)<StyledContainerProps>`
Expand Down Expand Up @@ -174,7 +175,8 @@ function JSONFormComponent<TValues>(
if (fieldLimitExceeded) {
return (
<InfoMessage fixHeight={fixMessageHeight}>
Source data exceeds {MAX_ALLOWED_FIELDS} fields.&nbsp;
Source data exceeds {rest.maxAllowedFields || MAX_ALLOWED_FIELDS}{" "}
fields. &nbsp;
{renderMode === RenderModes.PAGE
? "Please contact your developer for more information"
: "Please update the source data."}
Expand Down
77 changes: 76 additions & 1 deletion app/client/src/widgets/JSONFormWidget/widget/helper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
ARRAY_ITEM_KEY,
DataType,
FieldType,
MAX_ALLOWED_FIELDS,
ROOT_SCHEMA_KEY,
} from "../constants";
import schemaTestData from "../schemaTestData";
Expand Down Expand Up @@ -469,7 +470,7 @@ describe(".dynamicPropertyPathListFromSchema", () => {
});

describe(".computeSchema", () => {
it("returns LIMIT_EXCEEDED state when source data exceeds limit", () => {
it("returns LIMIT_EXCEEDED state when source data exceeds default limit", () => {
const sourceData = {
number: 10,
text: "text",
Expand Down Expand Up @@ -542,6 +543,7 @@ describe(".computeSchema", () => {
currSourceData: sourceData,
widgetName: "JSONForm1",
fieldThemeStylesheets: {} as FieldThemeStylesheet,
maxAllowedFields: MAX_ALLOWED_FIELDS,
});

expect(response.status).toEqual(ComputedSchemaStatus.LIMIT_EXCEEDED);
Expand All @@ -556,6 +558,7 @@ describe(".computeSchema", () => {
const response = computeSchema({
currSourceData: sourceData,
widgetName: "JSONForm1",
maxAllowedFields: MAX_ALLOWED_FIELDS,
fieldThemeStylesheets: {} as FieldThemeStylesheet,
});

Expand All @@ -565,6 +568,74 @@ describe(".computeSchema", () => {
});
});

it("respects custom maxAllowedFields when higher than default", () => {
const sourceData = {
number: 10,
text: "text",
object1: {
number: 10,
text: "text",
obj: {
arr: {
number: 10,
text: "text",
arr: ["a", "b"],
obj: {
a: 10,
c: 20,
},
},
},
},
object2: {
number: 10,
text: "text",
obj: {
arr: {
number: 10,
text: "text",
arr: ["a", "b"],
obj: {
a: 10,
c: 20,
},
},
},
},
};

const response = computeSchema({
currSourceData: sourceData,
widgetName: "TestWidget",
maxAllowedFields: 60,
fieldThemeStylesheets: {} as FieldThemeStylesheet,
});

expect(response.status).not.toEqual(ComputedSchemaStatus.LIMIT_EXCEEDED);
});

it("respects custom maxAllowedFields when lower than default", () => {
const sourceData = {
name: "John",
age: 30,
email: "[email protected]",
address: {
street: "123 Main St",
city: "Springfield",
country: "USA",
},
};

const response = computeSchema({
currSourceData: sourceData,
widgetName: "TestWidget",
maxAllowedFields: 5,
fieldThemeStylesheets: {} as FieldThemeStylesheet,
});

expect(response.status).toEqual(ComputedSchemaStatus.LIMIT_EXCEEDED);
});

it("returns UNCHANGED status when prev and curr source data are same", () => {
const currSourceData = {
obj: {
Expand All @@ -587,6 +658,7 @@ describe(".computeSchema", () => {
prevSourceData,
widgetName: "JSONForm1",
fieldThemeStylesheets: {} as FieldThemeStylesheet,
maxAllowedFields: MAX_ALLOWED_FIELDS,
});

expect(response.status).toEqual(ComputedSchemaStatus.UNCHANGED);
Expand All @@ -599,6 +671,7 @@ describe(".computeSchema", () => {
currSourceData: schemaTestData.initialDataset.dataSource,
widgetName: "JSONForm1",
fieldThemeStylesheets: schemaTestData.fieldThemeStylesheets,
maxAllowedFields: MAX_ALLOWED_FIELDS,
});

const expectedDynamicPropertyPathList = [
Expand Down Expand Up @@ -630,6 +703,7 @@ describe(".computeSchema", () => {
currSourceData: schemaTestData.initialDataset.dataSource,
currentDynamicPropertyPathList: existingDynamicBindingPropertyPathList,
widgetName: "JSONForm1",
maxAllowedFields: MAX_ALLOWED_FIELDS,
fieldThemeStylesheets: schemaTestData.fieldThemeStylesheets,
});

Expand Down Expand Up @@ -659,6 +733,7 @@ describe(".computeSchema", () => {
currentDynamicPropertyPathList: existingDynamicBindingPropertyPathList,
widgetName: "JSONForm1",
fieldThemeStylesheets: schemaTestData.fieldThemeStylesheets,
maxAllowedFields: MAX_ALLOWED_FIELDS,
});

expect(response.status).toEqual(ComputedSchemaStatus.UPDATED);
Expand Down
13 changes: 11 additions & 2 deletions app/client/src/widgets/JSONFormWidget/widget/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ interface ComputeSchemaProps {
widgetName: string;
currentDynamicPropertyPathList?: PathList;
fieldThemeStylesheets: FieldThemeStylesheet;
maxAllowedFields: number;
hasMaxFieldsChanged?: boolean;
prevDynamicPropertyPathList?: PathList;
}

Expand Down Expand Up @@ -267,13 +269,18 @@ export const computeSchema = ({
currentDynamicPropertyPathList,
currSourceData,
fieldThemeStylesheets,
hasMaxFieldsChanged,
maxAllowedFields = MAX_ALLOWED_FIELDS,
prevSchema = {},
prevSourceData,
widgetName,
}: ComputeSchemaProps): ComputedSchema => {
// Hot path - early exit
const shouldExitEarly =
!hasMaxFieldsChanged &&
(isEmpty(currSourceData) || equal(prevSourceData, currSourceData));

if (isEmpty(currSourceData) || equal(prevSourceData, currSourceData)) {
if (shouldExitEarly) {
return {
status: ComputedSchemaStatus.UNCHANGED,
schema: prevSchema,
Expand All @@ -292,10 +299,12 @@ export const computeSchema = ({
updatedValue: currSourceData,
metaInfo: {
limitExceeded: true,
currentLimit: MAX_ALLOWED_FIELDS,
currentLimit: maxAllowedFields,
},
});
}

if (count > maxAllowedFields) {
return {
status: ComputedSchemaStatus.LIMIT_EXCEEDED,
schema: prevSchema,
Expand Down
24 changes: 20 additions & 4 deletions app/client/src/widgets/JSONFormWidget/widget/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ import type { DerivedPropertiesMap } from "WidgetProvider/factory";
import type { ExecuteTriggerPayload } from "constants/AppsmithActionConstants/ActionConstants";
import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
import type { FieldState, FieldThemeStylesheet, Schema } from "../constants";
import { ActionUpdateDependency, ROOT_SCHEMA_KEY } from "../constants";
import {
ActionUpdateDependency,
MAX_ALLOWED_FIELDS,
ROOT_SCHEMA_KEY,
} from "../constants";
import {
ComputedSchemaStatus,
computeSchema,
Expand Down Expand Up @@ -517,7 +521,12 @@ class JSONFormWidget extends BaseWidget<
this.updateFormData(formData);
}

const { schema } = this.constructAndSaveSchemaIfRequired(prevProps);
const hasMaxFieldsChanged =
prevProps.maxAllowedFields !== this.props.maxAllowedFields;
const { schema } = this.constructAndSaveSchemaIfRequired(
prevProps,
hasMaxFieldsChanged,
);

this.debouncedParseAndSaveFieldState(
this.state.metaInternalFieldState,
Expand Down Expand Up @@ -560,12 +569,16 @@ class JSONFormWidget extends BaseWidget<
* we would get stale/previous data from the __evaluations__ object.
* So it will always stay 1 step behind the actual value.
*/
constructAndSaveSchemaIfRequired = (prevProps?: JSONFormWidgetProps) => {
if (!this.props.autoGenerateForm)
constructAndSaveSchemaIfRequired = (
prevProps?: JSONFormWidgetProps,
hasMaxFieldsChanged?: boolean,
) => {
if (!hasMaxFieldsChanged && !this.props.autoGenerateForm) {
return {
status: ComputedSchemaStatus.UNCHANGED,
schema: this.props?.schema || {},
};
}

const prevSourceData = this.getPreviousSourceData(prevProps);
const currSourceData = this.props?.sourceData;
Expand All @@ -577,6 +590,8 @@ class JSONFormWidget extends BaseWidget<
prevSourceData,
widgetName: this.props.widgetName,
fieldThemeStylesheets: this.props.childStylesheet,
maxAllowedFields: this.props.maxAllowedFields,
hasMaxFieldsChanged,
});
const {
dynamicPropertyPathList,
Expand Down Expand Up @@ -835,6 +850,7 @@ class JSONFormWidget extends BaseWidget<
getFormData={this.getFormData}
isSubmitting={this.state.isSubmitting}
isWidgetMounting={this.isWidgetMounting}
maxAllowedFields={this.props.maxAllowedFields || MAX_ALLOWED_FIELDS}
onConnectData={this.onConnectData}
onFormValidityUpdate={this.onFormValidityUpdate}
onSubmit={this.onSubmit}
Expand Down
23 changes: 22 additions & 1 deletion app/client/src/widgets/JSONFormWidget/widget/propertyConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory";
import { EVALUATION_PATH } from "utils/DynamicBindingUtils";
import type { ButtonWidgetProps } from "widgets/ButtonWidget/widget";
import type { JSONFormWidgetProps } from ".";
import { FieldType, ROOT_SCHEMA_KEY } from "../constants";
import { FieldType, MAX_ALLOWED_FIELDS, ROOT_SCHEMA_KEY } from "../constants";
import { ComputedSchemaStatus, computeSchema } from "./helper";
import generatePanelPropertyConfig from "./propertyConfig/generatePanelPropertyConfig";
import { AutocompleteDataType } from "utils/autocomplete/AutocompleteDataType";
Expand Down Expand Up @@ -142,6 +142,7 @@ export const onGenerateFormClick = ({
prevSchema: widgetProperties.schema,
prevSourceData,
widgetName: widgetProperties.widgetName,
maxAllowedFields: widgetProperties.maxAllowedFields,
});

if (status === ComputedSchemaStatus.LIMIT_EXCEEDED) {
Expand Down Expand Up @@ -414,6 +415,26 @@ export const contentConfig = [
isTriggerProperty: false,
validation: { type: ValidationTypes.TEXT },
},
{
propertyName: "maxAllowedFields",
label: "Max allowed fields",
helperText:
"⚠️ Warning: Increasing this value beyond 50 can severely impact performance and responsiveness",
helpText:
"Sets the maximum number of fields that can be generated in the form. Default value is 50 fields.",
controlType: "INPUT_TEXT",
isBindProperty: true,
isTriggerProperty: false,
validation: {
type: ValidationTypes.NUMBER,
params: {
min: 1,
max: 200,
default: MAX_ALLOWED_FIELDS,
},
},
placeholderText: "1-200",
},
],
expandedByDefault: false,
},
Expand Down

0 comments on commit ae21fa0

Please sign in to comment.