From 0b9a0cc6ab9b8ac4720cfdd84324a0c39b3eaa40 Mon Sep 17 00:00:00 2001 From: Satbir Singh Date: Fri, 6 Mar 2020 04:59:24 +0000 Subject: [PATCH] More events --- app/client/src/actions/actionActions.ts | 2 +- app/client/src/actions/applicationActions.ts | 15 ++++ app/client/src/api/ApplicationApi.tsx | 9 +++ app/client/src/api/DatasourcesApi.ts | 2 + .../DynamicAutocompleteInput.tsx | 9 ++- .../editorComponents/NavBarItem.tsx | 12 ++- .../form/fields/DatasourcesField.tsx | 12 ++- .../src/constants/ReduxActionConstants.tsx | 3 + app/client/src/pages/Applications/index.tsx | 4 + .../src/pages/Editor/APIEditor/Form.tsx | 7 +- .../src/pages/Editor/APIEditor/index.tsx | 19 ++++- app/client/src/pages/Editor/ApiSidebar.tsx | 22 ++++-- app/client/src/pages/Editor/EditorSidebar.tsx | 3 +- app/client/src/pages/Editor/index.tsx | 13 ++++ app/client/src/pages/common/SubHeader.tsx | 2 + .../uiReducers/applicationsReducer.tsx | 12 +++ app/client/src/sagas/ActionSagas.ts | 77 ++++++++++++++++++- app/client/src/sagas/ApplicationSagas.tsx | 31 ++++++++ app/client/src/sagas/DatasourcesSagas.ts | 5 ++ app/client/src/sagas/InitSagas.ts | 34 +++++++- .../src/selectors/applicationSelectors.tsx | 2 + app/client/src/utils/AnalyticsUtil.tsx | 53 ++++++++++++- 22 files changed, 328 insertions(+), 20 deletions(-) diff --git a/app/client/src/actions/actionActions.ts b/app/client/src/actions/actionActions.ts index 2419741f71..c937db1818 100644 --- a/app/client/src/actions/actionActions.ts +++ b/app/client/src/actions/actionActions.ts @@ -70,7 +70,7 @@ export const updateActionSuccess = (payload: { data: RestAction }) => { }; }; -export const deleteAction = (payload: { id: string }) => { +export const deleteAction = (payload: { id: string; name: string }) => { return { type: ReduxActionTypes.DELETE_ACTION_INIT, payload, diff --git a/app/client/src/actions/applicationActions.ts b/app/client/src/actions/applicationActions.ts index 89de271e4e..0f4fecc33a 100644 --- a/app/client/src/actions/applicationActions.ts +++ b/app/client/src/actions/applicationActions.ts @@ -12,3 +12,18 @@ export const setDefaultApplicationPageSuccess = ( }, }; }; + +export const fetchApplications = () => { + return { + type: ReduxActionTypes.FETCH_APPLICATION_LIST_INIT, + }; +}; + +export const fetchApplication = (applicationId: string) => { + return { + type: ReduxActionTypes.FETCH_APPLICATION_INIT, + payload: { + applicationId, + }, + }; +}; diff --git a/app/client/src/api/ApplicationApi.tsx b/app/client/src/api/ApplicationApi.tsx index 3d18b77e6c..2a19db1690 100644 --- a/app/client/src/api/ApplicationApi.tsx +++ b/app/client/src/api/ApplicationApi.tsx @@ -23,6 +23,10 @@ export interface ApplicationResponsePayload { pages?: ApplicationPagePayload[]; } +export interface FetchApplicationResponse extends ApiResponse { + data: ApplicationResponsePayload & { pages: ApplicationPagePayload[] }; +} + export interface FetchApplicationsResponse extends ApiResponse { data: Array; } @@ -62,6 +66,11 @@ class ApplicationApi extends Api { static fetchApplications(): AxiosPromise { return Api.get(ApplicationApi.baseURL); } + static fetchApplication( + applicationId: string, + ): AxiosPromise { + return Api.get(ApplicationApi.baseURL + applicationId); + } static createApplication( request: CreateApplicationRequest, ): AxiosPromise { diff --git a/app/client/src/api/DatasourcesApi.ts b/app/client/src/api/DatasourcesApi.ts index 0b04297ea9..207402ffe9 100644 --- a/app/client/src/api/DatasourcesApi.ts +++ b/app/client/src/api/DatasourcesApi.ts @@ -26,6 +26,8 @@ export interface CreateDatasourceConfig { datasourceConfiguration: { url: string; }; + //Passed for logging purposes. + appName: string; } class DatasourcesApi extends API { diff --git a/app/client/src/components/editorComponents/DynamicAutocompleteInput.tsx b/app/client/src/components/editorComponents/DynamicAutocompleteInput.tsx index 75bf0d4241..504e75a72c 100644 --- a/app/client/src/components/editorComponents/DynamicAutocompleteInput.tsx +++ b/app/client/src/components/editorComponents/DynamicAutocompleteInput.tsx @@ -18,6 +18,7 @@ import _ from "lodash"; import { parseDynamicString } from "utils/DynamicBindingUtils"; import { DataTree } from "entities/DataTree/dataTreeFactory"; import { Theme } from "constants/DefaultTheme"; +import AnalyticsUtil from "utils/AnalyticsUtil"; require("codemirror/mode/javascript/javascript"); const getBorderStyle = ( @@ -277,8 +278,13 @@ class DynamicAutocompleteInput extends Component { } }; - handleChange = () => { + handleChange = (instance?: any, changeObj?: any) => { const value = this.editor.getValue(); + if (changeObj && changeObj.origin === "complete") { + AnalyticsUtil.logEvent("AUTO_COMPLETE_SELECT", { + searchString: changeObj.text[0], + }); + } const inputValue = this.props.input.value; if (this.props.input.onChange && value !== inputValue) { this.props.input.onChange(value); @@ -312,6 +318,7 @@ class DynamicAutocompleteInput extends Component { }); const shouldShow = cursorBetweenBinding && !cm.state.completionActive; if (shouldShow) { + AnalyticsUtil.logEvent("AUTO_COMPELTE_SHOW", {}); cm.showHint(cm); } } diff --git a/app/client/src/components/editorComponents/NavBarItem.tsx b/app/client/src/components/editorComponents/NavBarItem.tsx index 02264cd3c7..d2e375c8af 100644 --- a/app/client/src/components/editorComponents/NavBarItem.tsx +++ b/app/client/src/components/editorComponents/NavBarItem.tsx @@ -1,6 +1,7 @@ import React from "react"; import styled from "styled-components"; import { NavLink } from "react-router-dom"; +import AnalyticsUtil from "utils/AnalyticsUtil"; type MenuBarItemProps = { icon: Function; @@ -62,7 +63,16 @@ class NavBarItem extends React.Component { const { title, icon, path, exact } = this.props; return ( - + { + AnalyticsUtil.logEvent("SIDEBAR_NAVIGATION", { + navPage: this.props.title.toUpperCase(), + }); + }} + > {icon({ width: 24, height: 24 })} {title} diff --git a/app/client/src/components/editorComponents/form/fields/DatasourcesField.tsx b/app/client/src/components/editorComponents/form/fields/DatasourcesField.tsx index a4b4f1d4b1..3b9dfaea14 100644 --- a/app/client/src/components/editorComponents/form/fields/DatasourcesField.tsx +++ b/app/client/src/components/editorComponents/form/fields/DatasourcesField.tsx @@ -6,6 +6,7 @@ import { AppState } from "reducers"; import { DatasourceDataState } from "reducers/entityReducers/datasourceReducer"; import _ from "lodash"; import { createDatasource } from "actions/datasourcesActions"; +import AnalyticsUtil from "utils/AnalyticsUtil"; interface ReduxStateProps { datasources: DatasourceDataState; @@ -17,6 +18,7 @@ interface ReduxActionProps { interface ComponentProps { name: string; pluginId: string; + appName: string; } const DatasourcesField = ( @@ -55,7 +57,11 @@ const mapDispatchToProps = ( dispatch: any, ownProps: ComponentProps, ): ReduxActionProps => ({ - createDatasource: (value: string) => + createDatasource: (value: string) => { + AnalyticsUtil.logEvent("CREATE_DATA_SOURCE_CLICK", { + appName: ownProps.appName, + dataSource: value, + }); dispatch( createDatasource({ // Datasource name should not end with / @@ -65,8 +71,10 @@ const mapDispatchToProps = ( url: value.endsWith("/") ? value : `${value}/`, }, pluginId: ownProps.pluginId, + appName: ownProps.appName, }), - ), + ); + }, }); export default connect(mapStateToProps, mapDispatchToProps)(DatasourcesField); diff --git a/app/client/src/constants/ReduxActionConstants.tsx b/app/client/src/constants/ReduxActionConstants.tsx index ab0f58c063..915b05465c 100644 --- a/app/client/src/constants/ReduxActionConstants.tsx +++ b/app/client/src/constants/ReduxActionConstants.tsx @@ -73,6 +73,8 @@ export const ReduxActionTypes: { [key: string]: string } = { INITIALIZE_PAGE_VIEWER_SUCCESS: "INITIALIZE_PAGE_VIEWER_SUCCESS", FETCH_APPLICATION_LIST_INIT: "FETCH_APPLICATION_LIST_INIT", FETCH_APPLICATION_LIST_SUCCESS: "FETCH_APPLICATION_LIST_SUCCESS", + FETCH_APPLICATION_INIT: "FETCH_APPLICATION_INIT", + FETCH_APPLICATION_SUCCESS: "FETCH_APFETCH_APPLICATION_SUCCESSPLICATION_INIT", CREATE_APPLICATION_INIT: "CREATE_APPLICATION_INIT", CREATE_APPLICATION_SUCCESS: "CREATE_APPLICATION_SUCCESS", UPDATE_WIDGET_PROPERTY_VALIDATION: "UPDATE_WIDGET_PROPERTY_VALIDATION", @@ -180,6 +182,7 @@ export const ReduxActionErrorTypes: { [key: string]: string } = { CREATE_PAGE_ERROR: "CREATE_PAGE_ERROR", FETCH_PAGE_LIST_ERROR: "FETCH_PAGE_LIST_ERROR", FETCH_APPLICATION_LIST_ERROR: "FETCH_APPLICATION_LIST_ERROR", + FETCH_APPLICATION_ERROR: "FETCH_APPLICATION_ERROR", CREATE_APPLICATION_ERROR: "CREATE_APPLICATION_ERROR", LOGIN_USER_ERROR: "LOGIN_USER_ERROR", CREATE_USER_ERROR: "CREATE_USER_ERROR", diff --git a/app/client/src/pages/Applications/index.tsx b/app/client/src/pages/Applications/index.tsx index fec59bf371..dcad4eeb5c 100644 --- a/app/client/src/pages/Applications/index.tsx +++ b/app/client/src/pages/Applications/index.tsx @@ -22,6 +22,7 @@ import CreateApplicationForm from "./CreateApplicationForm"; import { CREATE_APPLICATION_FORM_NAME } from "constants/forms"; import { DELETING_APPLICATION } from "constants/messages"; import { AppToaster } from "components/editorComponents/ToastComponent"; +import AnalyticsUtil from "utils/AnalyticsUtil"; const ApplicationCardsWrapper = styled.div` display: flex; @@ -64,6 +65,9 @@ class Applications extends Component { isAdding: this.props.isCreatingApplication, errorAdding: this.props.createApplicationError, formSubmitText: "Create", + onClick: () => { + AnalyticsUtil.logEvent("CREATE_APP_CLICK", {}); + }, }} search={{ placeholder: "Search", diff --git a/app/client/src/pages/Editor/APIEditor/Form.tsx b/app/client/src/pages/Editor/APIEditor/Form.tsx index 9f374d8934..30bf77d574 100644 --- a/app/client/src/pages/Editor/APIEditor/Form.tsx +++ b/app/client/src/pages/Editor/APIEditor/Form.tsx @@ -94,6 +94,7 @@ interface APIFormProps { isRunning: boolean; isDeleting: boolean; paginationType: PaginationType; + appName: string; } type Props = APIFormProps & InjectedFormProps; @@ -153,7 +154,11 @@ const ApiEditorForm: React.FC = (props: Props) => { options={HTTP_METHOD_OPTIONS} /> - + void; createAction: (values: RestAction) => void; runAction: (id: string, paginationField: PaginationField) => void; - deleteAction: (id: string) => void; + deleteAction: (id: string, name: string) => void; updateAction: (data: RestAction) => void; } @@ -55,7 +58,10 @@ class ApiEditor extends React.Component { this.props.submitForm(API_EDITOR_FORM_NAME); }; handleDeleteClick = () => { - this.props.deleteAction(this.props.match.params.apiId); + this.props.deleteAction( + this.props.match.params.apiId, + this.props.formData.name, + ); }; handleRunClick = (paginationField?: PaginationField) => { this.props.runAction(this.props.match.params.apiId, paginationField); @@ -96,6 +102,11 @@ class ApiEditor extends React.Component { onSaveClick={this.handleSaveClick} onDeleteClick={this.handleDeleteClick} onRunClick={this.handleRunClick} + appName={ + this.props.currentApplication + ? this.props.currentApplication.name + : "" + } /> ) : ( @@ -111,6 +122,7 @@ const mapStateToProps = (state: AppState): ReduxStateProps => ({ pluginId: getPluginIdOfName(state, PLUGIN_NAME), actions: state.entities.actions, apiPane: state.ui.apiPane, + currentApplication: getCurrentApplication(state), formData: getFormValues(API_EDITOR_FORM_NAME)(state) as RestAction, }); @@ -119,7 +131,8 @@ const mapDispatchToProps = (dispatch: any): ReduxActionProps => ({ createAction: (action: RestAction) => dispatch(createActionRequest(action)), runAction: (id: string, paginationField: PaginationField) => dispatch(runApiAction(id, paginationField)), - deleteAction: (id: string) => dispatch(deleteAction({ id })), + deleteAction: (id: string, name: string) => + dispatch(deleteAction({ id, name })), updateAction: (data: RestAction) => dispatch(updateAction({ data })), }); diff --git a/app/client/src/pages/Editor/ApiSidebar.tsx b/app/client/src/pages/Editor/ApiSidebar.tsx index 71907b6768..27774017f9 100644 --- a/app/client/src/pages/Editor/ApiSidebar.tsx +++ b/app/client/src/pages/Editor/ApiSidebar.tsx @@ -18,6 +18,7 @@ import { getPluginIdOfName } from "selectors/entitiesSelector"; import { DEFAULT_API_ACTION, PLUGIN_NAME } from "constants/ApiEditorConstants"; import EditorSidebar from "pages/Editor/EditorSidebar"; import { getNextEntityName } from "utils/AppsmithUtils"; +import AnalyticsUtil from "utils/AnalyticsUtil"; const HTTPMethod = styled.span<{ method?: string }>` flex: 1; @@ -69,7 +70,7 @@ interface ReduxDispatchProps { originalPageId: string, ) => void; copyAction: (id: string, pageId: string, name: string) => void; - deleteAction: (id: string) => void; + deleteAction: (id: string, name: string) => void; } type Props = ReduxStateProps & @@ -99,6 +100,9 @@ class ApiSidebar extends React.Component { .map(a => a.config.name); const newName = getNextEntityName("Api", pageApiNames); this.props.createAction({ ...DEFAULT_API_ACTION, name: newName, pageId }); + AnalyticsUtil.logEvent("CREATE_API_CLICK", { + apiName: newName, + }); }; handleApiChange = (actionId: string) => { @@ -134,13 +138,20 @@ class ApiSidebar extends React.Component { this.props.copyAction(itemId, destinationPageId, name); }; - handleDelete = (itemId: string) => { - this.props.deleteAction(itemId); + handleDelete = (itemId: string, itemName: string) => { + this.props.deleteAction(itemId, itemName); }; renderItem = (action: RestAction) => { return ( - + { + AnalyticsUtil.logEvent("API_SELECT", { + apiId: action.id, + apiName: action.name, + }); + }} + > {action.actionConfiguration ? ( {action.actionConfiguration.httpMethod} @@ -203,7 +214,8 @@ const mapDispatchToProps = (dispatch: Function): ReduxDispatchProps => ({ ), copyAction: (id: string, destinationPageId: string, name: string) => dispatch(copyActionRequest({ id, destinationPageId, name })), - deleteAction: (id: string) => dispatch(deleteAction({ id })), + deleteAction: (id: string, name: string) => + dispatch(deleteAction({ id, name })), }); export default connect(mapStateToProps, mapDispatchToProps)(ApiSidebar); diff --git a/app/client/src/pages/Editor/EditorSidebar.tsx b/app/client/src/pages/Editor/EditorSidebar.tsx index 29ff5a8e3c..f2bf09a37c 100644 --- a/app/client/src/pages/Editor/EditorSidebar.tsx +++ b/app/client/src/pages/Editor/EditorSidebar.tsx @@ -159,7 +159,7 @@ type EditorSidebarComponentProps = { onItemSelected: (itemId: string) => void; moveItem: (itemId: string, destinationPageId: string) => void; copyItem: (itemId: string, destinationPageId: string) => void; - deleteItem: (itemId: string) => void; + deleteItem: (itemId: string, itemName: string) => void; }; type Props = ReduxStateProps & @@ -389,6 +389,7 @@ class EditorSidebar extends React.Component { onSelect: () => this.props.deleteItem( item.id, + item.name, ), label: "Delete", intent: "danger", diff --git a/app/client/src/pages/Editor/index.tsx b/app/client/src/pages/Editor/index.tsx index 7e2c3bb2ee..30f1c0788b 100644 --- a/app/client/src/pages/Editor/index.tsx +++ b/app/client/src/pages/Editor/index.tsx @@ -27,9 +27,12 @@ import { import { ReduxActionTypes, PageListPayload, + ApplicationPayload, } from "constants/ReduxActionConstants"; import { Dialog, Classes, AnchorButton } from "@blueprintjs/core"; import { initEditor } from "actions/initActions"; +import { getCurrentApplication } from "selectors/applicationSelectors"; +import AnalyticsUtil from "utils/AnalyticsUtil"; type EditorProps = { currentPageName?: string; @@ -49,6 +52,7 @@ type EditorProps = { errorPublishing: boolean; publishedTime?: string; isPageSwitching: boolean; + currentApplication?: ApplicationPayload; } & RouteComponentProps; class Editor extends Component { @@ -80,6 +84,14 @@ class Editor extends Component { handlePublish = () => { if (this.props.currentApplicationId) { this.props.publishApplication(this.props.currentApplicationId); + + const appName = this.props.currentApplication + ? this.props.currentApplication.name + : ""; + AnalyticsUtil.logEvent("PUBLISH_APP", { + appId: this.props.currentApplicationId, + appName: appName, + }); } }; handleCreatePage = (pageName: string) => { @@ -152,6 +164,7 @@ const mapStateToProps = (state: AppState) => ({ currentPageName: state.ui.editor.currentPageName, isSaving: getIsPageSaving(state), currentApplicationId: getCurrentApplicationId(state), + currentApplication: getCurrentApplication(state), currentPageId: getCurrentPageId(state), currentLayoutId: getCurrentLayoutId(state), pages: getPageList(state), diff --git a/app/client/src/pages/common/SubHeader.tsx b/app/client/src/pages/common/SubHeader.tsx index d382f2a6f6..8f9d36a580 100644 --- a/app/client/src/pages/common/SubHeader.tsx +++ b/app/client/src/pages/common/SubHeader.tsx @@ -28,6 +28,7 @@ type SubHeaderProps = { formSubmitIntent: string; errorAdding?: string; formSubmitText: string; + onClick: () => void; }; search?: { placeholder: string; @@ -50,6 +51,7 @@ export const ApplicationsSubHeader = (props: SubHeaderProps) => { text={props.add.title} icon="plus" title={props.add.title} + onClick={props.add.onClick} filled intent="primary" /> diff --git a/app/client/src/reducers/uiReducers/applicationsReducer.tsx b/app/client/src/reducers/uiReducers/applicationsReducer.tsx index fd7d6c5822..3cd9912b73 100644 --- a/app/client/src/reducers/uiReducers/applicationsReducer.tsx +++ b/app/client/src/reducers/uiReducers/applicationsReducer.tsx @@ -49,6 +49,17 @@ const applicationsReducer = createReducer(initialState, { applicationList: action.payload, isFetchingApplications: false, }), + [ReduxActionTypes.FETCH_APPLICATION_INIT]: ( + state: ApplicationsReduxState, + ) => ({ ...state, isFetchingApplication: true }), + [ReduxActionTypes.FETCH_APPLICATION_SUCCESS]: ( + state: ApplicationsReduxState, + action: ReduxAction<{ applicationList: ApplicationPayload[] }>, + ) => ({ + ...state, + currentApplication: action.payload, + isFetchingApplication: false, + }), [ReduxActionTypes.CREATE_APPLICATION_INIT]: ( state: ApplicationsReduxState, ) => ({ @@ -93,6 +104,7 @@ export interface ApplicationsReduxState { creatingApplication: boolean; createApplicationError?: string; deletingApplication: boolean; + currentApplication?: ApplicationPayload; } export default applicationsReducer; diff --git a/app/client/src/sagas/ActionSagas.ts b/app/client/src/sagas/ActionSagas.ts index e502a0fb17..d3db583526 100644 --- a/app/client/src/sagas/ActionSagas.ts +++ b/app/client/src/sagas/ActionSagas.ts @@ -71,6 +71,7 @@ import { getApplicationViewerPageURL, } from "constants/routes"; import { ToastType } from "react-toastify"; +import AnalyticsUtil from "utils/AnalyticsUtil"; export const getAction = ( state: AppState, @@ -85,6 +86,25 @@ const createActionResponse = (response: ActionApiResponse): ActionResponse => ({ ...response.clientMeta, }); +function getCurrentPageNameByActionId( + state: AppState, + actionId: string, +): string { + const action = state.entities.actions.find(action => { + return action.config.id === actionId; + }); + const pageId = action ? action.config.pageId : ""; + return getPageNameByPageId(state, pageId); +} + +function getPageNameByPageId(state: AppState, pageId: string): string { + const page = state.entities.pageList.pages.find( + page => page.pageId === pageId, + ); + const pageName = page ? page.pageName : ""; + return pageName; +} + const createActionErrorResponse = ( response: ActionApiResponse, ): ActionResponse => ({ @@ -284,6 +304,17 @@ export function* createActionSaga(actionPayload: ReduxAction) { message: `${actionPayload.payload.name} Action created`, type: ToastType.SUCCESS, }); + + const pageName = yield select( + getCurrentPageNameByActionId, + response.data.id, + ); + + AnalyticsUtil.logEvent("CREATE_API", { + apiId: response.data.id, + apiName: response.data.name, + pageName: pageName, + }); yield put(createActionSuccess(response.data)); } } catch (error) { @@ -351,6 +382,17 @@ export function* updateActionSaga( message: `${actionPayload.payload.data.name} Action updated`, type: ToastType.SUCCESS, }); + + const pageName = yield select( + getCurrentPageNameByActionId, + response.data.id, + ); + + AnalyticsUtil.logEvent("SAVE_API", { + apiId: response.data.id, + apiName: response.data.name, + pageName: pageName, + }); yield put(updateActionSuccess({ data: response.data })); yield put(runApiAction(data.id)); } @@ -362,9 +404,12 @@ export function* updateActionSaga( } } -export function* deleteActionSaga(actionPayload: ReduxAction<{ id: string }>) { +export function* deleteActionSaga( + actionPayload: ReduxAction<{ id: string; name: string }>, +) { try { const id = actionPayload.payload.id; + const name = actionPayload.payload.name; const response: GenericApiResponse = yield ActionAPI.deleteAction( id, ); @@ -374,6 +419,12 @@ export function* deleteActionSaga(actionPayload: ReduxAction<{ id: string }>) { message: `${response.data.name} Action deleted`, type: ToastType.SUCCESS, }); + const pageName = yield select(getCurrentPageNameByActionId, id); + AnalyticsUtil.logEvent("DELETE_API", { + apiName: name, + pageName: pageName, + apiID: id, + }); yield put(deleteActionSuccess({ id })); } } catch (error) { @@ -432,6 +483,17 @@ export function* runApiActionSaga( payload = createActionErrorResponse(response); } const id = values.id || "DRY_RUN"; + + const pageName = yield select(getCurrentPageNameByActionId, values.id); + + AnalyticsUtil.logEvent("RUN_API", { + apiId: values.id, + apiName: values.name, + pageName: pageName, + responseTime: response.clientMeta.duration, + apiType: "INTERNAL", + }); + yield put({ type: ReduxActionTypes.RUN_API_SUCCESS, payload: { [id]: payload }, @@ -496,6 +558,12 @@ function* moveActionSaga( type: ToastType.SUCCESS, }); } + const pageName = yield select(getPageNameByPageId, response.data.pageId); + AnalyticsUtil.logEvent("MOVE_API", { + apiName: response.data.name, + pageName: pageName, + apiID: response.data.id, + }); yield put(moveActionSuccess(response.data)); } catch (e) { AppToaster.show({ @@ -537,6 +605,13 @@ function* copyActionSaga( type: ToastType.SUCCESS, }); } + + const pageName = yield select(getPageNameByPageId, response.data.pageId); + AnalyticsUtil.logEvent("DUPLICATE_API", { + apiName: response.data.name, + pageName: pageName, + apiID: response.data.id, + }); yield put(copyActionSuccess(response.data)); } catch (e) { AppToaster.show({ diff --git a/app/client/src/sagas/ApplicationSagas.tsx b/app/client/src/sagas/ApplicationSagas.tsx index bc9767d3e5..c8a9361beb 100644 --- a/app/client/src/sagas/ApplicationSagas.tsx +++ b/app/client/src/sagas/ApplicationSagas.tsx @@ -25,6 +25,7 @@ import history from "utils/history"; import { BUILDER_PAGE_URL } from "constants/routes"; import { AppState } from "reducers"; import { setDefaultApplicationPageSuccess } from "actions/applicationActions"; +import AnalyticsUtil from "utils/AnalyticsUtil"; export function* publishApplicationSaga( requestAction: ReduxAction, ) { @@ -85,6 +86,32 @@ export function* fetchApplicationListSaga() { } } +export function* fetchApplicationSaga( + action: ReduxAction<{ + applicationId: string; + }>, +) { + try { + const applicationId: string = action.payload.applicationId; + const response: FetchApplicationsResponse = yield call( + ApplicationApi.fetchApplication, + applicationId, + ); + + yield put({ + type: ReduxActionTypes.FETCH_APPLICATION_SUCCESS, + payload: response.data, + }); + } catch (error) { + yield put({ + type: ReduxActionErrorTypes.FETCH_APPLICATION_ERROR, + payload: { + error, + }, + }); + } +} + export function* setDefaultApplicationPageSaga( action: ReduxAction, ) { @@ -188,6 +215,9 @@ export function* createApplicationSaga( pageCount: response.data.pages ? response.data.pages.length : 0, defaultPageId: getDefaultPageId(response.data.pages), }; + AnalyticsUtil.logEvent("CREATE_APP", { + appName: application.name, + }); yield put({ type: ReduxActionTypes.CREATE_APPLICATION_SUCCESS, payload: application, @@ -224,6 +254,7 @@ export default function* applicationSagas() { ReduxActionTypes.FETCH_APPLICATION_LIST_INIT, fetchApplicationListSaga, ), + takeLatest(ReduxActionTypes.FETCH_APPLICATION_INIT, fetchApplicationSaga), takeLatest(ReduxActionTypes.CREATE_APPLICATION_INIT, createApplicationSaga), takeLatest( ReduxActionTypes.SET_DEFAULT_APPLICATION_PAGE_INIT, diff --git a/app/client/src/sagas/DatasourcesSagas.ts b/app/client/src/sagas/DatasourcesSagas.ts index 59e998b375..409d4cf27e 100644 --- a/app/client/src/sagas/DatasourcesSagas.ts +++ b/app/client/src/sagas/DatasourcesSagas.ts @@ -12,6 +12,7 @@ import DatasourcesApi, { } from "api/DatasourcesApi"; import { API_EDITOR_FORM_NAME } from "constants/forms"; import { validateResponse } from "./ErrorSagas"; +import AnalyticsUtil from "utils/AnalyticsUtil"; function* fetchDatasourcesSaga() { try { @@ -40,6 +41,10 @@ function* createDatasourceSaga( ); const isValidResponse = yield validateResponse(response); if (isValidResponse) { + AnalyticsUtil.logEvent("SAVE_DATA_SOURCE", { + dataSourceName: actionPayload.payload.name, + appName: actionPayload.payload.appName, + }); yield put({ type: ReduxActionTypes.CREATE_DATASOURCE_SUCCESS, payload: response.data, diff --git a/app/client/src/sagas/InitSagas.ts b/app/client/src/sagas/InitSagas.ts index ff7f6e9309..7464160114 100644 --- a/app/client/src/sagas/InitSagas.ts +++ b/app/client/src/sagas/InitSagas.ts @@ -1,4 +1,4 @@ -import { all, put, takeLatest, take } from "redux-saga/effects"; +import { all, put, takeLatest, take, select } from "redux-saga/effects"; import { ReduxAction, ReduxActionTypes, @@ -10,6 +10,9 @@ import { fetchPageList } from "actions/pageActions"; import { fetchDatasources } from "actions/datasourcesActions"; import { fetchPlugins } from "actions/pluginActions"; import { fetchActions } from "actions/actionActions"; +import { fetchApplication } from "actions/applicationActions"; +import { AppState } from "reducers"; +import AnalyticsUtil from "utils/AnalyticsUtil"; function* initializeEditorSaga( initializeEditorAction: ReduxAction, @@ -18,6 +21,7 @@ function* initializeEditorSaga( // Step 1: Start getting all the data needed by the yield all([ put(fetchPlugins()), + put(fetchApplication(applicationId)), put(fetchPageList(applicationId)), put(fetchEditorConfigs()), put(fetchActions(applicationId)), @@ -26,11 +30,25 @@ function* initializeEditorSaga( // Step 2: Wait for all data to be in the state yield all([ take(ReduxActionTypes.FETCH_PLUGINS_SUCCESS), + take(ReduxActionTypes.FETCH_APPLICATION_SUCCESS), take(ReduxActionTypes.FETCH_PAGE_LIST_SUCCESS), take(ReduxActionTypes.FETCH_ACTIONS_SUCCESS), take(ReduxActionTypes.FETCH_DATASOURCES_SUCCESS), ]); + const currentApplication = yield select( + (state: AppState) => state.ui.applications.currentApplication, + ); + + const appName = currentApplication ? currentApplication.name : ""; + const appId = currentApplication ? currentApplication.id : ""; + + AnalyticsUtil.setAppData(appId, appName); + AnalyticsUtil.logEvent("EDITOR_OPEN", { + appId: appId, + appName: appName, + }); + // Step 6: Notify UI that the editor is ready to go yield put({ type: ReduxActionTypes.INITIALIZE_EDITOR_SUCCESS, @@ -44,13 +62,27 @@ export function* initializeAppViewerSaga( yield all([ put(fetchActions(applicationId)), put(fetchPageList(applicationId)), + put(fetchApplication(applicationId)), ]); yield all([ take(ReduxActionTypes.FETCH_ACTIONS_SUCCESS), take(ReduxActionTypes.FETCH_PAGE_LIST_SUCCESS), + take(ReduxActionTypes.FETCH_APPLICATION_SUCCESS), ]); + const currentApplication = yield select( + (state: AppState) => state.ui.applications.currentApplication, + ); + + const appName = currentApplication ? currentApplication.name : ""; + const appId = currentApplication ? currentApplication.id : ""; + AnalyticsUtil.setAppData(appId, appName); + AnalyticsUtil.logEvent("PREVIEW_APP", { + appId: appId, + appName: appName, + }); + yield put({ type: ReduxActionTypes.INITIALIZE_PAGE_VIEWER_SUCCESS, }); diff --git a/app/client/src/selectors/applicationSelectors.tsx b/app/client/src/selectors/applicationSelectors.tsx index f3f0522596..7e4c10431a 100644 --- a/app/client/src/selectors/applicationSelectors.tsx +++ b/app/client/src/selectors/applicationSelectors.tsx @@ -15,6 +15,8 @@ const fuzzySearchOptions = { const getApplicationsState = (state: AppState) => state.ui.applications; const getApplications = (state: AppState) => state.ui.applications.applicationList; +export const getCurrentApplication = (state: AppState) => + state.ui.applications.currentApplication; const getApplicationSearchKeyword = (state: AppState) => state.ui.applications.searchKeyword; export const getIsDeletingApplication = (state: AppState) => diff --git a/app/client/src/utils/AnalyticsUtil.tsx b/app/client/src/utils/AnalyticsUtil.tsx index 7eca0e5e7f..2b810507c5 100644 --- a/app/client/src/utils/AnalyticsUtil.tsx +++ b/app/client/src/utils/AnalyticsUtil.tsx @@ -16,7 +16,25 @@ export type EventName = | "CREATE_PAGE" | "PAGE_RENAME" | "PAGE_SWITCH" - | "DELETE_PAGE"; + | "DELETE_PAGE" + | "SIDEBAR_NAVIGATION" + | "PUBLISH_APP" + | "PREVIEW_APP" + | "EDITOR_OPEN" + | "CREATE_API" + | "SAVE_API" + | "RUN_API" + | "DELETE_API" + | "DUPLICATE_API" + | "MOVE_API" + | "API_SELECT" + | "CREATE_API_CLICK" + | "AUTO_COMPELTE_SHOW" + | "AUTO_COMPLETE_SELECT" + | "CREATE_APP_CLICK" + | "CREATE_APP" + | "CREATE_DATA_SOURCE_CLICK" + | "SAVE_DATA_SOURCE"; export type Gender = "MALE" | "FEMALE"; export interface User { @@ -27,6 +45,14 @@ export interface User { } class AnalyticsUtil { + static user: any = {}; + static appData: { + appId: string; + appName: string; + } = { + appId: "", + appName: "", + }; static initializeHotjar(id: string, sv: string) { (function init(h: any, o: any, t: any, j: any, a?: any, r?: any) { h.hj = @@ -105,19 +131,40 @@ class AnalyticsUtil { static logEvent(eventName: EventName, eventData: any) { const windowDoc: any = window; + let finalEventData = eventData; + const userData = AnalyticsUtil.user; + const appData = AnalyticsUtil.appData; + + if (userData) { + finalEventData = { + ...finalEventData, + userData: { + email: userData.email, + currentOrgId: userData.currentOrganizationId, + ...appData, + }, + }; + } if (windowDoc.analytics) { - windowDoc.analytics.track(eventName, eventData); + windowDoc.analytics.track(eventName, finalEventData); } else { - console.log("Event fired", eventName, eventData); + console.log("Event fired", eventName, finalEventData); } } static identifyUser(userId: string, userData: User) { const windowDoc: any = window; + AnalyticsUtil.user = userData; if (windowDoc.analytics) { windowDoc.analytics.identify(userId, userData); } } + static setAppData(appId: string, appName: string) { + AnalyticsUtil.appData = { + appId: appId, + appName: appName, + }; + } } export default AnalyticsUtil;