fix: Show JS Function data in autocompletion hints (#19811)

## Description

This PR adds JS function data to autocompletion hints

Fixes #15909 

<img width="278" alt="Screenshot 2023-01-16 at 20 35 55"
src="https://user-images.githubusercontent.com/46670083/212754461-68844350-5d23-4b50-af1f-675b7719dc49.png">


## Type of change

- Bug fix (non-breaking change which fixes an issue)


## How Has This Been Tested?
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
- [ ] 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:
- [ ] Test plan has been approved by relevant developers
- [ ] Test plan has been peer reviewed by QA
- [ ] Cypress test cases have been added and approved by either SDET or
manual QA
- [ ] Organized project review call with relevant stakeholders after
Round 1/2 of QA
- [ ] Added Test Plan Approved label after reveiwing all Cypress test
This commit is contained in:
Favour Ohanekwu 2023-01-17 08:12:16 +01:00 committed by GitHub
parent dffe5890fb
commit a1b07e21c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 86 additions and 10 deletions

View File

@ -0,0 +1,37 @@
import { ObjectsRegistry } from "../../../../support/Objects/Registry";
import { WIDGET } from "../../../../locators/WidgetLocators";
const jsEditor = ObjectsRegistry.JSEditor,
ee = ObjectsRegistry.EntityExplorer,
agHelper = ObjectsRegistry.AggregateHelper,
propPane = ObjectsRegistry.PropertyPane,
CommonLocators = ObjectsRegistry.CommonLocators;
describe("JS Function Execution", function() {
before(() => {
ee.DragDropWidgetNVerify(WIDGET.BUTTON, 200, 200);
});
it("1. Shows js function data as part of autocompletion hints", function() {
jsEditor.CreateJSObject(
`export default {
myFun1: ()=>{
return "yes"
},
myFun2:()=>{
return [{name: "test"}]
}
}`,
{
paste: true,
completeReplace: true,
toRun: false,
shouldCreateNewJSObj: true,
prettify: false,
},
);
ee.SelectEntityByName("Button1", "Widgets");
propPane.EnterJSContext("onClick", `{{JSObject1.`, true, false);
agHelper.AssertContains("myFun1.data");
agHelper.AssertContains("myFun2.data");
});
});

View File

@ -91,7 +91,10 @@ import { FormEvaluationState } from "reducers/evaluationReducers/formEvaluationR
import { FormEvalActionPayload } from "./FormEvaluationSaga"; import { FormEvalActionPayload } from "./FormEvaluationSaga";
import { getSelectedAppTheme } from "selectors/appThemingSelectors"; import { getSelectedAppTheme } from "selectors/appThemingSelectors";
import { resetWidgetsMetaState, updateMetaState } from "actions/metaActions"; import { resetWidgetsMetaState, updateMetaState } from "actions/metaActions";
import { getAllActionValidationConfig } from "selectors/entitiesSelector"; import {
getAllActionValidationConfig,
getAllJSActionsData,
} from "selectors/entitiesSelector";
import { import {
DataTree, DataTree,
UnEvalTree, UnEvalTree,
@ -229,6 +232,7 @@ export function* evaluateTreeSaga(
yield call(evalErrorHandler as any, errors, updatedDataTree, evaluationOrder); yield call(evalErrorHandler as any, errors, updatedDataTree, evaluationOrder);
if (appMode !== APP_MODE.PUBLISHED) { if (appMode !== APP_MODE.PUBLISHED) {
const jsData: Record<string, unknown> = yield select(getAllJSActionsData);
yield call(makeUpdateJSCollection, jsUpdates); yield call(makeUpdateJSCollection, jsUpdates);
yield fork( yield fork(
logSuccessfulBindings, logSuccessfulBindings,
@ -243,6 +247,7 @@ export function* evaluateTreeSaga(
updatedDataTree, updatedDataTree,
unEvalUpdates, unEvalUpdates,
isCreateFirstTree, isCreateFirstTree,
jsData,
); );
} }
yield put(setDependencyMap(dependencies)); yield put(setDependencyMap(dependencies));
@ -566,6 +571,7 @@ function* evaluationChangeListenerSaga(): any {
if (shouldProcessBatchedAction(action)) { if (shouldProcessBatchedAction(action)) {
const postEvalActions = getPostEvalActions(action); const postEvalActions = getPostEvalActions(action);
yield call( yield call(
evaluateTreeSaga, evaluateTreeSaga,
postEvalActions, postEvalActions,

View File

@ -342,6 +342,7 @@ export function* updateTernDefinitions(
dataTree: DataTree, dataTree: DataTree,
updates: DataTreeDiff[], updates: DataTreeDiff[],
isCreateFirstTree: boolean, isCreateFirstTree: boolean,
jsData: Record<string, unknown> = {},
) { ) {
const shouldUpdate: boolean = const shouldUpdate: boolean =
isCreateFirstTree || isCreateFirstTree ||
@ -365,6 +366,7 @@ export function* updateTernDefinitions(
const { def, entityInfo } = dataTreeTypeDefCreator( const { def, entityInfo } = dataTreeTypeDefCreator(
treeWithoutPrivateWidgets, treeWithoutPrivateWidgets,
!!featureFlags.JS_EDITOR, !!featureFlags.JS_EDITOR,
jsData,
); );
CodemirrorTernService.updateDef("DATA_TREE", def, entityInfo); CodemirrorTernService.updateDef("DATA_TREE", def, entityInfo);
const end = performance.now(); const end = performance.now();

View File

@ -916,3 +916,22 @@ export const selectLibrariesForExplorer = createSelector(
return [...queuedInstalls, ...libs]; return [...queuedInstalls, ...libs];
}, },
); );
export const getAllJSActionsData = (state: AppState) => {
const jsActionsData: Record<string, unknown> = {};
const jsCollections = state.entities.jsActions;
jsCollections.forEach((collection) => {
if (collection.data) {
Object.keys(collection.data).forEach((actionId) => {
const jsAction = getJSActions(state, collection.config.id).find(
(action) => action.id === actionId,
);
if (jsAction) {
jsActionsData[`${collection.config.name}.${jsAction.name}`] =
collection.data?.[actionId];
}
});
}
});
return jsActionsData;
};

View File

@ -25,6 +25,7 @@ import { Variable } from "entities/JSCollection";
export const dataTreeTypeDefCreator = ( export const dataTreeTypeDefCreator = (
dataTree: DataTree, dataTree: DataTree,
isJSEditorEnabled: boolean, isJSEditorEnabled: boolean,
jsData: Record<string, unknown> = {},
): { def: Def; entityInfo: Map<string, DataTreeDefEntityInformation> } => { ): { def: Def; entityInfo: Map<string, DataTreeDefEntityInformation> } => {
// When there is a complex data type, we store it in extra def and refer to it in the def // When there is a complex data type, we store it in extra def and refer to it in the def
const extraDefsToDefine: Def = {}; const extraDefsToDefine: Def = {};
@ -67,15 +68,15 @@ export const dataTreeTypeDefCreator = (
const metaObj = entity.meta; const metaObj = entity.meta;
const jsPropertiesDef: Def = {}; const jsPropertiesDef: Def = {};
for (const key in metaObj) { for (const funcName in metaObj) {
// const jsFunctionObj = metaObj[key]; const funcTypeDef = generateJSFunctionTypeDef(
// const { arguments: args } = jsFunctionObj; jsData,
// const argsTypeString = getFunctionsArgsType(args); `${entity.name}.${funcName}`,
// As we don't show args we avoid to get args def of function extraDefsToDefine,
// we will also need to check performance implications here );
jsPropertiesDef[funcName] = funcTypeDef;
const argsTypeString = getFunctionsArgsType([]); // To also show funcName.data in autocompletion hint, we explictly add it here
jsPropertiesDef[key] = argsTypeString; jsPropertiesDef[`${funcName}.data`] = funcTypeDef.data;
} }
for (let i = 0; i < entity.variables.length; i++) { for (let i = 0; i < entity.variables.length; i++) {
@ -197,3 +198,14 @@ export const getFunctionsArgsType = (args: Variable[]): string => {
); );
return argsTypeString ? `fn(${argsTypeString})` : `fn()`; return argsTypeString ? `fn(${argsTypeString})` : `fn()`;
}; };
export function generateJSFunctionTypeDef(
jsData: Record<string, unknown> = {},
fullFunctionName: string,
extraDefs: ExtraDef,
) {
return {
"!type": getFunctionsArgsType([]),
data: generateTypeDef(jsData[fullFunctionName], extraDefs),
};
}