From 6863aa8ce3c173cf7b908586453aab42e0a047a8 Mon Sep 17 00:00:00 2001 From: Alex Golovanov Date: Tue, 11 Jun 2024 15:54:50 +0300 Subject: [PATCH 01/20] feat: add redux for canvas analytics --- app/client/src/actions/analyticsActions.ts | 19 +++++++++ .../src/ce/constants/ReduxActionConstants.tsx | 8 ++++ .../reducers/uiReducers/analyticsReducer.ts | 40 +++++++++++++++++++ .../src/selectors/analyticsSelectors.tsx | 3 ++ 4 files changed, 70 insertions(+) diff --git a/app/client/src/actions/analyticsActions.ts b/app/client/src/actions/analyticsActions.ts index 3ceb42984b7b..f20a3c71e33f 100644 --- a/app/client/src/actions/analyticsActions.ts +++ b/app/client/src/actions/analyticsActions.ts @@ -7,3 +7,22 @@ export const segmentInitSuccess = () => ({ export const segmentInitUncertain = () => ({ type: ReduxActionTypes.SEGMENT_INIT_UNCERTAIN, }); + +export const recordAnalyticsForSideBySideWidgetHover = ( + widgetType: string, +) => ({ + type: ReduxActionTypes.RECORD_ANALYTICS_FOR_SIDE_BY_SIDE_WIDGET_HOVER, + payload: widgetType, +}); + +export const sendAnalyticsForSideBySideHover = () => ({ + type: ReduxActionTypes.SEND_ANALYTICS_FOR_SIDE_BY_SIDE_HOVER, +}); + +export const recordAnalyticsForSideBySideNavigation = () => ({ + type: ReduxActionTypes.RECORD_ANALYTICS_FOR_SIDE_BY_SIDE_NAVIGATION, +}); + +export const resetAnalyticsForSideBySideHover = () => ({ + type: ReduxActionTypes.RESET_ANALYTICS_FOR_SIDE_BY_SIDE_HOVER, +}); diff --git a/app/client/src/ce/constants/ReduxActionConstants.tsx b/app/client/src/ce/constants/ReduxActionConstants.tsx index 558200332e7e..54b56b29336c 100644 --- a/app/client/src/ce/constants/ReduxActionConstants.tsx +++ b/app/client/src/ce/constants/ReduxActionConstants.tsx @@ -929,6 +929,14 @@ const ActionTypes = { RESET_BUILDING_BLOCK_DRAG_START_TIME: "RESET_BUILDING_BLOCK_DRAG_START_TIME", CLOSE_QUERY_ACTION_TAB_SUCCESS: "CLOSE_QUERY_ACTION_TAB_SUCCESS", LINT_SETUP: "LINT_SETUP", + SEND_ANALYTICS_FOR_SIDE_BY_SIDE_HOVER: + "SEND_ANALYTICS_FOR_SIDE_BY_SIDE_HOVER", + RECORD_ANALYTICS_FOR_SIDE_BY_SIDE_WIDGET_HOVER: + "RECORD_ANALYTICS_FOR_SIDE_BY_SIDE_WIDGET_HOVER", + RECORD_ANALYTICS_FOR_SIDE_BY_SIDE_NAVIGATION: + "RECORD_ANALYTICS_FOR_SIDE_BY_SIDE_NAVIGATION", + RESET_ANALYTICS_FOR_SIDE_BY_SIDE_HOVER: + "RESET_ANALYTICS_FOR_SIDE_BY_SIDE_HOVER", }; export const ReduxActionTypes = { diff --git a/app/client/src/reducers/uiReducers/analyticsReducer.ts b/app/client/src/reducers/uiReducers/analyticsReducer.ts index 354abe161db0..468e308fdde8 100644 --- a/app/client/src/reducers/uiReducers/analyticsReducer.ts +++ b/app/client/src/reducers/uiReducers/analyticsReducer.ts @@ -1,3 +1,4 @@ +import type { ReduxAction } from "@appsmith/constants/ReduxActionConstants"; import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants"; import { createReducer } from "utils/ReducerUtils"; @@ -5,15 +6,54 @@ export type SegmentState = "INIT_SUCCESS" | "INIT_UNCERTAIN"; export const initialState: AnalyticsReduxState = { telemetry: {}, + ideCanvasSideBySideHover: { + navigated: false, + widgetTypes: [], + }, }; export interface AnalyticsReduxState { telemetry: { segmentState?: SegmentState; }; + + ideCanvasSideBySideHover: { + navigated: boolean; + widgetTypes: string[]; + }; } export const handlers = { + [ReduxActionTypes.RESET_ANALYTICS_FOR_SIDE_BY_SIDE_HOVER]: ( + state: AnalyticsReduxState, + ): AnalyticsReduxState => ({ + ...state, + ideCanvasSideBySideHover: { + ...initialState.ideCanvasSideBySideHover, + }, + }), + [ReduxActionTypes.RECORD_ANALYTICS_FOR_SIDE_BY_SIDE_NAVIGATION]: ( + state: AnalyticsReduxState, + ): AnalyticsReduxState => ({ + ...state, + ideCanvasSideBySideHover: { + ...state.ideCanvasSideBySideHover, + navigated: true, + }, + }), + [ReduxActionTypes.RECORD_ANALYTICS_FOR_SIDE_BY_SIDE_WIDGET_HOVER]: ( + state: AnalyticsReduxState, + action: ReduxAction, + ): AnalyticsReduxState => ({ + ...state, + ideCanvasSideBySideHover: { + ...state.ideCanvasSideBySideHover, + widgetTypes: [ + ...state.ideCanvasSideBySideHover.widgetTypes, + action.payload, + ], + }, + }), [ReduxActionTypes.SEGMENT_INITIALIZED]: ( state: AnalyticsReduxState, ): AnalyticsReduxState => ({ diff --git a/app/client/src/selectors/analyticsSelectors.tsx b/app/client/src/selectors/analyticsSelectors.tsx index b2f84abb6ef1..b8228ca20b6b 100644 --- a/app/client/src/selectors/analyticsSelectors.tsx +++ b/app/client/src/selectors/analyticsSelectors.tsx @@ -2,3 +2,6 @@ import type { AppState } from "@appsmith/reducers"; export const getSegmentState = (state: AppState) => state.ui.analytics.telemetry.segmentState; + +export const getIdeCanvasSideBySideHoverState = (state: AppState) => + state.ui.analytics.ideCanvasSideBySideHover; From 8e684941085520bd37419bc73d7a888e46e67df9 Mon Sep 17 00:00:00 2001 From: Alex Golovanov Date: Tue, 11 Jun 2024 16:05:32 +0300 Subject: [PATCH 02/20] feat: add analytics canvas hover saga --- app/client/src/ce/sagas/index.tsx | 2 + app/client/src/ce/utils/analyticsUtilTypes.ts | 3 +- app/client/src/sagas/AnalyticsSaga.ts | 72 +++++++++++++++++++ 3 files changed, 76 insertions(+), 1 deletion(-) diff --git a/app/client/src/ce/sagas/index.tsx b/app/client/src/ce/sagas/index.tsx index 54daa5ccc22a..c1cb60a4e060 100644 --- a/app/client/src/ce/sagas/index.tsx +++ b/app/client/src/ce/sagas/index.tsx @@ -51,6 +51,7 @@ import entityNavigationSaga from "sagas/NavigationSagas"; import communityTemplateSagas from "sagas/CommunityTemplatesSagas"; import anvilSagas from "layoutSystems/anvil/integrations/sagas"; import ideSagas from "sagas/IDESaga"; +import sendSideBySideWidgetHoverAnalyticsEventSaga from "sagas/AnalyticsSaga"; /* Sagas that are registered by a module that is designed to be independent of the core platform */ import ternSagas from "sagas/TernSaga"; @@ -110,4 +111,5 @@ export const sagas = [ anvilSagas, ternSagas, ideSagas, + sendSideBySideWidgetHoverAnalyticsEventSaga, ]; diff --git a/app/client/src/ce/utils/analyticsUtilTypes.ts b/app/client/src/ce/utils/analyticsUtilTypes.ts index 36f1c025f6b6..988306feb3d7 100644 --- a/app/client/src/ce/utils/analyticsUtilTypes.ts +++ b/app/client/src/ce/utils/analyticsUtilTypes.ts @@ -354,7 +354,8 @@ export type EventName = | HOMEPAGE_CREATE_APP_FROM_TEMPLATE_EVENTS | "EDITOR_MODE_CHANGE" | BUILDING_BLOCKS_EVENTS - | "VISIT_SELF_HOST_DOCS"; + | "VISIT_SELF_HOST_DOCS" + | "CANVAS_HOVER"; type HOMEPAGE_CREATE_APP_FROM_TEMPLATE_EVENTS = | "TEMPLATE_DROPDOWN_CLICK" diff --git a/app/client/src/sagas/AnalyticsSaga.ts b/app/client/src/sagas/AnalyticsSaga.ts index 1790faaad832..266639437f62 100644 --- a/app/client/src/sagas/AnalyticsSaga.ts +++ b/app/client/src/sagas/AnalyticsSaga.ts @@ -11,6 +11,23 @@ import { } from "./helper"; import get from "lodash/get"; import log from "loglevel"; +import { all, put, select, takeEvery } from "redux-saga/effects"; +import { getIdeCanvasSideBySideHoverState } from "selectors/analyticsSelectors"; + +import { EditorViewMode } from "@appsmith/entities/IDE/constants"; +import { + recordAnalyticsForSideBySideNavigation, + resetAnalyticsForSideBySideHover, +} from "actions/analyticsActions"; + +import type { routeChanged } from "actions/focusHistoryActions"; +import { NavigationMethod } from "utils/history"; +import { getIDEViewMode } from "selectors/ideSelectors"; + +import { + JS_COLLECTION_EDITOR_PATH, + WIDGETS_EDITOR_BASE_PATH, +} from "constants/routes"; export function* sendAnalyticsEventSaga( type: ReduxActionType, @@ -50,3 +67,58 @@ export function* sendAnalyticsEventSaga( log.error("Failed to send analytics event"); } } + +function* sendSideBySideWidgetHoverAnalyticsEventSaga() { + const { + navigated, + widgetTypes, + }: ReturnType = yield select( + getIdeCanvasSideBySideHoverState, + ); + + const payload = { + navigated, + widgetHover: widgetTypes.length > 0, + widgetTypes: Array.from(new Set(widgetTypes)), + }; + + yield put(resetAnalyticsForSideBySideHover()); + + AnalyticsUtil.logEvent("CANVAS_HOVER", payload); +} + +function* routeChangeInSideBySideModeSaga({ + payload, +}: ReturnType) { + const viewMode: ReturnType = + yield select(getIDEViewMode); + + const { + location: { + pathname: pathName, + state: { invokedBy }, + }, + prevLocation: { pathname: prevPathName }, + } = payload; + + if ( + invokedBy === NavigationMethod.CanvasClick && + viewMode === EditorViewMode.SplitScreen && + pathName.includes(WIDGETS_EDITOR_BASE_PATH) && + prevPathName.includes(JS_COLLECTION_EDITOR_PATH) + ) { + yield put(recordAnalyticsForSideBySideNavigation()); + yield sendSideBySideWidgetHoverAnalyticsEventSaga(); + } +} + +export default function* root() { + yield all([ + takeEvery( + ReduxActionTypes.SEND_ANALYTICS_FOR_SIDE_BY_SIDE_HOVER, + sendSideBySideWidgetHoverAnalyticsEventSaga, + ), + + takeEvery(ReduxActionTypes.ROUTE_CHANGED, routeChangeInSideBySideModeSaga), + ]); +} From 31365465c2c83fe56d5b66dae8b51df1fb053de0 Mon Sep 17 00:00:00 2001 From: Alex Golovanov Date: Tue, 11 Jun 2024 16:06:34 +0300 Subject: [PATCH 03/20] feat: add hook to verify side-by-side is active and present --- .../utils/hooks/useIsInSideBySideEditor.ts | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 app/client/src/utils/hooks/useIsInSideBySideEditor.ts diff --git a/app/client/src/utils/hooks/useIsInSideBySideEditor.ts b/app/client/src/utils/hooks/useIsInSideBySideEditor.ts new file mode 100644 index 000000000000..ed0e65e22c41 --- /dev/null +++ b/app/client/src/utils/hooks/useIsInSideBySideEditor.ts @@ -0,0 +1,22 @@ +import { useSelector } from "react-redux"; +import { useLocation } from "react-router"; + +import { getIDEViewMode } from "selectors/ideSelectors"; +import { EditorViewMode } from "@appsmith/entities/IDE/constants"; +import { JS_COLLECTION_EDITOR_PATH } from "constants/routes"; + +/** + * Hook to check if current component is in side-by-side editor mode. + */ +const useIsInSideBySideEditor = () => { + const { pathname } = useLocation(); + const ideViewMode = useSelector(getIDEViewMode); + + const isInSideBySideEditor = + ideViewMode === EditorViewMode.SplitScreen && + pathname.includes(JS_COLLECTION_EDITOR_PATH); + + return isInSideBySideEditor; +}; + +export default useIsInSideBySideEditor; From e360f959ae2ee8936bfa71f5f723b67fc1e875f5 Mon Sep 17 00:00:00 2001 From: Alex Golovanov Date: Tue, 11 Jun 2024 16:10:06 +0300 Subject: [PATCH 04/20] feat: add hover logging in side-by-side mode --- .../common/widgetName/WidgetNameLayer.tsx | 2 +- .../layoutSystems/common/widgetName/index.tsx | 92 ++++++++++--------- 2 files changed, 48 insertions(+), 46 deletions(-) diff --git a/app/client/src/layoutSystems/common/widgetName/WidgetNameLayer.tsx b/app/client/src/layoutSystems/common/widgetName/WidgetNameLayer.tsx index 1e2e06e5e65c..2e04c314a2e6 100644 --- a/app/client/src/layoutSystems/common/widgetName/WidgetNameLayer.tsx +++ b/app/client/src/layoutSystems/common/widgetName/WidgetNameLayer.tsx @@ -1,4 +1,4 @@ -import WidgetNameComponent from "layoutSystems/common/widgetName"; +import WidgetNameComponent from "./"; import React from "react"; import { getErrorCount } from "./utils"; diff --git a/app/client/src/layoutSystems/common/widgetName/index.tsx b/app/client/src/layoutSystems/common/widgetName/index.tsx index 626ba3bb8443..1a1f4e5d15e4 100644 --- a/app/client/src/layoutSystems/common/widgetName/index.tsx +++ b/app/client/src/layoutSystems/common/widgetName/index.tsx @@ -1,9 +1,11 @@ +import React, { useEffect, useMemo } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import memoize from "micro-memoize"; +import styled from "styled-components"; + import type { AppState } from "@appsmith/reducers"; import { bindDataToWidget } from "actions/propertyPaneActions"; import type { WidgetType } from "constants/WidgetConstants"; -import React, { useMemo } from "react"; -// import type { CSSProperties } from "react"; -import { useDispatch, useSelector } from "react-redux"; import { SelectionRequestType } from "sagas/WidgetSelectUtils"; import { getIsAppSettingsPaneWithNavigationTabOpen } from "selectors/appSettingsPaneSelectors"; import { hideErrors } from "selectors/debuggerSelectors"; @@ -13,7 +15,6 @@ import { snipingModeSelector, } from "selectors/editorSelectors"; import { getIsTableFilterPaneVisible } from "selectors/tableFilterSelectors"; -import styled from "styled-components"; import AnalyticsUtil from "@appsmith/utils/AnalyticsUtil"; import PerformanceTracker, { PerformanceTransactionName, @@ -32,8 +33,9 @@ import { } from "selectors/widgetSelectors"; import { RESIZE_BORDER_BUFFER } from "layoutSystems/common/resizer/common"; import { Layers } from "constants/Layers"; -import memoize from "micro-memoize"; import { NavigationMethod } from "utils/history"; +import { recordAnalyticsForSideBySideWidgetHover } from "actions/analyticsActions"; +import useIsInSideBySideEditor from "utils/hooks/useIsInSideBySideEditor"; const WidgetTypes = WidgetFactory.widgetTypes; export const WidgetNameComponentHeight = theme.spaces[10]; @@ -78,6 +80,16 @@ interface WidgetNameComponentProps { } export function WidgetNameComponent(props: WidgetNameComponentProps) { + const { + errorCount, + showControls, + topRow, + type: widgetType, + widgetId, + widgetName, + widgetWidth, + } = props; + const dispatch = useDispatch(); const isSnipingMode = useSelector(snipingModeSelector); const isPreviewMode = useSelector(combinedPreviewModeSelector); @@ -89,61 +101,60 @@ export function WidgetNameComponent(props: WidgetNameComponentProps) { (state: AppState) => state.ui.widgetDragResize.isAutoCanvasResizing, ); const isAutoLayout = useSelector(getIsAutoLayout); + // Dispatch hook handy to set a widget as focused/selected const { selectWidget } = useWidgetSelection(); - - const isFocused = useSelector(isWidgetFocused(props.widgetId)); - + const isFocused = useSelector(isWidgetFocused(widgetId)); const shouldHideErrors = useSelector(hideErrors); - const isTableFilterPaneVisible = useSelector(getIsTableFilterPaneVisible); // True if the selected widget's property pane is open. const isActiveInPropertyPane = useSelector( - isCurrentWidgetActiveInPropertyPane(props.widgetId), + isCurrentWidgetActiveInPropertyPane(widgetId), ); const togglePropertyEditor = memoize((e: any) => { if (isSnipingMode) { dispatch( bindDataToWidget({ - widgetId: props.widgetId, + widgetId: widgetId, }), ); } else if (!isActiveInPropertyPane) { PerformanceTracker.startTracking( PerformanceTransactionName.OPEN_PROPERTY_PANE, - { widgetId: props.widgetId }, + { widgetId: widgetId }, true, - [{ name: "widget_type", value: props.type }], + [{ name: "widget_type", value: widgetType }], ); AnalyticsUtil.logEvent("PROPERTY_PANE_OPEN_CLICK", { - widgetType: props.type, - widgetId: props.widgetId, + widgetType: widgetType, + widgetId: widgetId, }); // hide table filter pane if open isTableFilterPaneVisible && showTableFilterPane && showTableFilterPane(); selectWidget && selectWidget( SelectionRequestType.One, - [props.widgetId], + [widgetId], NavigationMethod.CanvasClick, ); } else { AnalyticsUtil.logEvent("PROPERTY_PANE_CLOSE_CLICK", { - widgetType: props.type, - widgetId: props.widgetId, + widgetType: widgetType, + widgetId: widgetId, }); } e.preventDefault(); e.stopPropagation(); }); - const showAsSelected = useSelector(showWidgetAsSelected(props.widgetId)); - const isMultiSelected = useSelector(isMultiSelectedWidget(props.widgetId)); + const showAsSelected = useSelector(showWidgetAsSelected(widgetId)); + const isMultiSelected = useSelector(isMultiSelectedWidget(widgetId)); // True when any widget is dragging or resizing, including this one const resizingOrDragging = useSelector(isResizingOrDragging); + const shouldShowWidgetName = () => { return ( !isAutoCanvasResizing && @@ -153,7 +164,7 @@ export function WidgetNameComponent(props: WidgetNameComponentProps) { !isMultiSelected && (isSnipingMode ? isFocused - : props.showControls || + : showControls || ((isFocused || showAsSelected) && !resizingOrDragging)) ); }; @@ -162,10 +173,10 @@ export function WidgetNameComponent(props: WidgetNameComponentProps) { // in case of widget selection in sniping mode, if it's successful we bind the data else carry on // with sniping mode. const showWidgetName = shouldShowWidgetName(); - const isModalWidget = props.type === WidgetTypes.MODAL_WIDGET; + const isModalWidget = widgetType === WidgetTypes.MODAL_WIDGET; const getCurrentActivity = () => { let activity = - props.type === WidgetTypes.MODAL_WIDGET + widgetType === WidgetTypes.MODAL_WIDGET ? Activities.HOVERING : Activities.NONE; if (isFocused) activity = Activities.HOVERING; @@ -181,6 +192,13 @@ export function WidgetNameComponent(props: WidgetNameComponentProps) { showAsSelected, ]); + const isInSideBySideEditor = useIsInSideBySideEditor(); + useEffect(() => { + if (isInSideBySideEditor && currentActivity === Activities.HOVERING) { + dispatch(recordAnalyticsForSideBySideWidgetHover(widgetType)); + } + }, [currentActivity, isInSideBySideEditor, widgetType]); + const getPositionOffset = (): [number, number] => { if (isSnipingMode) { //ToDo: (Ashok) This is a hasty fix from my end. need to check the padding and margins and give a meaningful constant. @@ -196,38 +214,22 @@ export function WidgetNameComponent(props: WidgetNameComponentProps) { isAutoLayout, ]); - // const positionStyle: CSSProperties = useMemo(() => { - // return { - // top: - // props.topRow > 2 - // ? `${-1 * WidgetNameComponentHeight + 1 + positionOffset[0]}px` - // : `calc(100% - ${1 + positionOffset[0]}px)`, - // height: WidgetNameComponentHeight + "px", - // marginLeft: positionOffset[1] + "px", - // zIndex: Layers.widgetName, - // }; - // }, [ - // Layers?.widgetName, - // props.topRow, - // positionOffset, - // WidgetNameComponentHeight, - // ]); return showWidgetName ? ( From c40e83f2b7e4b7ae291aa9c56dfe5a5500e8fec6 Mon Sep 17 00:00:00 2001 From: Alex Golovanov Date: Tue, 11 Jun 2024 16:11:13 +0300 Subject: [PATCH 05/20] feat: add mouseleave handler for canvas hover analytics --- .../src/layoutSystems/CanvasFactory.tsx | 33 +++++++++++++++++-- app/client/src/layoutSystems/constants.ts | 3 ++ .../src/layoutSystems/styles.module.css | 3 ++ 3 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 app/client/src/layoutSystems/constants.ts create mode 100644 app/client/src/layoutSystems/styles.module.css diff --git a/app/client/src/layoutSystems/CanvasFactory.tsx b/app/client/src/layoutSystems/CanvasFactory.tsx index 83682c80c59d..fcb7879e3546 100644 --- a/app/client/src/layoutSystems/CanvasFactory.tsx +++ b/app/client/src/layoutSystems/CanvasFactory.tsx @@ -1,11 +1,18 @@ import React, { memo, useMemo } from "react"; -import { useSelector } from "react-redux"; +import { useDispatch, useSelector } from "react-redux"; + import { getRenderMode } from "selectors/editorSelectors"; import { getLayoutSystemType } from "selectors/layoutSystemSelectors"; import type { WidgetProps } from "widgets/BaseWidget"; import withWidgetProps from "widgets/withWidgetProps"; import { getLayoutSystem } from "./withLayoutSystemWidgetHOC"; +import { sendAnalyticsForSideBySideHover } from "actions/analyticsActions"; + +import { LAYOUT_WRAPPER_ID } from "./constants"; +import styles from "./styles.module.css"; +import useIsInSideBySideEditor from "utils/hooks/useIsInSideBySideEditor"; + // ToDo(#27615): destructure withWidgetProps to withCanvasProps by picking only necessary props of a canvas. /** @@ -15,6 +22,8 @@ import { getLayoutSystem } from "./withLayoutSystemWidgetHOC"; */ const LayoutSystemBasedCanvas = memo((props: WidgetProps) => { + const dispatch = useDispatch(); + const renderMode = useSelector(getRenderMode); const layoutSystemType = useSelector(getLayoutSystemType); const { canvasSystem } = useMemo( @@ -27,7 +36,27 @@ const LayoutSystemBasedCanvas = memo((props: WidgetProps) => { ], ); const { Canvas, propertyEnhancer } = canvasSystem; - return ; + + const isInSideBySideEditor = useIsInSideBySideEditor(); + const handleMouseLeave: React.MouseEventHandler = (e) => { + if ( + isInSideBySideEditor && + e.relatedTarget instanceof Element && + e.relatedTarget.contains(document.getElementById(LAYOUT_WRAPPER_ID)) + ) { + dispatch(sendAnalyticsForSideBySideHover()); + } + }; + + return ( +
+ +
+ ); }); const HydratedLayoutSystemBasedCanvas = withWidgetProps( diff --git a/app/client/src/layoutSystems/constants.ts b/app/client/src/layoutSystems/constants.ts new file mode 100644 index 000000000000..cf13dc70f369 --- /dev/null +++ b/app/client/src/layoutSystems/constants.ts @@ -0,0 +1,3 @@ +import { v4 as uuid } from "uuid"; + +export const LAYOUT_WRAPPER_ID = uuid(); diff --git a/app/client/src/layoutSystems/styles.module.css b/app/client/src/layoutSystems/styles.module.css new file mode 100644 index 000000000000..94ce0b845501 --- /dev/null +++ b/app/client/src/layoutSystems/styles.module.css @@ -0,0 +1,3 @@ +.root { + display: contents; +} From 87c76b116f6c4e02c65b189731f0ff558ed688e7 Mon Sep 17 00:00:00 2001 From: Alex Golovanov Date: Thu, 13 Jun 2024 19:52:39 +0300 Subject: [PATCH 06/20] chore: revert widget name changes --- .../common/widgetName/WidgetNameLayer.tsx | 2 +- .../layoutSystems/common/widgetName/index.tsx | 92 +++++++++---------- 2 files changed, 46 insertions(+), 48 deletions(-) diff --git a/app/client/src/layoutSystems/common/widgetName/WidgetNameLayer.tsx b/app/client/src/layoutSystems/common/widgetName/WidgetNameLayer.tsx index 2e04c314a2e6..1e2e06e5e65c 100644 --- a/app/client/src/layoutSystems/common/widgetName/WidgetNameLayer.tsx +++ b/app/client/src/layoutSystems/common/widgetName/WidgetNameLayer.tsx @@ -1,4 +1,4 @@ -import WidgetNameComponent from "./"; +import WidgetNameComponent from "layoutSystems/common/widgetName"; import React from "react"; import { getErrorCount } from "./utils"; diff --git a/app/client/src/layoutSystems/common/widgetName/index.tsx b/app/client/src/layoutSystems/common/widgetName/index.tsx index 1a1f4e5d15e4..626ba3bb8443 100644 --- a/app/client/src/layoutSystems/common/widgetName/index.tsx +++ b/app/client/src/layoutSystems/common/widgetName/index.tsx @@ -1,11 +1,9 @@ -import React, { useEffect, useMemo } from "react"; -import { useDispatch, useSelector } from "react-redux"; -import memoize from "micro-memoize"; -import styled from "styled-components"; - import type { AppState } from "@appsmith/reducers"; import { bindDataToWidget } from "actions/propertyPaneActions"; import type { WidgetType } from "constants/WidgetConstants"; +import React, { useMemo } from "react"; +// import type { CSSProperties } from "react"; +import { useDispatch, useSelector } from "react-redux"; import { SelectionRequestType } from "sagas/WidgetSelectUtils"; import { getIsAppSettingsPaneWithNavigationTabOpen } from "selectors/appSettingsPaneSelectors"; import { hideErrors } from "selectors/debuggerSelectors"; @@ -15,6 +13,7 @@ import { snipingModeSelector, } from "selectors/editorSelectors"; import { getIsTableFilterPaneVisible } from "selectors/tableFilterSelectors"; +import styled from "styled-components"; import AnalyticsUtil from "@appsmith/utils/AnalyticsUtil"; import PerformanceTracker, { PerformanceTransactionName, @@ -33,9 +32,8 @@ import { } from "selectors/widgetSelectors"; import { RESIZE_BORDER_BUFFER } from "layoutSystems/common/resizer/common"; import { Layers } from "constants/Layers"; +import memoize from "micro-memoize"; import { NavigationMethod } from "utils/history"; -import { recordAnalyticsForSideBySideWidgetHover } from "actions/analyticsActions"; -import useIsInSideBySideEditor from "utils/hooks/useIsInSideBySideEditor"; const WidgetTypes = WidgetFactory.widgetTypes; export const WidgetNameComponentHeight = theme.spaces[10]; @@ -80,16 +78,6 @@ interface WidgetNameComponentProps { } export function WidgetNameComponent(props: WidgetNameComponentProps) { - const { - errorCount, - showControls, - topRow, - type: widgetType, - widgetId, - widgetName, - widgetWidth, - } = props; - const dispatch = useDispatch(); const isSnipingMode = useSelector(snipingModeSelector); const isPreviewMode = useSelector(combinedPreviewModeSelector); @@ -101,60 +89,61 @@ export function WidgetNameComponent(props: WidgetNameComponentProps) { (state: AppState) => state.ui.widgetDragResize.isAutoCanvasResizing, ); const isAutoLayout = useSelector(getIsAutoLayout); - // Dispatch hook handy to set a widget as focused/selected const { selectWidget } = useWidgetSelection(); - const isFocused = useSelector(isWidgetFocused(widgetId)); + + const isFocused = useSelector(isWidgetFocused(props.widgetId)); + const shouldHideErrors = useSelector(hideErrors); + const isTableFilterPaneVisible = useSelector(getIsTableFilterPaneVisible); // True if the selected widget's property pane is open. const isActiveInPropertyPane = useSelector( - isCurrentWidgetActiveInPropertyPane(widgetId), + isCurrentWidgetActiveInPropertyPane(props.widgetId), ); const togglePropertyEditor = memoize((e: any) => { if (isSnipingMode) { dispatch( bindDataToWidget({ - widgetId: widgetId, + widgetId: props.widgetId, }), ); } else if (!isActiveInPropertyPane) { PerformanceTracker.startTracking( PerformanceTransactionName.OPEN_PROPERTY_PANE, - { widgetId: widgetId }, + { widgetId: props.widgetId }, true, - [{ name: "widget_type", value: widgetType }], + [{ name: "widget_type", value: props.type }], ); AnalyticsUtil.logEvent("PROPERTY_PANE_OPEN_CLICK", { - widgetType: widgetType, - widgetId: widgetId, + widgetType: props.type, + widgetId: props.widgetId, }); // hide table filter pane if open isTableFilterPaneVisible && showTableFilterPane && showTableFilterPane(); selectWidget && selectWidget( SelectionRequestType.One, - [widgetId], + [props.widgetId], NavigationMethod.CanvasClick, ); } else { AnalyticsUtil.logEvent("PROPERTY_PANE_CLOSE_CLICK", { - widgetType: widgetType, - widgetId: widgetId, + widgetType: props.type, + widgetId: props.widgetId, }); } e.preventDefault(); e.stopPropagation(); }); + const showAsSelected = useSelector(showWidgetAsSelected(props.widgetId)); - const showAsSelected = useSelector(showWidgetAsSelected(widgetId)); - const isMultiSelected = useSelector(isMultiSelectedWidget(widgetId)); + const isMultiSelected = useSelector(isMultiSelectedWidget(props.widgetId)); // True when any widget is dragging or resizing, including this one const resizingOrDragging = useSelector(isResizingOrDragging); - const shouldShowWidgetName = () => { return ( !isAutoCanvasResizing && @@ -164,7 +153,7 @@ export function WidgetNameComponent(props: WidgetNameComponentProps) { !isMultiSelected && (isSnipingMode ? isFocused - : showControls || + : props.showControls || ((isFocused || showAsSelected) && !resizingOrDragging)) ); }; @@ -173,10 +162,10 @@ export function WidgetNameComponent(props: WidgetNameComponentProps) { // in case of widget selection in sniping mode, if it's successful we bind the data else carry on // with sniping mode. const showWidgetName = shouldShowWidgetName(); - const isModalWidget = widgetType === WidgetTypes.MODAL_WIDGET; + const isModalWidget = props.type === WidgetTypes.MODAL_WIDGET; const getCurrentActivity = () => { let activity = - widgetType === WidgetTypes.MODAL_WIDGET + props.type === WidgetTypes.MODAL_WIDGET ? Activities.HOVERING : Activities.NONE; if (isFocused) activity = Activities.HOVERING; @@ -192,13 +181,6 @@ export function WidgetNameComponent(props: WidgetNameComponentProps) { showAsSelected, ]); - const isInSideBySideEditor = useIsInSideBySideEditor(); - useEffect(() => { - if (isInSideBySideEditor && currentActivity === Activities.HOVERING) { - dispatch(recordAnalyticsForSideBySideWidgetHover(widgetType)); - } - }, [currentActivity, isInSideBySideEditor, widgetType]); - const getPositionOffset = (): [number, number] => { if (isSnipingMode) { //ToDo: (Ashok) This is a hasty fix from my end. need to check the padding and margins and give a meaningful constant. @@ -214,22 +196,38 @@ export function WidgetNameComponent(props: WidgetNameComponentProps) { isAutoLayout, ]); + // const positionStyle: CSSProperties = useMemo(() => { + // return { + // top: + // props.topRow > 2 + // ? `${-1 * WidgetNameComponentHeight + 1 + positionOffset[0]}px` + // : `calc(100% - ${1 + positionOffset[0]}px)`, + // height: WidgetNameComponentHeight + "px", + // marginLeft: positionOffset[1] + "px", + // zIndex: Layers.widgetName, + // }; + // }, [ + // Layers?.widgetName, + // props.topRow, + // positionOffset, + // WidgetNameComponentHeight, + // ]); return showWidgetName ? ( From 83c60d78144e5c002449ba147a1ed475bbaa5e3e Mon Sep 17 00:00:00 2001 From: Alex Golovanov Date: Thu, 13 Jun 2024 19:53:36 +0300 Subject: [PATCH 07/20] perf: move entity segment resolution to a util --- app/client/src/pages/Editor/IDE/hooks.ts | 51 ++---------------- app/client/src/pages/Editor/utils.tsx | 66 ++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 46 deletions(-) diff --git a/app/client/src/pages/Editor/IDE/hooks.ts b/app/client/src/pages/Editor/IDE/hooks.ts index cd2b4e9a2db5..ef4639e90119 100644 --- a/app/client/src/pages/Editor/IDE/hooks.ts +++ b/app/client/src/pages/Editor/IDE/hooks.ts @@ -34,6 +34,7 @@ import { createEditorFocusInfoKey } from "@appsmith/navigation/FocusStrategy/App import { FocusElement } from "navigation/FocusElements"; import { closeJSActionTab } from "actions/jsActionActions"; import { closeQueryActionTab } from "actions/pluginActionActions"; +import { getCurrentEntityInfo } from "../utils"; export const useCurrentAppState = () => { const [appState, setAppState] = useState(EditorState.EDITOR); @@ -60,52 +61,10 @@ export const useCurrentEditorState = () => { * */ useEffect(() => { - const currentEntityInfo = identifyEntityFromPath(location.pathname); - switch (currentEntityInfo.entity) { - case FocusEntity.QUERY: - case FocusEntity.API: - case FocusEntity.QUERY_MODULE_INSTANCE: - setSelectedSegment(EditorEntityTab.QUERIES); - setSelectedSegmentState(EditorEntityTabState.Edit); - break; - case FocusEntity.QUERY_LIST: - setSelectedSegment(EditorEntityTab.QUERIES); - setSelectedSegmentState(EditorEntityTabState.List); - break; - case FocusEntity.QUERY_ADD: - setSelectedSegment(EditorEntityTab.QUERIES); - setSelectedSegmentState(EditorEntityTabState.Add); - break; - case FocusEntity.JS_OBJECT: - case FocusEntity.JS_MODULE_INSTANCE: - setSelectedSegment(EditorEntityTab.JS); - setSelectedSegmentState(EditorEntityTabState.Edit); - break; - case FocusEntity.JS_OBJECT_ADD: - setSelectedSegment(EditorEntityTab.JS); - setSelectedSegmentState(EditorEntityTabState.Add); - break; - case FocusEntity.JS_OBJECT_LIST: - setSelectedSegment(EditorEntityTab.JS); - setSelectedSegmentState(EditorEntityTabState.List); - break; - case FocusEntity.CANVAS: - setSelectedSegment(EditorEntityTab.UI); - setSelectedSegmentState(EditorEntityTabState.Add); - break; - case FocusEntity.PROPERTY_PANE: - setSelectedSegment(EditorEntityTab.UI); - setSelectedSegmentState(EditorEntityTabState.Edit); - break; - case FocusEntity.WIDGET_LIST: - setSelectedSegment(EditorEntityTab.UI); - setSelectedSegmentState(EditorEntityTabState.List); - break; - default: - setSelectedSegment(EditorEntityTab.UI); - setSelectedSegmentState(EditorEntityTabState.Add); - break; - } + const { entity } = identifyEntityFromPath(location.pathname); + const { segment, segmentMode } = getCurrentEntityInfo(entity); + setSelectedSegment(segment); + setSelectedSegmentState(segmentMode); }, [location.pathname]); return { diff --git a/app/client/src/pages/Editor/utils.tsx b/app/client/src/pages/Editor/utils.tsx index fab1158cc45d..59c2f6ed077d 100644 --- a/app/client/src/pages/Editor/utils.tsx +++ b/app/client/src/pages/Editor/utils.tsx @@ -33,6 +33,11 @@ import { getAssetUrl } from "@appsmith/utils/airgapHelpers"; import type { Plugin } from "api/PluginApi"; import ImageAlt from "assets/images/placeholder-image.svg"; import { Icon } from "design-system"; +import { + EditorEntityTab, + EditorEntityTabState, +} from "@appsmith/entities/IDE/constants"; +import { FocusEntity } from "navigation/FocusEntity"; export const draggableElement = ( id: string, @@ -415,3 +420,64 @@ export function getPluginImagesFromPlugins(plugins: Plugin[]) { }); return pluginImages; } + +/** + * Resolve segment and segmentMode based on entity type. + */ +export function getCurrentEntityInfo(entity: FocusEntity) { + switch (entity) { + case FocusEntity.QUERY: + case FocusEntity.API: + case FocusEntity.QUERY_MODULE_INSTANCE: + return { + segment: EditorEntityTab.QUERIES, + segmentMode: EditorEntityTabState.Edit, + }; + case FocusEntity.QUERY_LIST: + return { + segment: EditorEntityTab.QUERIES, + segmentMode: EditorEntityTabState.List, + }; + case FocusEntity.QUERY_ADD: + return { + segment: EditorEntityTab.QUERIES, + segmentMode: EditorEntityTabState.Add, + }; + case FocusEntity.JS_OBJECT: + case FocusEntity.JS_MODULE_INSTANCE: + return { + segment: EditorEntityTab.JS, + segmentMode: EditorEntityTabState.Edit, + }; + case FocusEntity.JS_OBJECT_ADD: + return { + segment: EditorEntityTab.JS, + segmentMode: EditorEntityTabState.Add, + }; + case FocusEntity.JS_OBJECT_LIST: + return { + segment: EditorEntityTab.JS, + segmentMode: EditorEntityTabState.List, + }; + case FocusEntity.CANVAS: + return { + segment: EditorEntityTab.UI, + segmentMode: EditorEntityTabState.Add, + }; + case FocusEntity.PROPERTY_PANE: + return { + segment: EditorEntityTab.UI, + segmentMode: EditorEntityTabState.Edit, + }; + case FocusEntity.WIDGET_LIST: + return { + segment: EditorEntityTab.UI, + segmentMode: EditorEntityTabState.List, + }; + default: + return { + segment: EditorEntityTab.UI, + segmentMode: EditorEntityTabState.Add, + }; + } +} From 4e92f5cede0d33a29555a9ba17264c2f46b500d6 Mon Sep 17 00:00:00 2001 From: Alex Golovanov Date: Thu, 13 Jun 2024 20:30:13 +0300 Subject: [PATCH 08/20] chore: reverted canvas factory changes --- .../src/layoutSystems/CanvasFactory.tsx | 33 ++----------------- .../src/layoutSystems/styles.module.css | 3 -- 2 files changed, 2 insertions(+), 34 deletions(-) delete mode 100644 app/client/src/layoutSystems/styles.module.css diff --git a/app/client/src/layoutSystems/CanvasFactory.tsx b/app/client/src/layoutSystems/CanvasFactory.tsx index fcb7879e3546..83682c80c59d 100644 --- a/app/client/src/layoutSystems/CanvasFactory.tsx +++ b/app/client/src/layoutSystems/CanvasFactory.tsx @@ -1,18 +1,11 @@ import React, { memo, useMemo } from "react"; -import { useDispatch, useSelector } from "react-redux"; - +import { useSelector } from "react-redux"; import { getRenderMode } from "selectors/editorSelectors"; import { getLayoutSystemType } from "selectors/layoutSystemSelectors"; import type { WidgetProps } from "widgets/BaseWidget"; import withWidgetProps from "widgets/withWidgetProps"; import { getLayoutSystem } from "./withLayoutSystemWidgetHOC"; -import { sendAnalyticsForSideBySideHover } from "actions/analyticsActions"; - -import { LAYOUT_WRAPPER_ID } from "./constants"; -import styles from "./styles.module.css"; -import useIsInSideBySideEditor from "utils/hooks/useIsInSideBySideEditor"; - // ToDo(#27615): destructure withWidgetProps to withCanvasProps by picking only necessary props of a canvas. /** @@ -22,8 +15,6 @@ import useIsInSideBySideEditor from "utils/hooks/useIsInSideBySideEditor"; */ const LayoutSystemBasedCanvas = memo((props: WidgetProps) => { - const dispatch = useDispatch(); - const renderMode = useSelector(getRenderMode); const layoutSystemType = useSelector(getLayoutSystemType); const { canvasSystem } = useMemo( @@ -36,27 +27,7 @@ const LayoutSystemBasedCanvas = memo((props: WidgetProps) => { ], ); const { Canvas, propertyEnhancer } = canvasSystem; - - const isInSideBySideEditor = useIsInSideBySideEditor(); - const handleMouseLeave: React.MouseEventHandler = (e) => { - if ( - isInSideBySideEditor && - e.relatedTarget instanceof Element && - e.relatedTarget.contains(document.getElementById(LAYOUT_WRAPPER_ID)) - ) { - dispatch(sendAnalyticsForSideBySideHover()); - } - }; - - return ( -
- -
- ); + return ; }); const HydratedLayoutSystemBasedCanvas = withWidgetProps( diff --git a/app/client/src/layoutSystems/styles.module.css b/app/client/src/layoutSystems/styles.module.css deleted file mode 100644 index 94ce0b845501..000000000000 --- a/app/client/src/layoutSystems/styles.module.css +++ /dev/null @@ -1,3 +0,0 @@ -.root { - display: contents; -} From adf5f268ba582cf927b60536f9484cf251b8a1c5 Mon Sep 17 00:00:00 2001 From: Alex Golovanov Date: Thu, 13 Jun 2024 22:16:24 +0300 Subject: [PATCH 09/20] perf: add canvas click navigation method --- .../editor/AnvilWidgetName/AnvilWidgetNameComponent.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/client/src/layoutSystems/anvil/editor/AnvilWidgetName/AnvilWidgetNameComponent.tsx b/app/client/src/layoutSystems/anvil/editor/AnvilWidgetName/AnvilWidgetNameComponent.tsx index db005c914fba..c33e3ea1eeb5 100644 --- a/app/client/src/layoutSystems/anvil/editor/AnvilWidgetName/AnvilWidgetNameComponent.tsx +++ b/app/client/src/layoutSystems/anvil/editor/AnvilWidgetName/AnvilWidgetNameComponent.tsx @@ -10,6 +10,7 @@ import { import { createMessage } from "@appsmith/constants/messages"; import { debugWidget } from "layoutSystems/anvil/integrations/actions"; import { useDispatch } from "react-redux"; +import { NavigationMethod } from "utils/history"; /** * Floating UI doesn't seem to respect initial styles from styled components or modules @@ -63,7 +64,11 @@ export function _AnvilWidgetNameComponent( }, [parentId]); const handleSelectWidget = useCallback(() => { - selectWidget(SelectionRequestType.One, [props.widgetId]); + selectWidget( + SelectionRequestType.One, + [props.widgetId], + NavigationMethod.CanvasClick, + ); }, [props.widgetId]); const handleDebugClick = useCallback(() => { From 94d02811cd314a84393e9d351aa036683e1e2101 Mon Sep 17 00:00:00 2001 From: Alex Golovanov Date: Thu, 13 Jun 2024 22:18:49 +0300 Subject: [PATCH 10/20] perf: move analytics wrapper to a component and attach to editor canvas --- .../anvil/editor/canvas/AnvilEditorCanvas.tsx | 5 ++- .../AnalyticsWrapper/AnalyticsWrapper.tsx | 33 +++++++++++++++++++ .../common/AnalyticsWrapper/constants.ts | 3 ++ .../common/AnalyticsWrapper/index.ts | 1 + .../canvas/FixedLayoutEditorCanvas.tsx | 3 +- 5 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 app/client/src/layoutSystems/common/AnalyticsWrapper/AnalyticsWrapper.tsx create mode 100644 app/client/src/layoutSystems/common/AnalyticsWrapper/constants.ts create mode 100644 app/client/src/layoutSystems/common/AnalyticsWrapper/index.ts diff --git a/app/client/src/layoutSystems/anvil/editor/canvas/AnvilEditorCanvas.tsx b/app/client/src/layoutSystems/anvil/editor/canvas/AnvilEditorCanvas.tsx index 67319c5c0976..a8c6f026cb05 100644 --- a/app/client/src/layoutSystems/anvil/editor/canvas/AnvilEditorCanvas.tsx +++ b/app/client/src/layoutSystems/anvil/editor/canvas/AnvilEditorCanvas.tsx @@ -7,6 +7,7 @@ import type { AnvilGlobalDnDStates } from "./hooks/useAnvilGlobalDnDStates"; import { useAnvilGlobalDnDStates } from "./hooks/useAnvilGlobalDnDStates"; import { AnvilDragPreview } from "../canvasArenas/AnvilDragPreview"; import { AnvilWidgetElevationProvider } from "./providers/AnvilWidgetElevationProvider"; +import { AnalyticsWrapper } from "../../../common/AnalyticsWrapper"; export const AnvilDnDStatesContext = React.createContext< AnvilGlobalDnDStates | undefined @@ -58,7 +59,9 @@ export const AnvilEditorCanvas = (props: BaseWidgetProps) => { return ( - + + + { + const dispatch = useDispatch(); + const isInSideBySideEditor = useIsInSideBySideEditor(); + const layoutSystemType = useSelector(getLayoutSystemType); + + const handleMouseLeave: React.MouseEventHandler = (e) => { + if ( + isInSideBySideEditor && + ((e.relatedTarget instanceof Element && + e.relatedTarget.contains(document.getElementById(LAYOUT_WRAPPER_ID))) || + layoutSystemType === LayoutSystemTypes.ANVIL) + ) { + dispatch(sendAnalyticsForSideBySideHover()); + } + }; + return ( +
+ {children} +
+ ); +}; diff --git a/app/client/src/layoutSystems/common/AnalyticsWrapper/constants.ts b/app/client/src/layoutSystems/common/AnalyticsWrapper/constants.ts new file mode 100644 index 000000000000..cf13dc70f369 --- /dev/null +++ b/app/client/src/layoutSystems/common/AnalyticsWrapper/constants.ts @@ -0,0 +1,3 @@ +import { v4 as uuid } from "uuid"; + +export const LAYOUT_WRAPPER_ID = uuid(); diff --git a/app/client/src/layoutSystems/common/AnalyticsWrapper/index.ts b/app/client/src/layoutSystems/common/AnalyticsWrapper/index.ts new file mode 100644 index 000000000000..ce37b5f888ca --- /dev/null +++ b/app/client/src/layoutSystems/common/AnalyticsWrapper/index.ts @@ -0,0 +1 @@ +export { AnalyticsWrapper } from "./AnalyticsWrapper"; diff --git a/app/client/src/layoutSystems/fixedlayout/canvas/FixedLayoutEditorCanvas.tsx b/app/client/src/layoutSystems/fixedlayout/canvas/FixedLayoutEditorCanvas.tsx index 025531c9f8b3..b52dfbb1f2be 100644 --- a/app/client/src/layoutSystems/fixedlayout/canvas/FixedLayoutEditorCanvas.tsx +++ b/app/client/src/layoutSystems/fixedlayout/canvas/FixedLayoutEditorCanvas.tsx @@ -14,6 +14,7 @@ import { FixedCanvasDraggingArena } from "../editor/FixedLayoutCanvasArenas/Fixe import { compact, sortBy } from "lodash"; import { Positioning } from "layoutSystems/common/utils/constants"; import type { DSLWidget } from "WidgetProvider/constants"; +import { AnalyticsWrapper } from "../../common/AnalyticsWrapper"; export type CanvasProps = DSLWidget; /** @@ -112,7 +113,7 @@ export const FixedLayoutEditorCanvas = (props: BaseWidgetProps) => { widgetId={props.widgetId} widgetType={props.type} /> - {canvasChildren} + {canvasChildren} ); From 934df57cb6e3b1720bd70e1614ed592fdfedb5f9 Mon Sep 17 00:00:00 2001 From: Alex Golovanov Date: Thu, 13 Jun 2024 22:20:40 +0300 Subject: [PATCH 11/20] perf: add util for in side-by-side --- app/client/src/pages/Editor/utils.tsx | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/app/client/src/pages/Editor/utils.tsx b/app/client/src/pages/Editor/utils.tsx index 59c2f6ed077d..e47970482238 100644 --- a/app/client/src/pages/Editor/utils.tsx +++ b/app/client/src/pages/Editor/utils.tsx @@ -36,6 +36,8 @@ import { Icon } from "design-system"; import { EditorEntityTab, EditorEntityTabState, + EditorState, + EditorViewMode, } from "@appsmith/entities/IDE/constants"; import { FocusEntity } from "navigation/FocusEntity"; @@ -481,3 +483,22 @@ export function getCurrentEntityInfo(entity: FocusEntity) { }; } } + +/** + * Check if use is currently working is side-by-side editor mode. + */ +export function isInSideBySideEditor({ + appState, + segment, + viewMode, +}: { + viewMode: EditorViewMode; + appState: EditorState; + segment: EditorEntityTab; +}) { + return ( + viewMode === EditorViewMode.SplitScreen && + appState === EditorState.EDITOR && + segment !== EditorEntityTab.UI + ); +} From 8d7824d610f3c0eef42910a5077a6d310e5c2fba Mon Sep 17 00:00:00 2001 From: Alex Golovanov Date: Thu, 13 Jun 2024 22:21:42 +0300 Subject: [PATCH 12/20] perf: add saga for widget hover --- app/client/src/sagas/AnalyticsSaga.ts | 40 ++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/app/client/src/sagas/AnalyticsSaga.ts b/app/client/src/sagas/AnalyticsSaga.ts index 266639437f62..99dbd752d8e3 100644 --- a/app/client/src/sagas/AnalyticsSaga.ts +++ b/app/client/src/sagas/AnalyticsSaga.ts @@ -17,6 +17,7 @@ import { getIdeCanvasSideBySideHoverState } from "selectors/analyticsSelectors"; import { EditorViewMode } from "@appsmith/entities/IDE/constants"; import { recordAnalyticsForSideBySideNavigation, + recordAnalyticsForSideBySideWidgetHover, resetAnalyticsForSideBySideHover, } from "actions/analyticsActions"; @@ -28,6 +29,10 @@ import { JS_COLLECTION_EDITOR_PATH, WIDGETS_EDITOR_BASE_PATH, } from "constants/routes"; +import type { focusWidget } from "actions/widgetActions"; +import { getEntities } from "@appsmith/selectors/entitiesSelector"; +import { identifyEntityFromPath } from "navigation/FocusEntity"; +import { getCurrentEntityInfo, isInSideBySideEditor } from "pages/Editor/utils"; export function* sendAnalyticsEventSaga( type: ReduxActionType, @@ -94,13 +99,12 @@ function* routeChangeInSideBySideModeSaga({ yield select(getIDEViewMode); const { - location: { - pathname: pathName, - state: { invokedBy }, - }, + location: { pathname: pathName, state }, prevLocation: { pathname: prevPathName }, } = payload; + const invokedBy = state?.invokedBy; + if ( invokedBy === NavigationMethod.CanvasClick && viewMode === EditorViewMode.SplitScreen && @@ -112,6 +116,33 @@ function* routeChangeInSideBySideModeSaga({ } } +function* focusWidgetInSideBySideModeSaga({ + payload, +}: ReturnType) { + const { widgetId } = payload; + + if (widgetId) { + const viewMode: ReturnType = + yield select(getIDEViewMode); + + const { appState, entity } = identifyEntityFromPath( + window.location.pathname, + ); + + const { segment } = getCurrentEntityInfo(entity); + + if (isInSideBySideEditor({ appState, segment, viewMode })) { + const entities: ReturnType = + yield select(getEntities); + const widget = entities.canvasWidgets[widgetId]; + + if (widget) { + yield put(recordAnalyticsForSideBySideWidgetHover(widget.type)); + } + } + } +} + export default function* root() { yield all([ takeEvery( @@ -120,5 +151,6 @@ export default function* root() { ), takeEvery(ReduxActionTypes.ROUTE_CHANGED, routeChangeInSideBySideModeSaga), + takeEvery(ReduxActionTypes.FOCUS_WIDGET, focusWidgetInSideBySideModeSaga), ]); } From cdc6bceb178fa3cd820c680cef150eed7755fb59 Mon Sep 17 00:00:00 2001 From: Alex Golovanov Date: Thu, 13 Jun 2024 22:24:29 +0300 Subject: [PATCH 13/20] fix: add wrapper element check --- .../common/AnalyticsWrapper/AnalyticsWrapper.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/client/src/layoutSystems/common/AnalyticsWrapper/AnalyticsWrapper.tsx b/app/client/src/layoutSystems/common/AnalyticsWrapper/AnalyticsWrapper.tsx index a627bc79ba4c..b01ac4829f97 100644 --- a/app/client/src/layoutSystems/common/AnalyticsWrapper/AnalyticsWrapper.tsx +++ b/app/client/src/layoutSystems/common/AnalyticsWrapper/AnalyticsWrapper.tsx @@ -12,11 +12,14 @@ export const AnalyticsWrapper: React.FC = ({ children }) => { const layoutSystemType = useSelector(getLayoutSystemType); const handleMouseLeave: React.MouseEventHandler = (e) => { + const wrapperElement = document.getElementById(LAYOUT_WRAPPER_ID); + if ( isInSideBySideEditor && - ((e.relatedTarget instanceof Element && - e.relatedTarget.contains(document.getElementById(LAYOUT_WRAPPER_ID))) || - layoutSystemType === LayoutSystemTypes.ANVIL) + (layoutSystemType === LayoutSystemTypes.ANVIL || + (wrapperElement && + e.relatedTarget instanceof Element && + e.relatedTarget.contains(wrapperElement))) ) { dispatch(sendAnalyticsForSideBySideHover()); } From 872730574df657ce83c4b517c3cc872adb0c42f2 Mon Sep 17 00:00:00 2001 From: Alex Golovanov Date: Fri, 14 Jun 2024 14:28:16 +0300 Subject: [PATCH 14/20] perf: add segment dependency & move logic to utility --- .../utils/hooks/useIsInSideBySideEditor.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/app/client/src/utils/hooks/useIsInSideBySideEditor.ts b/app/client/src/utils/hooks/useIsInSideBySideEditor.ts index ed0e65e22c41..9bacd1e1951c 100644 --- a/app/client/src/utils/hooks/useIsInSideBySideEditor.ts +++ b/app/client/src/utils/hooks/useIsInSideBySideEditor.ts @@ -2,21 +2,22 @@ import { useSelector } from "react-redux"; import { useLocation } from "react-router"; import { getIDEViewMode } from "selectors/ideSelectors"; -import { EditorViewMode } from "@appsmith/entities/IDE/constants"; -import { JS_COLLECTION_EDITOR_PATH } from "constants/routes"; +import { identifyEntityFromPath } from "../../navigation/FocusEntity"; +import { + getCurrentEntityInfo, + isInSideBySideEditor, +} from "../../pages/Editor/utils"; /** - * Hook to check if current component is in side-by-side editor mode. + * Checks if current component is in side-by-side editor mode. */ const useIsInSideBySideEditor = () => { const { pathname } = useLocation(); - const ideViewMode = useSelector(getIDEViewMode); + const viewMode = useSelector(getIDEViewMode); + const { appState, entity } = identifyEntityFromPath(pathname); + const { segment } = getCurrentEntityInfo(entity); - const isInSideBySideEditor = - ideViewMode === EditorViewMode.SplitScreen && - pathname.includes(JS_COLLECTION_EDITOR_PATH); - - return isInSideBySideEditor; + return isInSideBySideEditor({ appState, segment, viewMode }); }; export default useIsInSideBySideEditor; From e2eca7d8e1ccfb093cdba448706108fddba056e1 Mon Sep 17 00:00:00 2001 From: Alex Golovanov Date: Fri, 14 Jun 2024 17:30:30 +0300 Subject: [PATCH 15/20] chore: move hook to ide folder --- app/client/src/IDE/hooks/index.ts | 1 + .../src/{utils => IDE}/hooks/useIsInSideBySideEditor.ts | 6 ++---- .../common/AnalyticsWrapper/AnalyticsWrapper.tsx | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) create mode 100644 app/client/src/IDE/hooks/index.ts rename app/client/src/{utils => IDE}/hooks/useIsInSideBySideEditor.ts (81%) diff --git a/app/client/src/IDE/hooks/index.ts b/app/client/src/IDE/hooks/index.ts new file mode 100644 index 000000000000..50897137a667 --- /dev/null +++ b/app/client/src/IDE/hooks/index.ts @@ -0,0 +1 @@ +export { useIsInSideBySideEditor } from "./useIsInSideBySideEditor"; diff --git a/app/client/src/utils/hooks/useIsInSideBySideEditor.ts b/app/client/src/IDE/hooks/useIsInSideBySideEditor.ts similarity index 81% rename from app/client/src/utils/hooks/useIsInSideBySideEditor.ts rename to app/client/src/IDE/hooks/useIsInSideBySideEditor.ts index 9bacd1e1951c..8ceb91ba95a0 100644 --- a/app/client/src/utils/hooks/useIsInSideBySideEditor.ts +++ b/app/client/src/IDE/hooks/useIsInSideBySideEditor.ts @@ -1,7 +1,7 @@ import { useSelector } from "react-redux"; import { useLocation } from "react-router"; -import { getIDEViewMode } from "selectors/ideSelectors"; +import { getIDEViewMode } from "../../selectors/ideSelectors"; import { identifyEntityFromPath } from "../../navigation/FocusEntity"; import { getCurrentEntityInfo, @@ -11,7 +11,7 @@ import { /** * Checks if current component is in side-by-side editor mode. */ -const useIsInSideBySideEditor = () => { +export const useIsInSideBySideEditor = () => { const { pathname } = useLocation(); const viewMode = useSelector(getIDEViewMode); const { appState, entity } = identifyEntityFromPath(pathname); @@ -19,5 +19,3 @@ const useIsInSideBySideEditor = () => { return isInSideBySideEditor({ appState, segment, viewMode }); }; - -export default useIsInSideBySideEditor; diff --git a/app/client/src/layoutSystems/common/AnalyticsWrapper/AnalyticsWrapper.tsx b/app/client/src/layoutSystems/common/AnalyticsWrapper/AnalyticsWrapper.tsx index b01ac4829f97..272f90f5e5b6 100644 --- a/app/client/src/layoutSystems/common/AnalyticsWrapper/AnalyticsWrapper.tsx +++ b/app/client/src/layoutSystems/common/AnalyticsWrapper/AnalyticsWrapper.tsx @@ -1,10 +1,10 @@ import React from "react"; import { useDispatch, useSelector } from "react-redux"; -import useIsInSideBySideEditor from "utils/hooks/useIsInSideBySideEditor"; import { LAYOUT_WRAPPER_ID } from "./constants"; import { sendAnalyticsForSideBySideHover } from "actions/analyticsActions"; import { getLayoutSystemType } from "selectors/layoutSystemSelectors"; import { LayoutSystemTypes } from "layoutSystems/types"; +import { useIsInSideBySideEditor } from "IDE/hooks"; export const AnalyticsWrapper: React.FC = ({ children }) => { const dispatch = useDispatch(); From fd98592e6b6dda6a486ff5cbcb1e12bc74dd8820 Mon Sep 17 00:00:00 2001 From: Alex Golovanov Date: Fri, 14 Jun 2024 17:31:11 +0300 Subject: [PATCH 16/20] test: add side-by-side hook tests --- .../hooks/useIsInSideBySideEditor.test.tsx | 147 ++++++++++++++++++ .../test/factories/AppIDEFactoryUtils.ts | 10 +- 2 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 app/client/src/IDE/hooks/useIsInSideBySideEditor.test.tsx diff --git a/app/client/src/IDE/hooks/useIsInSideBySideEditor.test.tsx b/app/client/src/IDE/hooks/useIsInSideBySideEditor.test.tsx new file mode 100644 index 000000000000..04c50c5cf4b6 --- /dev/null +++ b/app/client/src/IDE/hooks/useIsInSideBySideEditor.test.tsx @@ -0,0 +1,147 @@ +import React from "react"; +import { renderHook, act } from "@testing-library/react-hooks/dom"; +import { Provider } from "react-redux"; +import { EditorViewMode } from "@appsmith/entities/IDE/constants"; +import { useIsInSideBySideEditor } from "./useIsInSideBySideEditor"; +import { getIDETestState } from "test/factories/AppIDEFactoryUtils"; +import type { AppState } from "@appsmith/reducers"; + +import { createMemoryHistory, type MemoryHistory } from "history"; +import { Router } from "react-router"; + +import { getIDEViewMode } from "../../selectors/ideSelectors"; +import { setIdeEditorViewMode } from "../../actions/ideActions"; +import { testStore } from "../../store"; +import type { Store } from "redux"; + +const JS_COLLECTION_EDITOR_PATH = + "/app/app-name/page-665dd1103e4483728c9ed11a/edit/jsObjects"; +const NON_JS_COLLECTION_EDITOR_PATH = "/some-other-path"; +const FEATURE_FLAGS = { + rollout_side_by_side_enabled: true, +}; + +const renderUseIsInSideBySideEditor = ( + history: MemoryHistory, + store: Store, +) => { + const wrapper: React.FC = ({ children }) => ( + + {children} + + ); + return renderHook(() => useIsInSideBySideEditor(), { + wrapper, + }); +}; + +describe("useIsInSideBySideEditor", () => { + it("should enter into split screen mode", () => { + const store = testStore( + getIDETestState({ + ideView: EditorViewMode.SplitScreen, + featureFlags: FEATURE_FLAGS, + }), + ); + + const ideViewMode = getIDEViewMode(store.getState()); + expect(ideViewMode).toBe(EditorViewMode.SplitScreen); + }); + + it("should return false when on correct path but not in SplitScreen mode", () => { + const store = testStore( + getIDETestState({ + ideView: EditorViewMode.FullScreen, + featureFlags: FEATURE_FLAGS, + }), + ); + + const history = createMemoryHistory({ + initialEntries: [JS_COLLECTION_EDITOR_PATH], + }); + + const { result } = renderUseIsInSideBySideEditor(history, store); + expect(result.current).toBe(false); + }); + + it("should return false when pathname does not satisfy JS_COLLECTION_EDITOR_PATH", () => { + const store = testStore( + getIDETestState({ + ideView: EditorViewMode.SplitScreen, + featureFlags: FEATURE_FLAGS, + }), + ); + + const history = createMemoryHistory({ + initialEntries: [NON_JS_COLLECTION_EDITOR_PATH], + }); + + const { result } = renderUseIsInSideBySideEditor(history, store); + + expect(result.current).toBe(false); + }); + + it("should return true when in SplitScreen mode and pathname satisfies JS_COLLECTION_EDITOR_PATH", () => { + const store = testStore( + getIDETestState({ + ideView: EditorViewMode.SplitScreen, + featureFlags: FEATURE_FLAGS, + }), + ); + + const history = createMemoryHistory({ + initialEntries: [JS_COLLECTION_EDITOR_PATH], + }); + + const { result } = renderUseIsInSideBySideEditor(history, store); + expect(result.current).toBe(true); + }); + + it("should update when ideViewMode changes", () => { + const store = testStore( + getIDETestState({ + ideView: EditorViewMode.SplitScreen, + featureFlags: FEATURE_FLAGS, + }), + ); + + const history = createMemoryHistory({ + initialEntries: [JS_COLLECTION_EDITOR_PATH], + }); + + const { rerender, result } = renderUseIsInSideBySideEditor(history, store); + + expect(result.current).toBe(true); + + act(() => { + store.dispatch(setIdeEditorViewMode(EditorViewMode.FullScreen)); + rerender(); + }); + + expect(getIDEViewMode(store.getState())).toBe(EditorViewMode.FullScreen); + expect(result.current).toBe(false); + }); + + it("should update when pathname changes", () => { + const store = testStore( + getIDETestState({ + ideView: EditorViewMode.SplitScreen, + featureFlags: FEATURE_FLAGS, + }), + ); + + const history = createMemoryHistory({ + initialEntries: [NON_JS_COLLECTION_EDITOR_PATH], + }); + + const { rerender, result } = renderUseIsInSideBySideEditor(history, store); + expect(result.current).toBe(false); + + act(() => { + history.push(JS_COLLECTION_EDITOR_PATH); + rerender(); + }); + + expect(result.current).toBe(true); + }); +}); diff --git a/app/client/test/factories/AppIDEFactoryUtils.ts b/app/client/test/factories/AppIDEFactoryUtils.ts index 28b65206af47..60c925159ee7 100644 --- a/app/client/test/factories/AppIDEFactoryUtils.ts +++ b/app/client/test/factories/AppIDEFactoryUtils.ts @@ -12,6 +12,7 @@ import { IDETabsDefaultValue } from "reducers/uiReducers/ideReducer"; import type { JSCollection } from "entities/JSCollection"; import type { FocusHistory } from "reducers/uiReducers/focusHistoryReducer"; import type { Datasource } from "entities/Datasource"; +import type { FeatureFlags } from "@appsmith/entities/FeatureFlag"; interface IDEStateArgs { ideView?: EditorViewMode; @@ -22,12 +23,14 @@ interface IDEStateArgs { branch?: string; focusHistory?: FocusHistory; datasources?: Datasource[]; + featureFlags?: Partial; } export const getIDETestState = ({ actions = [], branch, datasources = [], + featureFlags, focusHistory = {}, ideView = EditorViewMode.FullScreen, js = [], @@ -44,14 +47,15 @@ export const getIDETestState = ({ defaultPageId: pages[0]?.pageId, loading: {}, }; + let ideTabs: ParentEntityIDETabs = {}; if (pageList.currentPageId) { ideTabs = { [pageList.currentPageId]: tabs }; } const actionData = actions.map((a) => ({ isLoading: false, config: a })); - const jsData = js.map((a) => ({ isLoading: false, config: a })); + const featureFlag = featureFlags ? { data: featureFlags } : {}; return { ...initialState, @@ -68,6 +72,10 @@ export const getIDETestState = ({ }, ui: { ...initialState.ui, + users: { + ...initialState.ui.users, + featureFlag, + }, ide: { ...initialState.ui.ide, view: ideView, From 49382dac175d9ce763ea805c7de5c05da1bffdf8 Mon Sep 17 00:00:00 2001 From: Alex Golovanov Date: Mon, 17 Jun 2024 21:57:59 +0300 Subject: [PATCH 17/20] chore: move analytics redux branch to ide --- app/client/src/actions/analyticsActions.ts | 19 --------- app/client/src/actions/ideActions.ts | 19 +++++++++ .../AnalyticsWrapper/AnalyticsWrapper.tsx | 12 ++++-- .../reducers/uiReducers/analyticsReducer.ts | 40 ------------------- .../src/reducers/uiReducers/ideReducer.ts | 28 +++++++++++++ app/client/src/sagas/AnalyticsSaga.ts | 4 +- .../src/selectors/analyticsSelectors.tsx | 3 -- app/client/src/selectors/ideSelectors.tsx | 3 ++ 8 files changed, 60 insertions(+), 68 deletions(-) diff --git a/app/client/src/actions/analyticsActions.ts b/app/client/src/actions/analyticsActions.ts index f20a3c71e33f..3ceb42984b7b 100644 --- a/app/client/src/actions/analyticsActions.ts +++ b/app/client/src/actions/analyticsActions.ts @@ -7,22 +7,3 @@ export const segmentInitSuccess = () => ({ export const segmentInitUncertain = () => ({ type: ReduxActionTypes.SEGMENT_INIT_UNCERTAIN, }); - -export const recordAnalyticsForSideBySideWidgetHover = ( - widgetType: string, -) => ({ - type: ReduxActionTypes.RECORD_ANALYTICS_FOR_SIDE_BY_SIDE_WIDGET_HOVER, - payload: widgetType, -}); - -export const sendAnalyticsForSideBySideHover = () => ({ - type: ReduxActionTypes.SEND_ANALYTICS_FOR_SIDE_BY_SIDE_HOVER, -}); - -export const recordAnalyticsForSideBySideNavigation = () => ({ - type: ReduxActionTypes.RECORD_ANALYTICS_FOR_SIDE_BY_SIDE_NAVIGATION, -}); - -export const resetAnalyticsForSideBySideHover = () => ({ - type: ReduxActionTypes.RESET_ANALYTICS_FOR_SIDE_BY_SIDE_HOVER, -}); diff --git a/app/client/src/actions/ideActions.ts b/app/client/src/actions/ideActions.ts index ac44769645d2..000da12c6b37 100644 --- a/app/client/src/actions/ideActions.ts +++ b/app/client/src/actions/ideActions.ts @@ -35,3 +35,22 @@ export const setShowQueryCreateNewModal = (payload: boolean) => { payload, }; }; + +export const recordAnalyticsForSideBySideWidgetHover = ( + widgetType: string, +) => ({ + type: ReduxActionTypes.RECORD_ANALYTICS_FOR_SIDE_BY_SIDE_WIDGET_HOVER, + payload: widgetType, +}); + +export const sendAnalyticsForSideBySideHover = () => ({ + type: ReduxActionTypes.SEND_ANALYTICS_FOR_SIDE_BY_SIDE_HOVER, +}); + +export const recordAnalyticsForSideBySideNavigation = () => ({ + type: ReduxActionTypes.RECORD_ANALYTICS_FOR_SIDE_BY_SIDE_NAVIGATION, +}); + +export const resetAnalyticsForSideBySideHover = () => ({ + type: ReduxActionTypes.RESET_ANALYTICS_FOR_SIDE_BY_SIDE_HOVER, +}); diff --git a/app/client/src/layoutSystems/common/AnalyticsWrapper/AnalyticsWrapper.tsx b/app/client/src/layoutSystems/common/AnalyticsWrapper/AnalyticsWrapper.tsx index 272f90f5e5b6..12b22ca06bb7 100644 --- a/app/client/src/layoutSystems/common/AnalyticsWrapper/AnalyticsWrapper.tsx +++ b/app/client/src/layoutSystems/common/AnalyticsWrapper/AnalyticsWrapper.tsx @@ -1,7 +1,7 @@ import React from "react"; import { useDispatch, useSelector } from "react-redux"; import { LAYOUT_WRAPPER_ID } from "./constants"; -import { sendAnalyticsForSideBySideHover } from "actions/analyticsActions"; +import { sendAnalyticsForSideBySideHover } from "actions/ideActions"; import { getLayoutSystemType } from "selectors/layoutSystemSelectors"; import { LayoutSystemTypes } from "layoutSystems/types"; import { useIsInSideBySideEditor } from "IDE/hooks"; @@ -10,23 +10,27 @@ export const AnalyticsWrapper: React.FC = ({ children }) => { const dispatch = useDispatch(); const isInSideBySideEditor = useIsInSideBySideEditor(); const layoutSystemType = useSelector(getLayoutSystemType); + const isAnvil = layoutSystemType === LayoutSystemTypes.ANVIL; + const className = isAnvil ? "contents" : "h-full"; const handleMouseLeave: React.MouseEventHandler = (e) => { const wrapperElement = document.getElementById(LAYOUT_WRAPPER_ID); if ( isInSideBySideEditor && - (layoutSystemType === LayoutSystemTypes.ANVIL || + (isAnvil || (wrapperElement && + wrapperElement !== e.relatedTarget && e.relatedTarget instanceof Element && - e.relatedTarget.contains(wrapperElement))) + e.relatedTarget.contains(wrapperElement) && + e.target instanceof Element)) ) { dispatch(sendAnalyticsForSideBySideHover()); } }; return (
diff --git a/app/client/src/reducers/uiReducers/analyticsReducer.ts b/app/client/src/reducers/uiReducers/analyticsReducer.ts index 468e308fdde8..354abe161db0 100644 --- a/app/client/src/reducers/uiReducers/analyticsReducer.ts +++ b/app/client/src/reducers/uiReducers/analyticsReducer.ts @@ -1,4 +1,3 @@ -import type { ReduxAction } from "@appsmith/constants/ReduxActionConstants"; import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants"; import { createReducer } from "utils/ReducerUtils"; @@ -6,54 +5,15 @@ export type SegmentState = "INIT_SUCCESS" | "INIT_UNCERTAIN"; export const initialState: AnalyticsReduxState = { telemetry: {}, - ideCanvasSideBySideHover: { - navigated: false, - widgetTypes: [], - }, }; export interface AnalyticsReduxState { telemetry: { segmentState?: SegmentState; }; - - ideCanvasSideBySideHover: { - navigated: boolean; - widgetTypes: string[]; - }; } export const handlers = { - [ReduxActionTypes.RESET_ANALYTICS_FOR_SIDE_BY_SIDE_HOVER]: ( - state: AnalyticsReduxState, - ): AnalyticsReduxState => ({ - ...state, - ideCanvasSideBySideHover: { - ...initialState.ideCanvasSideBySideHover, - }, - }), - [ReduxActionTypes.RECORD_ANALYTICS_FOR_SIDE_BY_SIDE_NAVIGATION]: ( - state: AnalyticsReduxState, - ): AnalyticsReduxState => ({ - ...state, - ideCanvasSideBySideHover: { - ...state.ideCanvasSideBySideHover, - navigated: true, - }, - }), - [ReduxActionTypes.RECORD_ANALYTICS_FOR_SIDE_BY_SIDE_WIDGET_HOVER]: ( - state: AnalyticsReduxState, - action: ReduxAction, - ): AnalyticsReduxState => ({ - ...state, - ideCanvasSideBySideHover: { - ...state.ideCanvasSideBySideHover, - widgetTypes: [ - ...state.ideCanvasSideBySideHover.widgetTypes, - action.payload, - ], - }, - }), [ReduxActionTypes.SEGMENT_INITIALIZED]: ( state: AnalyticsReduxState, ): AnalyticsReduxState => ({ diff --git a/app/client/src/reducers/uiReducers/ideReducer.ts b/app/client/src/reducers/uiReducers/ideReducer.ts index c155157b6ed4..8226ad605649 100644 --- a/app/client/src/reducers/uiReducers/ideReducer.ts +++ b/app/client/src/reducers/uiReducers/ideReducer.ts @@ -17,6 +17,10 @@ const initialState: IDEState = { view: EditorViewMode.FullScreen, tabs: {}, showCreateModal: false, + ideCanvasSideBySideHover: { + navigated: false, + widgetTypes: [], + }, }; const ideReducer = createImmerReducer(initialState, { @@ -80,12 +84,31 @@ const ideReducer = createImmerReducer(initialState, { ); remove(tabs, (tab) => tab === action.payload.id); }, + [ReduxActionTypes.RESET_ANALYTICS_FOR_SIDE_BY_SIDE_HOVER]: ( + state: IDEState, + ) => { + state.ideCanvasSideBySideHover = klona( + initialState.ideCanvasSideBySideHover, + ); + }, + [ReduxActionTypes.RECORD_ANALYTICS_FOR_SIDE_BY_SIDE_NAVIGATION]: ( + state: IDEState, + ) => { + state.ideCanvasSideBySideHover.navigated = true; + }, + [ReduxActionTypes.RECORD_ANALYTICS_FOR_SIDE_BY_SIDE_WIDGET_HOVER]: ( + state: IDEState, + action: ReduxAction, + ) => { + state.ideCanvasSideBySideHover.widgetTypes.push(action.payload); + }, }); export interface IDEState { view: EditorViewMode; tabs: ParentEntityIDETabs; showCreateModal: boolean; + ideCanvasSideBySideHover: IDECanvasSideBySideHover; } export interface ParentEntityIDETabs { @@ -97,4 +120,9 @@ export interface IDETabs { [EditorEntityTab.QUERIES]: string[]; } +export interface IDECanvasSideBySideHover { + navigated: boolean; + widgetTypes: string[]; +} + export default ideReducer; diff --git a/app/client/src/sagas/AnalyticsSaga.ts b/app/client/src/sagas/AnalyticsSaga.ts index 99dbd752d8e3..9c686916c153 100644 --- a/app/client/src/sagas/AnalyticsSaga.ts +++ b/app/client/src/sagas/AnalyticsSaga.ts @@ -12,14 +12,14 @@ import { import get from "lodash/get"; import log from "loglevel"; import { all, put, select, takeEvery } from "redux-saga/effects"; -import { getIdeCanvasSideBySideHoverState } from "selectors/analyticsSelectors"; +import { getIdeCanvasSideBySideHoverState } from "selectors/ideSelectors"; import { EditorViewMode } from "@appsmith/entities/IDE/constants"; import { recordAnalyticsForSideBySideNavigation, recordAnalyticsForSideBySideWidgetHover, resetAnalyticsForSideBySideHover, -} from "actions/analyticsActions"; +} from "actions/ideActions"; import type { routeChanged } from "actions/focusHistoryActions"; import { NavigationMethod } from "utils/history"; diff --git a/app/client/src/selectors/analyticsSelectors.tsx b/app/client/src/selectors/analyticsSelectors.tsx index b8228ca20b6b..b2f84abb6ef1 100644 --- a/app/client/src/selectors/analyticsSelectors.tsx +++ b/app/client/src/selectors/analyticsSelectors.tsx @@ -2,6 +2,3 @@ import type { AppState } from "@appsmith/reducers"; export const getSegmentState = (state: AppState) => state.ui.analytics.telemetry.segmentState; - -export const getIdeCanvasSideBySideHoverState = (state: AppState) => - state.ui.analytics.ideCanvasSideBySideHover; diff --git a/app/client/src/selectors/ideSelectors.tsx b/app/client/src/selectors/ideSelectors.tsx index 697e833f79f1..6bb835c1322d 100644 --- a/app/client/src/selectors/ideSelectors.tsx +++ b/app/client/src/selectors/ideSelectors.tsx @@ -60,3 +60,6 @@ export const getQueryTabs = createSelector( export const getShowCreateNewModal = (state: AppState) => state.ui.ide.showCreateModal; + +export const getIdeCanvasSideBySideHoverState = (state: AppState) => + state.ui.ide.ideCanvasSideBySideHover; From 8a693f890f73d96330f79e1d72c2ff2817ac6999 Mon Sep 17 00:00:00 2001 From: Alex Golovanov Date: Mon, 17 Jun 2024 22:02:35 +0300 Subject: [PATCH 18/20] perf: use canvas widgets selector --- app/client/src/sagas/AnalyticsSaga.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/client/src/sagas/AnalyticsSaga.ts b/app/client/src/sagas/AnalyticsSaga.ts index 9c686916c153..96a77e978cf4 100644 --- a/app/client/src/sagas/AnalyticsSaga.ts +++ b/app/client/src/sagas/AnalyticsSaga.ts @@ -30,7 +30,7 @@ import { WIDGETS_EDITOR_BASE_PATH, } from "constants/routes"; import type { focusWidget } from "actions/widgetActions"; -import { getEntities } from "@appsmith/selectors/entitiesSelector"; +import { getCanvasWidgets } from "@appsmith/selectors/entitiesSelector"; import { identifyEntityFromPath } from "navigation/FocusEntity"; import { getCurrentEntityInfo, isInSideBySideEditor } from "pages/Editor/utils"; @@ -132,9 +132,9 @@ function* focusWidgetInSideBySideModeSaga({ const { segment } = getCurrentEntityInfo(entity); if (isInSideBySideEditor({ appState, segment, viewMode })) { - const entities: ReturnType = - yield select(getEntities); - const widget = entities.canvasWidgets[widgetId]; + const widgets: ReturnType = + yield select(getCanvasWidgets); + const widget = widgets[widgetId]; if (widget) { yield put(recordAnalyticsForSideBySideWidgetHover(widget.type)); From e6ff7e10915cea168abadeecbdb928c6a4de9d8f Mon Sep 17 00:00:00 2001 From: Alex Golovanov Date: Wed, 19 Jun 2024 15:54:33 +0300 Subject: [PATCH 19/20] perf: tweak event handler for fixed layout --- .../common/AnalyticsWrapper/AnalyticsWrapper.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/app/client/src/layoutSystems/common/AnalyticsWrapper/AnalyticsWrapper.tsx b/app/client/src/layoutSystems/common/AnalyticsWrapper/AnalyticsWrapper.tsx index 12b22ca06bb7..3114cd507cc0 100644 --- a/app/client/src/layoutSystems/common/AnalyticsWrapper/AnalyticsWrapper.tsx +++ b/app/client/src/layoutSystems/common/AnalyticsWrapper/AnalyticsWrapper.tsx @@ -18,12 +18,7 @@ export const AnalyticsWrapper: React.FC = ({ children }) => { if ( isInSideBySideEditor && - (isAnvil || - (wrapperElement && - wrapperElement !== e.relatedTarget && - e.relatedTarget instanceof Element && - e.relatedTarget.contains(wrapperElement) && - e.target instanceof Element)) + (isAnvil || (wrapperElement && wrapperElement === e.target)) ) { dispatch(sendAnalyticsForSideBySideHover()); } From 9cec247f7f1e82e0cc23fa5aa5499008bdc58964 Mon Sep 17 00:00:00 2001 From: Alex Golovanov Date: Wed, 19 Jun 2024 15:56:16 +0300 Subject: [PATCH 20/20] feat: added queries editor path --- app/client/src/sagas/AnalyticsSaga.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/client/src/sagas/AnalyticsSaga.ts b/app/client/src/sagas/AnalyticsSaga.ts index 96a77e978cf4..940f59c063e5 100644 --- a/app/client/src/sagas/AnalyticsSaga.ts +++ b/app/client/src/sagas/AnalyticsSaga.ts @@ -27,6 +27,7 @@ import { getIDEViewMode } from "selectors/ideSelectors"; import { JS_COLLECTION_EDITOR_PATH, + QUERIES_EDITOR_BASE_PATH, WIDGETS_EDITOR_BASE_PATH, } from "constants/routes"; import type { focusWidget } from "actions/widgetActions"; @@ -109,7 +110,8 @@ function* routeChangeInSideBySideModeSaga({ invokedBy === NavigationMethod.CanvasClick && viewMode === EditorViewMode.SplitScreen && pathName.includes(WIDGETS_EDITOR_BASE_PATH) && - prevPathName.includes(JS_COLLECTION_EDITOR_PATH) + (prevPathName.includes(JS_COLLECTION_EDITOR_PATH) || + prevPathName.includes(QUERIES_EDITOR_BASE_PATH)) ) { yield put(recordAnalyticsForSideBySideNavigation()); yield sendSideBySideWidgetHoverAnalyticsEventSaga();