PromucFlow_constructor/app/client/src/selectors/navigationSelectors.ts
Anand Srinivasan 9dd015a1e6
feat: peek overlay nested properties + perf improvements (#23414)
Fixes #23057
Fixes #23054

## Description
TL;DR Added support for peeking on nested properties. e.g.
`Api1.data[0].id`.

This won't work when:
-  local variables are involved in the expression. 
e.g. `Api1.data[x].id` won't support peeking at the variable `[x]` or
anything after that.
- library code is involved e.g. `moment`, `_` etc...
- when functions are called. e.g. Api1.data[0].id.toFixed()

Because these cases requires evaluation.

<img width="355" alt="image"
src="https://github.com/appsmithorg/appsmith/assets/66776129/d09d1f0d-1692-46f5-8ec1-592f4fe75f7a">

#### Media (old vs new)
https://www.loom.com/share/dedcf113439c4ee2a19028acca54045e




## Performance improvements:
- Use AST to identify expressions instead marking text manually.
- This reduces the number of markers we process (~ half).

- Before

![image](https://github.com/appsmithorg/appsmith/assets/66776129/bb16ac6b-46dd-4e39-8524-e4f4fa2c3243)

- After

![image](https://github.com/appsmithorg/appsmith/assets/66776129/28f0f209-5437-4718-a74a-f025c576afda)

- AST logs
https://www.loom.com/share/ddde93233cc8470ea04309d8a8332240

#### Type of change
- Bug fix (non-breaking change which fixes an issue)
- New feature (non-breaking change which adds functionality)

## Testing
>
#### How Has This Been Tested?
- [x] Manual
- [x] Jest
- [x] Cypress
>
>
#### Test Plan
https://github.com/appsmithorg/TestSmith/issues/2402

#### Issues raised during DP testing

https://github.com/appsmithorg/appsmith/pull/23414#issuecomment-1553164908

## Checklist:
#### Dev activity
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my own code
- [x] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] PR is being merged under a feature flag


#### QA activity:
- [x] [Speedbreak
features](https://github.com/appsmithorg/TestSmith/wiki/Test-plan-implementation#speedbreaker-features-to-consider-for-every-change)
have been covered
- [x] Test plan covers all impacted features and [areas of
interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans/_edit#areas-of-interest)
- [ ] Test plan has been peer reviewed by project stakeholders and other
QA members
- [x] Manually tested functionality on DP
- [ ] We had an implementation alignment call with stakeholders post QA
Round 2
- [x] Cypress test cases have been added and approved by SDET/manual QA
- [x] Added `Test Plan Approved` label after Cypress tests were reviewed
- [ ] Added `Test Plan Approved` label after JUnit tests were reviewed
2023-05-26 17:12:10 +05:30

154 lines
5.0 KiB
TypeScript

import type { DataTree } from "entities/DataTree/dataTreeFactory";
import { ENTITY_TYPE } from "entities/DataTree/dataTreeFactory";
import { createSelector } from "reselect";
import {
getActionsForCurrentPage,
getDatasources,
getJSCollections,
getPlugins,
} from "selectors/entitiesSelector";
import { getWidgets } from "sagas/selectors";
import { getCurrentPageId } from "selectors/editorSelectors";
import { getActionConfig } from "pages/Editor/Explorer/Actions/helpers";
import { jsCollectionIdURL, widgetURL } from "RouteBuilder";
import { getDataTree } from "selectors/dataTreeSelectors";
import { createNavData } from "utils/NavigationSelector/common";
import { getWidgetChildrenNavData } from "utils/NavigationSelector/WidgetChildren";
import { getJsChildrenNavData } from "utils/NavigationSelector/JsChildren";
import {
getEntityNameAndPropertyPath,
isJSAction,
} from "@appsmith/workers/Evaluation/evaluationUtils";
import type { AppState } from "@appsmith/reducers";
import { PluginType } from "entities/Action";
import type { StoredDatasource } from "entities/Action";
import type { Datasource } from "entities/Datasource";
export type NavigationData = {
name: string;
id: string;
type: ENTITY_TYPE;
url: string | undefined;
navigable: boolean;
children: EntityNavigationData;
key?: string;
pluginName?: string;
isMock?: boolean;
datasourceId?: string;
actionType?: string;
};
export type EntityNavigationData = Record<string, NavigationData>;
export const getEntitiesForNavigation = createSelector(
getActionsForCurrentPage,
getPlugins,
getJSCollections,
getWidgets,
getCurrentPageId,
getDataTree,
getDatasources,
(_: any, entityName: string | undefined) => entityName,
(
actions,
plugins,
jsActions,
widgets,
pageId,
dataTree: DataTree,
datasources: Datasource[],
entityName: string | undefined,
) => {
// data tree retriggers this
jsActions = jsActions.filter((a) => a.config.pageId === pageId);
const navigationData: EntityNavigationData = {};
if (!dataTree) return navigationData;
actions.forEach((action) => {
const plugin = plugins.find(
(plugin) => plugin.id === action.config.pluginId,
);
const datasourceId = (action.config?.datasource as StoredDatasource)?.id;
const datasource = datasources.find(
(datasource) => datasource.id === datasourceId,
);
const config = getActionConfig(action.config.pluginType);
if (!config) return;
navigationData[action.config.name] = createNavData({
id: action.config.id,
name: action.config.name,
type: ENTITY_TYPE.ACTION,
url: config.getURL(
pageId,
action.config.id,
action.config.pluginType,
plugin,
),
children: {},
// Adding below data as it is required for analytical events
pluginName: plugin?.name,
datasourceId: datasource?.id,
isMock: datasource?.isMock,
actionType:
action.config.pluginType === PluginType.DB ? "Query" : "API",
});
});
jsActions.forEach((jsAction) => {
// dataTree for null check
const result = getJsChildrenNavData(jsAction, pageId, dataTree);
navigationData[jsAction.config.name] = createNavData({
id: jsAction.config.id,
name: jsAction.config.name,
type: ENTITY_TYPE.JSACTION,
url: jsCollectionIdURL({ pageId, collectionId: jsAction.config.id }),
children: result?.childNavData || {},
});
});
Object.values(widgets).forEach((widget) => {
// dataTree to get entityDefinitions, for url (can use getWidgetByName?)
const result = getWidgetChildrenNavData(
widget.widgetName,
widget.type,
dataTree,
pageId,
);
navigationData[widget.widgetName] = createNavData({
id: widget.widgetId,
name: widget.widgetName,
type: ENTITY_TYPE.WIDGET,
url: widgetURL({ pageId, selectedWidgets: [widget.widgetId] }),
children: result?.childNavData || {},
});
});
if (
entityName &&
isJSAction(dataTree[entityName]) &&
entityName in navigationData
) {
return {
...navigationData,
this: navigationData[entityName],
};
}
return navigationData;
},
);
export const getJSFunctionNavigationUrl = createSelector(
[
(state: AppState, entityName: string) =>
getEntitiesForNavigation(state, entityName),
(_, __, jsFunctionFullName: string | undefined) => jsFunctionFullName,
],
(entitiesForNavigation, jsFunctionFullName) => {
if (!jsFunctionFullName) return undefined;
const { entityName: jsObjectName, propertyPath: jsFunctionName } =
getEntityNameAndPropertyPath(jsFunctionFullName);
const jsObjectNavigationData = entitiesForNavigation[jsObjectName];
const jsFuncNavigationData =
jsObjectNavigationData && jsObjectNavigationData.children[jsFunctionName];
return jsFuncNavigationData?.url;
},
);