PromucFlow_constructor/app/client/src/WidgetProvider/factory/helpers.ts
Abhinav Jha e62a2d8eb6
fix: error in scroll on load feature (#27214)
## Description
- Fixes #27064 
- issue where the scroll on load feature threw an error when failing to
find the container to scroll into
Fixed by organising the code such that `undefined` values don't have
their properties accessed.
In an edge case, the widget's parent container like widget was not
correctly identified, as a result, accessing the `dynamicHeight`
property of the parent container like widget threw an error.

- Fixes #27209 
- issue where the ButtonWidgetV2's dynamic height feature was
incorrectly configured
    Fixed by removing the dynamic height feature from ButtonWidgetV2.


#### Type of change
- Bug fix (non-breaking change which fixes an issue)
## Testing
- As the issue is an edge case scenario, no reliable mechanism to
replicate this has been identified to automate. @Sripriya93
@kamakshibhat-appsmith
#### How Has This Been Tested?
- [x] Manual
- [ ] JUnit
- [ ] Jest
- [ ] Cypress

#### Test Plan
#### Issues raised during DP testing
## Checklist:
#### Dev activity
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] 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
2023-09-13 11:59:41 +05:30

293 lines
9.0 KiB
TypeScript

import type {
PropertyPaneConfig,
PropertyPaneControlConfig,
PropertyPaneSectionConfig,
} from "constants/PropertyControlConstants";
import { ValidationTypes } from "constants/WidgetValidation";
import { memoize } from "lodash";
import log from "loglevel";
import { generateReactKey } from "../../utils/generators";
import type { WidgetType } from ".";
import WidgetFactory from ".";
import type {
RegisteredWidgetFeatures,
WidgetFeatures,
} from "../../utils/WidgetFeatures";
import {
PropertyPaneConfigTemplates,
WidgetFeaturePropertyPaneEnhancements,
} from "../../utils/WidgetFeatures";
export enum PropertyPaneConfigTypes {
STYLE = "STYLE",
CONTENT = "CONTENT",
}
export function addSearchConfigToPanelConfig(
config: readonly PropertyPaneConfig[],
) {
return config.map((configItem) => {
if ((configItem as PropertyPaneSectionConfig).sectionName) {
const sectionConfig = {
...configItem,
};
if (configItem.children) {
sectionConfig.children = addSearchConfigToPanelConfig(
configItem.children,
);
}
return sectionConfig;
} else if ((configItem as PropertyPaneControlConfig).controlType) {
const controlConfig = configItem as PropertyPaneControlConfig;
if (controlConfig.panelConfig) {
return {
...controlConfig,
panelConfig: {
...controlConfig.panelConfig,
searchConfig: generatePropertyPaneSearchConfig(
controlConfig.panelConfig?.contentChildren ?? [],
controlConfig.panelConfig?.styleChildren ?? [],
),
},
};
}
return controlConfig;
}
return configItem;
});
}
function addSearchSpecificPropertiesToConfig(
config: readonly PropertyPaneConfig[],
tag: string,
): PropertyPaneConfig[] {
return config.map((configItem) => {
if ((configItem as PropertyPaneSectionConfig).sectionName) {
const sectionConfig = {
...configItem,
collapsible: false,
tag,
};
if (configItem.children) {
sectionConfig.children = addSearchSpecificPropertiesToConfig(
configItem.children,
tag,
);
}
return sectionConfig;
} else if ((configItem as PropertyPaneControlConfig).controlType) {
const controlConfig = configItem as PropertyPaneControlConfig;
if (controlConfig.panelConfig) {
return {
...controlConfig,
panelConfig: {
...controlConfig.panelConfig,
searchConfig: generatePropertyPaneSearchConfig(
controlConfig.panelConfig?.contentChildren ?? [],
controlConfig.panelConfig?.styleChildren ?? [],
),
},
};
}
return controlConfig;
}
return configItem;
});
}
export function generatePropertyPaneSearchConfig(
contentConfig: readonly PropertyPaneConfig[],
styleConfig: readonly PropertyPaneConfig[],
) {
return [
...addSearchSpecificPropertiesToConfig(contentConfig, "CONTENT"),
...addSearchSpecificPropertiesToConfig(styleConfig, "STYLE"),
];
}
/* This function recursively parses the property pane configuration and
adds random hash values as `id`.
These are generated once when the Appsmith editor is loaded,
the resulting config is frozen and re-used during the lifecycle
of the current browser session. See WidgetFactory
*/
export const addPropertyConfigIds = (config: PropertyPaneConfig[]) => {
return config.map((sectionOrControlConfig: PropertyPaneConfig) => {
sectionOrControlConfig.id = generateReactKey();
if (sectionOrControlConfig.children) {
sectionOrControlConfig.children = addPropertyConfigIds(
sectionOrControlConfig.children,
);
}
const config = sectionOrControlConfig as PropertyPaneControlConfig;
if (config.panelConfig) {
if (
config.panelConfig.children &&
Array.isArray(config.panelConfig.children)
) {
config.panelConfig.children = addPropertyConfigIds(
config.panelConfig.children,
);
}
if (
config.panelConfig.contentChildren &&
Array.isArray(config.panelConfig.contentChildren)
) {
config.panelConfig.contentChildren = addPropertyConfigIds(
config.panelConfig.contentChildren,
);
}
if (
config.panelConfig.styleChildren &&
Array.isArray(config.panelConfig.styleChildren)
) {
config.panelConfig.styleChildren = addPropertyConfigIds(
config.panelConfig.styleChildren,
);
}
(sectionOrControlConfig as PropertyPaneControlConfig) = config;
}
return sectionOrControlConfig;
});
};
/* General function which enhances the property pane configuration
We can use this to insert or add property configs based on widget
features passed as the second argument.
*/
export function enhancePropertyPaneConfig(
config: PropertyPaneConfig[],
features?: WidgetFeatures,
configType?: PropertyPaneConfigTypes,
widgetType?: WidgetType,
) {
// Enhance property pane with widget features
// TODO(abhinav): The following "configType" check should come
// from the features themselves.
if (
features &&
(configType === undefined || configType === PropertyPaneConfigTypes.CONTENT)
) {
Object.keys(features).forEach((registeredFeature: string) => {
const { sectionIndex } =
features[registeredFeature as RegisteredWidgetFeatures];
const sectionName = (config[sectionIndex] as PropertyPaneSectionConfig)
?.sectionName;
// This has been designed to check if the sectionIndex provided in the
// features configuration of the widget to point to the section named "General"
// If not, it logs an error
// This is a sanity check, and doesn't effect the functionality of the feature
// For consistency, we expect that all "Auto Height" property pane controls
// be present in the "General" section of the property pane
if (!sectionName || sectionName !== "General") {
log.error(
`Invalid section index for feature: ${registeredFeature} in widget: ${widgetType}`,
);
}
if (
Array.isArray(config[sectionIndex].children) &&
PropertyPaneConfigTemplates[
registeredFeature as RegisteredWidgetFeatures
]
) {
config[sectionIndex].children?.push(
...PropertyPaneConfigTemplates[
registeredFeature as RegisteredWidgetFeatures
],
);
config = WidgetFeaturePropertyPaneEnhancements[
registeredFeature as RegisteredWidgetFeatures
](config, widgetType);
}
});
}
return config;
}
/*
ValidationTypes.FUNCTION, allow us to configure functions within them,
However, these are not serializable, which results in them not being able to
be sent to the workers.
We convert these functions to strings and delete the original function properties
in this function
property added `fnString`
property deleted `fn`
*/
export function convertFunctionsToString(config: PropertyPaneConfig[]) {
return config.map((sectionOrControlConfig: PropertyPaneConfig) => {
const controlConfig = sectionOrControlConfig as PropertyPaneControlConfig;
if (
controlConfig.validation &&
controlConfig.validation?.type === ValidationTypes.FUNCTION &&
controlConfig.validation?.params &&
controlConfig.validation?.params.fn
) {
controlConfig.validation.params.fnString =
controlConfig.validation.params.fn.toString();
delete controlConfig.validation.params.fn;
return sectionOrControlConfig;
}
if (sectionOrControlConfig.children) {
sectionOrControlConfig.children = convertFunctionsToString(
sectionOrControlConfig.children,
);
}
const config = sectionOrControlConfig as PropertyPaneControlConfig;
if (
config.panelConfig &&
config.panelConfig.children &&
Array.isArray(config.panelConfig.children)
) {
config.panelConfig.children = convertFunctionsToString(
config.panelConfig.children,
);
(sectionOrControlConfig as PropertyPaneControlConfig) = config;
}
if (
config.panelConfig &&
config.panelConfig.contentChildren &&
Array.isArray(config.panelConfig.contentChildren)
) {
config.panelConfig.contentChildren = convertFunctionsToString(
config.panelConfig.contentChildren,
);
(sectionOrControlConfig as PropertyPaneControlConfig) = config;
}
if (
config.panelConfig &&
config.panelConfig.styleChildren &&
Array.isArray(config.panelConfig.styleChildren)
) {
config.panelConfig.styleChildren = convertFunctionsToString(
config.panelConfig.styleChildren,
);
(sectionOrControlConfig as PropertyPaneControlConfig) = config;
}
return sectionOrControlConfig;
});
}
export const checkIsDropTarget = memoize(function isDropTarget(
type: WidgetType,
) {
return !!WidgetFactory.widgetConfigMap.get(type)?.isCanvas;
});