From 60d45ea6ff2205244d39e9e0aef0e88cb1997d55 Mon Sep 17 00:00:00 2001 From: Jacques Ikot Date: Mon, 22 Apr 2024 15:58:37 +0100 Subject: [PATCH] feat: add analytics for drag and drop building blocks (#32699) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description > [!TIP] Add events to track the dragging of building blocks, dropping of blocks, and the time taken from drag till drop is complete. **Drag Event** ``` AnalyticsUtil.logEvent("DRAG_BUILDING_BLOCK_INITIATED", { applicationId, workspaceId, source: "explorer", eventData: { buildingBlockName: props.details.displayName, }, }); ``` **Drop Event** ``` AnalyticsUtil.logEvent("DROP_BUILDING_BLOCK_INITIATED", { applicationId, workspaceId, source: "explorer", eventData: { buildingBlockName: props.details.displayName, }, }); AnalyticsUtil.logEvent("DROP_BUILDING_BLOCK_COMPLETED", { applicationId, workspaceId, source: "explorer", eventData: { buildingBlockName: dragDetails.newWidget.displayName, timeTakenToCompletion: timeTakenTo CompleteValueInSeconds, timeTakenToDropWidgets: timeTakenValueInSeconds }, }); ``` Fixes #32492 ## Automation /ok-to-test tags="@tag.Widget" ### :mag: Cypress test results > [!TIP] > 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉 > Workflow run: > Commit: 8316506b039256ad6d171a3a81ddaec56cecdfc2 > Cypress dashboard url: Click here! ## Summary by CodeRabbit - **New Features** - Introduced analytics tracking for building block drag-and-drop operations. - Enhanced functionality for adding and managing building blocks within the application. - **Refactor** - Updated action type constants for better consistency in handling building block operations. - **Bug Fixes** - Improved logic for setting the start time of building block drag operations to ensure accurate tracking. --------- Co-authored-by: Anagh Hegde Co-authored-by: Rahul Barwal --- .../src/ce/constants/ReduxActionConstants.tsx | 2 + app/client/src/ce/utils/analyticsUtilTypes.ts | 6 +++ .../pages/Editor/widgetSidebar/WidgetCard.tsx | 25 +++++++-- .../uiReducers/buildingBlockReducer.ts | 19 +++++++ .../sagas/CanvasSagas/DraggingCanvasSagas.ts | 11 +++- app/client/src/sagas/WidgetAdditionSagas.ts | 53 +++++++++++++++++-- .../src/selectors/buildingBlocksSelectors.ts | 3 ++ .../src/selectors/templatesSelectors.tsx | 3 +- app/client/src/utils/buildingBlockUtils.ts | 28 ++++++++++ 9 files changed, 139 insertions(+), 11 deletions(-) create mode 100644 app/client/src/utils/buildingBlockUtils.ts diff --git a/app/client/src/ce/constants/ReduxActionConstants.tsx b/app/client/src/ce/constants/ReduxActionConstants.tsx index 3ff8c1f60c..bf65825c2d 100644 --- a/app/client/src/ce/constants/ReduxActionConstants.tsx +++ b/app/client/src/ce/constants/ReduxActionConstants.tsx @@ -928,6 +928,8 @@ const ActionTypes = { CLOSE_JS_ACTION_TAB: "CLOSE_JS_ACTION_TAB", CLOSE_JS_ACTION_TAB_SUCCESS: "CLOSE_JS_ACTION_TAB_SUCCESS", CLOSE_QUERY_ACTION_TAB: "CLOSE_QUERY_ACTION_TAB", + SET_BUILDING_BLOCK_DRAG_START_TIME: "SET_BUILDING_BLOCK_DRAG_START_TIME", + RESET_BUILDING_BLOCK_DRAG_START_TIME: "RESET_BUILDING_BLOCK_DRAG_START_TIME", CLOSE_QUERY_ACTION_TAB_SUCCESS: "CLOSE_QUERY_ACTION_TAB_SUCCESS", }; diff --git a/app/client/src/ce/utils/analyticsUtilTypes.ts b/app/client/src/ce/utils/analyticsUtilTypes.ts index e1668546d0..36f1c025f6 100644 --- a/app/client/src/ce/utils/analyticsUtilTypes.ts +++ b/app/client/src/ce/utils/analyticsUtilTypes.ts @@ -353,6 +353,7 @@ export type EventName = | "TEMPLATE_ADD_PAGE_FROM_TEMPLATE_FLOW" | HOMEPAGE_CREATE_APP_FROM_TEMPLATE_EVENTS | "EDITOR_MODE_CHANGE" + | BUILDING_BLOCKS_EVENTS | "VISIT_SELF_HOST_DOCS"; type HOMEPAGE_CREATE_APP_FROM_TEMPLATE_EVENTS = @@ -365,6 +366,11 @@ export type CANVAS_STARTER_BUILDING_BLOCK_EVENTS = | "STARTER_BUILDING_BLOCK_CONNECT_DATA_CLICK" | "STARTER_BUILDING_BLOCK_SEE_MORE_CLICK"; +export type BUILDING_BLOCKS_EVENTS = + | "DROP_BUILDING_BLOCK_INITIATED" + | "DROP_BUILDING_BLOCK_COMPLETED" + | "DRAG_BUILDING_BLOCK_INITIATED"; + export type ONBOARDING_FLOW_EVENTS = | "CREATE_APP_FROM_TEMPLATE" | "CREATE_APP_FROM_SCRATCH" diff --git a/app/client/src/pages/Editor/widgetSidebar/WidgetCard.tsx b/app/client/src/pages/Editor/widgetSidebar/WidgetCard.tsx index 9b85e1bc0f..94b2ba7ab0 100644 --- a/app/client/src/pages/Editor/widgetSidebar/WidgetCard.tsx +++ b/app/client/src/pages/Editor/widgetSidebar/WidgetCard.tsx @@ -5,6 +5,10 @@ import { useWidgetDragResize } from "utils/hooks/dragResizeHooks"; import AnalyticsUtil from "@appsmith/utils/AnalyticsUtil"; import { generateReactKey } from "utils/generators"; import { Text } from "design-system"; +import { BUILDING_BLOCK_EXPLORER_TYPE } from "constants/WidgetConstants"; +import { useSelector } from "react-redux"; +import { getCurrentApplicationId } from "selectors/editorSelectors"; +import { getCurrentWorkspaceId } from "@appsmith/selectors/selectedWorkspaceSelectors"; interface CardProps { details: WidgetCardProps; @@ -72,15 +76,28 @@ const THUMBNAIL_HEIGHT = 76; const THUMBNAIL_WIDTH = 72; function WidgetCard(props: CardProps) { + const applicationId = useSelector(getCurrentApplicationId); + const workspaceId = useSelector(getCurrentWorkspaceId); const { setDraggingNewWidget } = useWidgetDragResize(); const onDragStart = (e: any) => { e.preventDefault(); e.stopPropagation(); - AnalyticsUtil.logEvent("WIDGET_CARD_DRAG", { - widgetType: props.details.type, - widgetName: props.details.displayName, - }); + if (props.details.type === BUILDING_BLOCK_EXPLORER_TYPE) { + AnalyticsUtil.logEvent("DRAG_BUILDING_BLOCK_INITIATED", { + applicationId, + workspaceId, + source: "explorer", + eventData: { + buildingBlockName: props.details.displayName, + }, + }); + } else { + AnalyticsUtil.logEvent("WIDGET_CARD_DRAG", { + widgetType: props.details.type, + widgetName: props.details.displayName, + }); + } setDraggingNewWidget && setDraggingNewWidget(true, { ...props.details, diff --git a/app/client/src/reducers/uiReducers/buildingBlockReducer.ts b/app/client/src/reducers/uiReducers/buildingBlockReducer.ts index 4fe8463ebc..de279ab796 100644 --- a/app/client/src/reducers/uiReducers/buildingBlockReducer.ts +++ b/app/client/src/reducers/uiReducers/buildingBlockReducer.ts @@ -1,3 +1,4 @@ +import type { ReduxAction } from "@appsmith/constants/ReduxActionConstants"; import { ReduxActionErrorTypes, ReduxActionTypes, @@ -33,10 +34,28 @@ const buildingBlockReducer = createReducer(initialState, { isDraggingBuildingBlockToCanvas: false, }; }, + [ReduxActionTypes.SET_BUILDING_BLOCK_DRAG_START_TIME]: ( + state: BuildingBlocksReduxState, + action: ReduxAction<{ startTime: number }>, + ) => { + return { + ...state, + buildingBlockDragStartTimestamp: action.payload.startTime, + }; + }, + [ReduxActionTypes.RESET_BUILDING_BLOCK_DRAG_START_TIME]: ( + state: BuildingBlocksReduxState, + ) => { + return { + ...state, + buildingBlockDragStartTimestamp: null, + }; + }, }); export interface BuildingBlocksReduxState { isDraggingBuildingBlocksToCanvas: boolean; + buildingBlockDragStartTimestamp?: number; } export default buildingBlockReducer; diff --git a/app/client/src/sagas/CanvasSagas/DraggingCanvasSagas.ts b/app/client/src/sagas/CanvasSagas/DraggingCanvasSagas.ts index c891ec2071..9e74ee7c72 100644 --- a/app/client/src/sagas/CanvasSagas/DraggingCanvasSagas.ts +++ b/app/client/src/sagas/CanvasSagas/DraggingCanvasSagas.ts @@ -3,6 +3,7 @@ import { ReduxActionErrorTypes, ReduxActionTypes, } from "@appsmith/constants/ReduxActionConstants"; +import AnalyticsUtil from "@appsmith/utils/AnalyticsUtil"; import { BlueprintOperationTypes } from "WidgetProvider/constants"; import { generateAutoHeightLayoutTreeAction } from "actions/autoHeightActions"; import type { WidgetAddChild } from "actions/pageActions"; @@ -45,12 +46,13 @@ import { } from "sagas/selectors"; import { getCanvasWidth, + getCurrentApplicationId, getIsAutoLayoutMobileBreakPoint, getMainCanvasProps, getOccupiedSpacesSelectorForContainer, } from "selectors/editorSelectors"; import { getLayoutSystemType } from "selectors/layoutSystemSelectors"; -import AnalyticsUtil from "@appsmith/utils/AnalyticsUtil"; +import { initiateBuildingBlockDropEvent } from "utils/buildingBlockUtils"; import { collisionCheckPostReflow } from "utils/reflowHookUtils"; import type { WidgetProps } from "widgets/BaseWidget"; @@ -157,6 +159,8 @@ function* addBuildingBlockAndMoveWidgetsSaga( canvasId: string; }>, ) { + const applicationId: string = yield select(getCurrentApplicationId); + const workspaceId: string = yield select(getCurrentApplicationId); const dragDetails: DragDetails = yield select(getDragDetails); const buildingblockName = dragDetails.newWidget.displayName; const skeletonWidgetName = `loading_${buildingblockName @@ -177,6 +181,11 @@ function* addBuildingBlockAndMoveWidgetsSaga( }, }, }); + yield call(initiateBuildingBlockDropEvent, { + applicationId, + workspaceId, + buildingblockName, + }); const skeletonWidget: FlattenedWidgetProps = yield select( getWidgetByName, diff --git a/app/client/src/sagas/WidgetAdditionSagas.ts b/app/client/src/sagas/WidgetAdditionSagas.ts index 9cfbfb05e9..dd8996426f 100644 --- a/app/client/src/sagas/WidgetAdditionSagas.ts +++ b/app/client/src/sagas/WidgetAdditionSagas.ts @@ -92,6 +92,10 @@ import { SelectionRequestType } from "./WidgetSelectUtils"; import type { ActionDataState } from "@appsmith/reducers/entityReducers/actionsReducer"; import type { WidgetLayoutPositionInfo } from "layoutSystems/anvil/utils/layouts/widgetPositionUtils"; +import { getBuildingBlockDragStartTimestamp } from "selectors/buildingBlocksSelectors"; +import { initiateBuildingBlockDropEvent } from "utils/buildingBlockUtils"; +import AnalyticsUtil from "@appsmith/utils/AnalyticsUtil"; + const WidgetTypes = WidgetFactory.widgetTypes; export interface GeneratedWidgetPayload { @@ -650,9 +654,13 @@ export function* addBuildingBlockToApplication( try { const dragDetails: DragDetails = yield select(getDragDetails); const applicationId: string = yield select(getCurrentApplicationId); + const workspaceId: string = yield select(getCurrentWorkspaceId); const actionsBeforeAddingBuildingBlock: ActionDataState = yield select(getActions); const existingCopiedWidgets: unknown = yield call(getCopiedWidgets); + const buildingBlockDragStartTimestamp: number = yield select( + getBuildingBlockDragStartTimestamp, + ); // start loading for dragging building blocks yield put({ @@ -687,8 +695,10 @@ export function* addBuildingBlockToApplication( }); yield put(pasteWidget(false, mousePosition)); + const timeTakenToDropWidgetsInSeconds = + (Date.now() - buildingBlockDragStartTimestamp) / 1000; yield call(postPageAdditionSaga, applicationId); - // remove selecting of recently imported widgets + // remove selecting of recently pasted widgets caused by pasteWidget yield put(selectWidgetInitAction(SelectionRequestType.Empty)); // stop loading after pasting process is complete @@ -699,10 +709,34 @@ export function* addBuildingBlockToApplication( const actionsAfterAddingBuildingBlocks: ActionDataState = yield select(getActions); - yield runNewlyCreatedActions( - actionsBeforeAddingBuildingBlock, - actionsAfterAddingBuildingBlocks, - ); + if ( + response.data.onPageLoadActions && + response.data.onPageLoadActions.length > 0 + ) { + yield runNewlyCreatedActions( + actionsBeforeAddingBuildingBlock, + actionsAfterAddingBuildingBlocks, + ); + } + + const timeTakenToCompleteInMs = buildingBlockDragStartTimestamp + ? Date.now() - buildingBlockDragStartTimestamp + : 0; + const timeTakenToCompleteInSeconds = timeTakenToCompleteInMs / 1000; + + AnalyticsUtil.logEvent("DROP_BUILDING_BLOCK_COMPLETED", { + applicationId, + workspaceId, + source: "explorer", + eventData: { + buildingBlockName: dragDetails.newWidget.displayName, + timeTakenToCompletion: timeTakenToCompleteInSeconds, + timeTakenToDropWidgets: timeTakenToDropWidgetsInSeconds, + }, + }); + yield put({ + type: ReduxActionTypes.RESET_BUILDING_BLOCK_DRAG_START_TIME, + }); if (existingCopiedWidgets) { yield call(saveCopiedWidgets, JSON.stringify(existingCopiedWidgets)); @@ -725,6 +759,8 @@ export function* addBuildingBlockToApplication( } function* addBuildingBlockSaga(addEntityAction: ReduxAction) { + const applicationId: string = yield select(getCurrentApplicationId); + const workspaceId: string = yield select(getCurrentWorkspaceId); const dragDetails: DragDetails = yield select(getDragDetails); const buildingblockName = dragDetails.newWidget.displayName; const skeletonWidgetName = `loading_${buildingblockName @@ -743,6 +779,13 @@ function* addBuildingBlockSaga(addEntityAction: ReduxAction) { shouldReplay: false, }, }; + + yield call(initiateBuildingBlockDropEvent, { + applicationId, + workspaceId, + buildingblockName, + }); + yield call(addChildSaga, addSkeletonWidgetAction); const skeletonWidget: FlattenedWidgetProps = yield select( getWidgetByName, diff --git a/app/client/src/selectors/buildingBlocksSelectors.ts b/app/client/src/selectors/buildingBlocksSelectors.ts index c06fa740fa..dfbd2678f9 100644 --- a/app/client/src/selectors/buildingBlocksSelectors.ts +++ b/app/client/src/selectors/buildingBlocksSelectors.ts @@ -2,3 +2,6 @@ import type { AppState } from "@appsmith/reducers"; export const isDraggingBuildingBlockToCanvas = (state: AppState) => state.ui.buildingBlocks.isDraggingBuildingBlocksToCanvas; + +export const getBuildingBlockDragStartTimestamp = (state: AppState) => + state.ui.buildingBlocks.buildingBlockDragStartTimestamp; diff --git a/app/client/src/selectors/templatesSelectors.tsx b/app/client/src/selectors/templatesSelectors.tsx index 4888e71b51..229010749e 100644 --- a/app/client/src/selectors/templatesSelectors.tsx +++ b/app/client/src/selectors/templatesSelectors.tsx @@ -5,6 +5,7 @@ import { getFetchedWorkspaces } from "@appsmith/selectors/workspaceSelectors"; import { hasCreateNewAppPermission } from "@appsmith/utils/permissionHelpers"; import type { FilterKeys, Template } from "api/TemplatesApi"; import { + BUILDING_BLOCK_EXPLORER_TYPE, DEFAULT_COLUMNS_FOR_EXPLORER_BUILDING_BLOCKS, DEFAULT_ROWS_FOR_EXPLORER_BUILDING_BLOCKS, WIDGET_TAGS, @@ -80,7 +81,7 @@ export const getBuildingBlockExplorerCards = createSelector( columns: buildingBlock.templateGridColumnSize || DEFAULT_COLUMNS_FOR_EXPLORER_BUILDING_BLOCKS, - type: "BUILDING_BLOCK", + type: BUILDING_BLOCK_EXPLORER_TYPE, displayName: buildingBlock.title, icon: buildingBlock.screenshotUrls.length > 1 diff --git a/app/client/src/utils/buildingBlockUtils.ts b/app/client/src/utils/buildingBlockUtils.ts new file mode 100644 index 0000000000..e4cbfec08b --- /dev/null +++ b/app/client/src/utils/buildingBlockUtils.ts @@ -0,0 +1,28 @@ +import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants"; +import AnalyticsUtil from "@appsmith/utils/AnalyticsUtil"; +import { put } from "redux-saga/effects"; + +interface BuildingBlockDropInitiateEvent { + applicationId: string; + workspaceId: string; + buildingblockName: string; +} + +export function* initiateBuildingBlockDropEvent({ + applicationId, + buildingblockName, + workspaceId, +}: BuildingBlockDropInitiateEvent) { + AnalyticsUtil.logEvent("DROP_BUILDING_BLOCK_INITIATED", { + applicationId, + workspaceId, + source: "explorer", + eventData: { + buildingBlockName: buildingblockName, + }, + }); + yield put({ + type: ReduxActionTypes.SET_BUILDING_BLOCK_DRAG_START_TIME, + payload: { startTime: Date.now() }, + }); +}