From a1b07e21c72f46412d0a4da4b9d1685e11eedae1 Mon Sep 17 00:00:00 2001 From: Favour Ohanekwu Date: Tue, 17 Jan 2023 08:12:16 +0100 Subject: [PATCH] fix: Show JS Function data in autocompletion hints (#19811) ## Description This PR adds JS function data to autocompletion hints Fixes #15909 Screenshot 2023-01-16 at 20 35 55 ## 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 --- .../ClientSideTests/BugTests/Bug15909_Spec.ts | 37 +++++++++++++++++++ app/client/src/sagas/EvaluationsSaga.ts | 8 +++- app/client/src/sagas/PostEvaluationSagas.ts | 2 + app/client/src/selectors/entitiesSelector.ts | 19 ++++++++++ .../autocomplete/dataTreeTypeDefCreator.ts | 30 ++++++++++----- 5 files changed, 86 insertions(+), 10 deletions(-) create mode 100644 app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/BugTests/Bug15909_Spec.ts diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/BugTests/Bug15909_Spec.ts b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/BugTests/Bug15909_Spec.ts new file mode 100644 index 0000000000..2585cbcfac --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/BugTests/Bug15909_Spec.ts @@ -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"); + }); +}); diff --git a/app/client/src/sagas/EvaluationsSaga.ts b/app/client/src/sagas/EvaluationsSaga.ts index c12facec81..5511a4d03f 100644 --- a/app/client/src/sagas/EvaluationsSaga.ts +++ b/app/client/src/sagas/EvaluationsSaga.ts @@ -91,7 +91,10 @@ import { FormEvaluationState } from "reducers/evaluationReducers/formEvaluationR import { FormEvalActionPayload } from "./FormEvaluationSaga"; import { getSelectedAppTheme } from "selectors/appThemingSelectors"; import { resetWidgetsMetaState, updateMetaState } from "actions/metaActions"; -import { getAllActionValidationConfig } from "selectors/entitiesSelector"; +import { + getAllActionValidationConfig, + getAllJSActionsData, +} from "selectors/entitiesSelector"; import { DataTree, UnEvalTree, @@ -229,6 +232,7 @@ export function* evaluateTreeSaga( yield call(evalErrorHandler as any, errors, updatedDataTree, evaluationOrder); if (appMode !== APP_MODE.PUBLISHED) { + const jsData: Record = yield select(getAllJSActionsData); yield call(makeUpdateJSCollection, jsUpdates); yield fork( logSuccessfulBindings, @@ -243,6 +247,7 @@ export function* evaluateTreeSaga( updatedDataTree, unEvalUpdates, isCreateFirstTree, + jsData, ); } yield put(setDependencyMap(dependencies)); @@ -566,6 +571,7 @@ function* evaluationChangeListenerSaga(): any { if (shouldProcessBatchedAction(action)) { const postEvalActions = getPostEvalActions(action); + yield call( evaluateTreeSaga, postEvalActions, diff --git a/app/client/src/sagas/PostEvaluationSagas.ts b/app/client/src/sagas/PostEvaluationSagas.ts index b6d2d55c55..e8f7ad34c0 100644 --- a/app/client/src/sagas/PostEvaluationSagas.ts +++ b/app/client/src/sagas/PostEvaluationSagas.ts @@ -342,6 +342,7 @@ export function* updateTernDefinitions( dataTree: DataTree, updates: DataTreeDiff[], isCreateFirstTree: boolean, + jsData: Record = {}, ) { const shouldUpdate: boolean = isCreateFirstTree || @@ -365,6 +366,7 @@ export function* updateTernDefinitions( const { def, entityInfo } = dataTreeTypeDefCreator( treeWithoutPrivateWidgets, !!featureFlags.JS_EDITOR, + jsData, ); CodemirrorTernService.updateDef("DATA_TREE", def, entityInfo); const end = performance.now(); diff --git a/app/client/src/selectors/entitiesSelector.ts b/app/client/src/selectors/entitiesSelector.ts index e5be36dd8b..4d9eb90f21 100644 --- a/app/client/src/selectors/entitiesSelector.ts +++ b/app/client/src/selectors/entitiesSelector.ts @@ -916,3 +916,22 @@ export const selectLibrariesForExplorer = createSelector( return [...queuedInstalls, ...libs]; }, ); + +export const getAllJSActionsData = (state: AppState) => { + const jsActionsData: Record = {}; + 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; +}; diff --git a/app/client/src/utils/autocomplete/dataTreeTypeDefCreator.ts b/app/client/src/utils/autocomplete/dataTreeTypeDefCreator.ts index f98e9cf834..cdf23d1c08 100644 --- a/app/client/src/utils/autocomplete/dataTreeTypeDefCreator.ts +++ b/app/client/src/utils/autocomplete/dataTreeTypeDefCreator.ts @@ -25,6 +25,7 @@ import { Variable } from "entities/JSCollection"; export const dataTreeTypeDefCreator = ( dataTree: DataTree, isJSEditorEnabled: boolean, + jsData: Record = {}, ): { def: Def; entityInfo: Map } => { // When there is a complex data type, we store it in extra def and refer to it in the def const extraDefsToDefine: Def = {}; @@ -67,15 +68,15 @@ export const dataTreeTypeDefCreator = ( const metaObj = entity.meta; const jsPropertiesDef: Def = {}; - for (const key in metaObj) { - // const jsFunctionObj = metaObj[key]; - // const { arguments: args } = jsFunctionObj; - // const argsTypeString = getFunctionsArgsType(args); - // As we don't show args we avoid to get args def of function - // we will also need to check performance implications here - - const argsTypeString = getFunctionsArgsType([]); - jsPropertiesDef[key] = argsTypeString; + for (const funcName in metaObj) { + const funcTypeDef = generateJSFunctionTypeDef( + jsData, + `${entity.name}.${funcName}`, + extraDefsToDefine, + ); + jsPropertiesDef[funcName] = funcTypeDef; + // To also show funcName.data in autocompletion hint, we explictly add it here + jsPropertiesDef[`${funcName}.data`] = funcTypeDef.data; } for (let i = 0; i < entity.variables.length; i++) { @@ -197,3 +198,14 @@ export const getFunctionsArgsType = (args: Variable[]): string => { ); return argsTypeString ? `fn(${argsTypeString})` : `fn()`; }; + +export function generateJSFunctionTypeDef( + jsData: Record = {}, + fullFunctionName: string, + extraDefs: ExtraDef, +) { + return { + "!type": getFunctionsArgsType([]), + data: generateTypeDef(jsData[fullFunctionName], extraDefs), + }; +}