feat: add analytics for drag and drop building blocks (#32699)

## 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"

### 🔍 Cypress test results
<!-- This is an auto-generated comment: Cypress test results  -->
> [!TIP]
> 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉
> Workflow run:
<https://github.com/appsmithorg/appsmith/actions/runs/8785306375>
> Commit: 8316506b039256ad6d171a3a81ddaec56cecdfc2
> Cypress dashboard url: <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=8785306375&attempt=1"
target="_blank">Click here!</a>

<!-- end of auto-generated comment: Cypress test results  -->






























<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## 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.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Anagh Hegde <anagh@appsmith.com>
Co-authored-by: Rahul Barwal <rahul.barwal@appsmith.com>
This commit is contained in:
Jacques Ikot 2024-04-22 15:58:37 +01:00 committed by GitHub
parent dc7bd0298f
commit 60d45ea6ff
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 139 additions and 11 deletions

View File

@ -928,6 +928,8 @@ const ActionTypes = {
CLOSE_JS_ACTION_TAB: "CLOSE_JS_ACTION_TAB", CLOSE_JS_ACTION_TAB: "CLOSE_JS_ACTION_TAB",
CLOSE_JS_ACTION_TAB_SUCCESS: "CLOSE_JS_ACTION_TAB_SUCCESS", CLOSE_JS_ACTION_TAB_SUCCESS: "CLOSE_JS_ACTION_TAB_SUCCESS",
CLOSE_QUERY_ACTION_TAB: "CLOSE_QUERY_ACTION_TAB", 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", CLOSE_QUERY_ACTION_TAB_SUCCESS: "CLOSE_QUERY_ACTION_TAB_SUCCESS",
}; };

View File

@ -353,6 +353,7 @@ export type EventName =
| "TEMPLATE_ADD_PAGE_FROM_TEMPLATE_FLOW" | "TEMPLATE_ADD_PAGE_FROM_TEMPLATE_FLOW"
| HOMEPAGE_CREATE_APP_FROM_TEMPLATE_EVENTS | HOMEPAGE_CREATE_APP_FROM_TEMPLATE_EVENTS
| "EDITOR_MODE_CHANGE" | "EDITOR_MODE_CHANGE"
| BUILDING_BLOCKS_EVENTS
| "VISIT_SELF_HOST_DOCS"; | "VISIT_SELF_HOST_DOCS";
type HOMEPAGE_CREATE_APP_FROM_TEMPLATE_EVENTS = 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_CONNECT_DATA_CLICK"
| "STARTER_BUILDING_BLOCK_SEE_MORE_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 = export type ONBOARDING_FLOW_EVENTS =
| "CREATE_APP_FROM_TEMPLATE" | "CREATE_APP_FROM_TEMPLATE"
| "CREATE_APP_FROM_SCRATCH" | "CREATE_APP_FROM_SCRATCH"

View File

@ -5,6 +5,10 @@ import { useWidgetDragResize } from "utils/hooks/dragResizeHooks";
import AnalyticsUtil from "@appsmith/utils/AnalyticsUtil"; import AnalyticsUtil from "@appsmith/utils/AnalyticsUtil";
import { generateReactKey } from "utils/generators"; import { generateReactKey } from "utils/generators";
import { Text } from "design-system"; 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 { interface CardProps {
details: WidgetCardProps; details: WidgetCardProps;
@ -72,15 +76,28 @@ const THUMBNAIL_HEIGHT = 76;
const THUMBNAIL_WIDTH = 72; const THUMBNAIL_WIDTH = 72;
function WidgetCard(props: CardProps) { function WidgetCard(props: CardProps) {
const applicationId = useSelector(getCurrentApplicationId);
const workspaceId = useSelector(getCurrentWorkspaceId);
const { setDraggingNewWidget } = useWidgetDragResize(); const { setDraggingNewWidget } = useWidgetDragResize();
const onDragStart = (e: any) => { const onDragStart = (e: any) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
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", { AnalyticsUtil.logEvent("WIDGET_CARD_DRAG", {
widgetType: props.details.type, widgetType: props.details.type,
widgetName: props.details.displayName, widgetName: props.details.displayName,
}); });
}
setDraggingNewWidget && setDraggingNewWidget &&
setDraggingNewWidget(true, { setDraggingNewWidget(true, {
...props.details, ...props.details,

View File

@ -1,3 +1,4 @@
import type { ReduxAction } from "@appsmith/constants/ReduxActionConstants";
import { import {
ReduxActionErrorTypes, ReduxActionErrorTypes,
ReduxActionTypes, ReduxActionTypes,
@ -33,10 +34,28 @@ const buildingBlockReducer = createReducer(initialState, {
isDraggingBuildingBlockToCanvas: false, 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 { export interface BuildingBlocksReduxState {
isDraggingBuildingBlocksToCanvas: boolean; isDraggingBuildingBlocksToCanvas: boolean;
buildingBlockDragStartTimestamp?: number;
} }
export default buildingBlockReducer; export default buildingBlockReducer;

View File

@ -3,6 +3,7 @@ import {
ReduxActionErrorTypes, ReduxActionErrorTypes,
ReduxActionTypes, ReduxActionTypes,
} from "@appsmith/constants/ReduxActionConstants"; } from "@appsmith/constants/ReduxActionConstants";
import AnalyticsUtil from "@appsmith/utils/AnalyticsUtil";
import { BlueprintOperationTypes } from "WidgetProvider/constants"; import { BlueprintOperationTypes } from "WidgetProvider/constants";
import { generateAutoHeightLayoutTreeAction } from "actions/autoHeightActions"; import { generateAutoHeightLayoutTreeAction } from "actions/autoHeightActions";
import type { WidgetAddChild } from "actions/pageActions"; import type { WidgetAddChild } from "actions/pageActions";
@ -45,12 +46,13 @@ import {
} from "sagas/selectors"; } from "sagas/selectors";
import { import {
getCanvasWidth, getCanvasWidth,
getCurrentApplicationId,
getIsAutoLayoutMobileBreakPoint, getIsAutoLayoutMobileBreakPoint,
getMainCanvasProps, getMainCanvasProps,
getOccupiedSpacesSelectorForContainer, getOccupiedSpacesSelectorForContainer,
} from "selectors/editorSelectors"; } from "selectors/editorSelectors";
import { getLayoutSystemType } from "selectors/layoutSystemSelectors"; import { getLayoutSystemType } from "selectors/layoutSystemSelectors";
import AnalyticsUtil from "@appsmith/utils/AnalyticsUtil"; import { initiateBuildingBlockDropEvent } from "utils/buildingBlockUtils";
import { collisionCheckPostReflow } from "utils/reflowHookUtils"; import { collisionCheckPostReflow } from "utils/reflowHookUtils";
import type { WidgetProps } from "widgets/BaseWidget"; import type { WidgetProps } from "widgets/BaseWidget";
@ -157,6 +159,8 @@ function* addBuildingBlockAndMoveWidgetsSaga(
canvasId: string; canvasId: string;
}>, }>,
) { ) {
const applicationId: string = yield select(getCurrentApplicationId);
const workspaceId: string = yield select(getCurrentApplicationId);
const dragDetails: DragDetails = yield select(getDragDetails); const dragDetails: DragDetails = yield select(getDragDetails);
const buildingblockName = dragDetails.newWidget.displayName; const buildingblockName = dragDetails.newWidget.displayName;
const skeletonWidgetName = `loading_${buildingblockName const skeletonWidgetName = `loading_${buildingblockName
@ -177,6 +181,11 @@ function* addBuildingBlockAndMoveWidgetsSaga(
}, },
}, },
}); });
yield call(initiateBuildingBlockDropEvent, {
applicationId,
workspaceId,
buildingblockName,
});
const skeletonWidget: FlattenedWidgetProps = yield select( const skeletonWidget: FlattenedWidgetProps = yield select(
getWidgetByName, getWidgetByName,

View File

@ -92,6 +92,10 @@ import { SelectionRequestType } from "./WidgetSelectUtils";
import type { ActionDataState } from "@appsmith/reducers/entityReducers/actionsReducer"; import type { ActionDataState } from "@appsmith/reducers/entityReducers/actionsReducer";
import type { WidgetLayoutPositionInfo } from "layoutSystems/anvil/utils/layouts/widgetPositionUtils"; 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; const WidgetTypes = WidgetFactory.widgetTypes;
export interface GeneratedWidgetPayload { export interface GeneratedWidgetPayload {
@ -650,9 +654,13 @@ export function* addBuildingBlockToApplication(
try { try {
const dragDetails: DragDetails = yield select(getDragDetails); const dragDetails: DragDetails = yield select(getDragDetails);
const applicationId: string = yield select(getCurrentApplicationId); const applicationId: string = yield select(getCurrentApplicationId);
const workspaceId: string = yield select(getCurrentWorkspaceId);
const actionsBeforeAddingBuildingBlock: ActionDataState = const actionsBeforeAddingBuildingBlock: ActionDataState =
yield select(getActions); yield select(getActions);
const existingCopiedWidgets: unknown = yield call(getCopiedWidgets); const existingCopiedWidgets: unknown = yield call(getCopiedWidgets);
const buildingBlockDragStartTimestamp: number = yield select(
getBuildingBlockDragStartTimestamp,
);
// start loading for dragging building blocks // start loading for dragging building blocks
yield put({ yield put({
@ -687,8 +695,10 @@ export function* addBuildingBlockToApplication(
}); });
yield put(pasteWidget(false, mousePosition)); yield put(pasteWidget(false, mousePosition));
const timeTakenToDropWidgetsInSeconds =
(Date.now() - buildingBlockDragStartTimestamp) / 1000;
yield call(postPageAdditionSaga, applicationId); yield call(postPageAdditionSaga, applicationId);
// remove selecting of recently imported widgets // remove selecting of recently pasted widgets caused by pasteWidget
yield put(selectWidgetInitAction(SelectionRequestType.Empty)); yield put(selectWidgetInitAction(SelectionRequestType.Empty));
// stop loading after pasting process is complete // stop loading after pasting process is complete
@ -699,10 +709,34 @@ export function* addBuildingBlockToApplication(
const actionsAfterAddingBuildingBlocks: ActionDataState = const actionsAfterAddingBuildingBlocks: ActionDataState =
yield select(getActions); yield select(getActions);
if (
response.data.onPageLoadActions &&
response.data.onPageLoadActions.length > 0
) {
yield runNewlyCreatedActions( yield runNewlyCreatedActions(
actionsBeforeAddingBuildingBlock, actionsBeforeAddingBuildingBlock,
actionsAfterAddingBuildingBlocks, 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) { if (existingCopiedWidgets) {
yield call(saveCopiedWidgets, JSON.stringify(existingCopiedWidgets)); yield call(saveCopiedWidgets, JSON.stringify(existingCopiedWidgets));
@ -725,6 +759,8 @@ export function* addBuildingBlockToApplication(
} }
function* addBuildingBlockSaga(addEntityAction: ReduxAction<WidgetAddChild>) { function* addBuildingBlockSaga(addEntityAction: ReduxAction<WidgetAddChild>) {
const applicationId: string = yield select(getCurrentApplicationId);
const workspaceId: string = yield select(getCurrentWorkspaceId);
const dragDetails: DragDetails = yield select(getDragDetails); const dragDetails: DragDetails = yield select(getDragDetails);
const buildingblockName = dragDetails.newWidget.displayName; const buildingblockName = dragDetails.newWidget.displayName;
const skeletonWidgetName = `loading_${buildingblockName const skeletonWidgetName = `loading_${buildingblockName
@ -743,6 +779,13 @@ function* addBuildingBlockSaga(addEntityAction: ReduxAction<WidgetAddChild>) {
shouldReplay: false, shouldReplay: false,
}, },
}; };
yield call(initiateBuildingBlockDropEvent, {
applicationId,
workspaceId,
buildingblockName,
});
yield call(addChildSaga, addSkeletonWidgetAction); yield call(addChildSaga, addSkeletonWidgetAction);
const skeletonWidget: FlattenedWidgetProps = yield select( const skeletonWidget: FlattenedWidgetProps = yield select(
getWidgetByName, getWidgetByName,

View File

@ -2,3 +2,6 @@ import type { AppState } from "@appsmith/reducers";
export const isDraggingBuildingBlockToCanvas = (state: AppState) => export const isDraggingBuildingBlockToCanvas = (state: AppState) =>
state.ui.buildingBlocks.isDraggingBuildingBlocksToCanvas; state.ui.buildingBlocks.isDraggingBuildingBlocksToCanvas;
export const getBuildingBlockDragStartTimestamp = (state: AppState) =>
state.ui.buildingBlocks.buildingBlockDragStartTimestamp;

View File

@ -5,6 +5,7 @@ import { getFetchedWorkspaces } from "@appsmith/selectors/workspaceSelectors";
import { hasCreateNewAppPermission } from "@appsmith/utils/permissionHelpers"; import { hasCreateNewAppPermission } from "@appsmith/utils/permissionHelpers";
import type { FilterKeys, Template } from "api/TemplatesApi"; import type { FilterKeys, Template } from "api/TemplatesApi";
import { import {
BUILDING_BLOCK_EXPLORER_TYPE,
DEFAULT_COLUMNS_FOR_EXPLORER_BUILDING_BLOCKS, DEFAULT_COLUMNS_FOR_EXPLORER_BUILDING_BLOCKS,
DEFAULT_ROWS_FOR_EXPLORER_BUILDING_BLOCKS, DEFAULT_ROWS_FOR_EXPLORER_BUILDING_BLOCKS,
WIDGET_TAGS, WIDGET_TAGS,
@ -80,7 +81,7 @@ export const getBuildingBlockExplorerCards = createSelector(
columns: columns:
buildingBlock.templateGridColumnSize || buildingBlock.templateGridColumnSize ||
DEFAULT_COLUMNS_FOR_EXPLORER_BUILDING_BLOCKS, DEFAULT_COLUMNS_FOR_EXPLORER_BUILDING_BLOCKS,
type: "BUILDING_BLOCK", type: BUILDING_BLOCK_EXPLORER_TYPE,
displayName: buildingBlock.title, displayName: buildingBlock.title,
icon: icon:
buildingBlock.screenshotUrls.length > 1 buildingBlock.screenshotUrls.length > 1

View File

@ -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() },
});
}