From 885de0466bc1167a92e622fd109757c1e72b9a68 Mon Sep 17 00:00:00 2001 From: sneha122 Date: Fri, 12 May 2023 20:34:38 +0530 Subject: [PATCH] chore: analytic events added for gsheet (#23171) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description This PR adds: - Analytics events for google sheet datasource. #### PR fixes following issue(s) Fixes #22805 > if no issue exists, please create an issue and ask the maintainers about this first > > #### Media > A video or a GIF is preferred. when using Loom, don’t embed because it looks like it’s a GIF. instead, just link to the video > > #### Type of change - Chore (housekeeping or task changes that don't impact user perception) > > > ## Testing > #### How Has This Been Tested? > Please describe the tests that you ran to verify your changes. Also list any relevant details for your test configuration. > Delete anything that is not relevant - [ ] Manual - [ ] Jest - [ ] Cypress > > #### Test Plan > Add Testsmith test cases links that relate to this PR > > #### Issues raised during DP testing > Link issues raised during DP testing for better visiblity and tracking (copy link from comments dropped on this PR) > > > ## Checklist: #### Dev activity - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] PR is being merged under a feature flag #### QA activity: - [ ] [Speedbreak features](https://github.com/appsmithorg/TestSmith/wiki/Test-plan-implementation#speedbreaker-features-to-consider-for-every-change) have been covered - [ ] Test plan covers all impacted features and [areas of interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans/_edit#areas-of-interest) - [ ] Test plan has been peer reviewed by project stakeholders and other QA members - [ ] Manually tested functionality on DP - [ ] We had an implementation alignment call with stakeholders post QA Round 2 - [ ] Cypress test cases have been added and approved by SDET/manual QA - [ ] Added `Test Plan Approved` label after Cypress tests were reviewed - [ ] Added `Test Plan Approved` label after JUnit tests were reviewed --------- Co-authored-by: “sneha122” <“sneha@appsmith.com”> --- app/client/src/ce/constants/messages.ts | 1 + .../Editor/SaaSEditor/DatasourceForm.tsx | 32 ++++++++++++------- .../gitSync/ReconnectDatasourceModal.tsx | 17 +++++----- .../src/pages/common/datasourceAuth/index.tsx | 23 ++++++++----- app/client/src/sagas/DatasourcesSagas.ts | 19 +++++++++++ app/client/src/selectors/entitiesSelector.ts | 26 +++++++++++++++ app/client/src/utils/AnalyticsUtil.tsx | 2 ++ 7 files changed, 93 insertions(+), 27 deletions(-) diff --git a/app/client/src/ce/constants/messages.ts b/app/client/src/ce/constants/messages.ts index cf1c9f6df5..bf4e1c7a7c 100644 --- a/app/client/src/ce/constants/messages.ts +++ b/app/client/src/ce/constants/messages.ts @@ -366,6 +366,7 @@ export const GSHEET_AUTHORIZATION_ERROR = "Authorisation failed, to continue using this data source authorize now."; export const GSHEET_FILES_NOT_SELECTED = "Datasource does not have access to any files, please authorize google sheets to use this data source"; +export const FILES_NOT_SELECTED_EVENT = () => "Files not selected"; export const LOCAL_STORAGE_QUOTA_EXCEEDED_MESSAGE = () => "Error saving a key in localStorage. You have exceeded the allowed storage size limit"; diff --git a/app/client/src/pages/Editor/SaaSEditor/DatasourceForm.tsx b/app/client/src/pages/Editor/SaaSEditor/DatasourceForm.tsx index 75214908dc..1302bd5490 100644 --- a/app/client/src/pages/Editor/SaaSEditor/DatasourceForm.tsx +++ b/app/client/src/pages/Editor/SaaSEditor/DatasourceForm.tsx @@ -16,6 +16,7 @@ import { getDatasourceFormButtonConfig, getPlugin, getPluginDocumentationLinks, + getDatasourceScopeValue, } from "selectors/entitiesSelector"; import type { ActionDataState } from "reducers/entityReducers/actionsReducer"; import type { JSONtoFormProps } from "../DataSourceEditor/JSONtoForm"; @@ -97,6 +98,7 @@ interface StateProps extends JSONtoFormProps { gsheetToken?: string; gsheetProjectID?: string; documentationLink: string | undefined; + scopeValue?: string; } interface DatasourceFormFunctions { discardTempDatasource: () => void; @@ -286,6 +288,7 @@ class DatasourceSaaSEditor extends JSONtoForm { pageId, plugin, pluginPackageName, + scopeValue, } = this.props; const params: string = location.search; const viewMode = @@ -426,17 +429,15 @@ class DatasourceSaaSEditor extends JSONtoForm { } showDatasourceSavedText={!isGoogleSheetPlugin} /> -
- {!_.isNil(formConfig) && - !_.isNil(datasource) && - !hideDatasourceSection ? ( - - ) : undefined} -
+ {!_.isNil(formConfig) && + !_.isNil(datasource) && + !hideDatasourceSection ? ( + + ) : undefined} )} {/* Render datasource form call-to-actions */} @@ -449,6 +450,7 @@ class DatasourceSaaSEditor extends JSONtoForm { getSanitizedFormData={_.memoize(this.getSanitizedData)} isInvalid={this.validate()} pageId={pageId} + scopeValue={scopeValue} shouldDisplayAuthMessage={!isGoogleSheetPlugin} shouldRender={!viewMode} triggerSave={this.props.isDatasourceBeingSavedFromPopup} @@ -496,6 +498,13 @@ const mapStateToProps = (state: AppState, props: any) => { merge(initialValues, datasource); + // get scopeValue to be shown in analytical events + const scopeValue = getDatasourceScopeValue( + state, + datasourceId, + DATASOURCE_SAAS_FORM, + ); + const datasourceButtonConfiguration = getDatasourceFormButtonConfig( state, formData?.pluginId, @@ -551,6 +560,7 @@ const mapStateToProps = (state: AppState, props: any) => { canCreateDatasourceActions, gsheetToken, gsheetProjectID, + scopeValue, }; }; diff --git a/app/client/src/pages/Editor/gitSync/ReconnectDatasourceModal.tsx b/app/client/src/pages/Editor/gitSync/ReconnectDatasourceModal.tsx index a2edaa6df4..c2f5b8c262 100644 --- a/app/client/src/pages/Editor/gitSync/ReconnectDatasourceModal.tsx +++ b/app/client/src/pages/Editor/gitSync/ReconnectDatasourceModal.tsx @@ -335,20 +335,21 @@ function ReconnectDatasourceModal() { const status = queryParams.get("response_status"); const display_message = queryParams.get("display_message"); const variant = Variant.danger; - + const oauthReason = status; + const isReconnectDS = true; + AnalyticsUtil.logEvent("DATASOURCE_AUTHORIZE_RESULT", { + dsName, + oauthReason, + orgId, + pluginName, + isReconnectDS, + }); if (status !== AuthorizationStatus.SUCCESS) { const message = status === AuthorizationStatus.APPSMITH_ERROR ? OAUTH_AUTHORIZATION_APPSMITH_ERROR : OAUTH_AUTHORIZATION_FAILED; Toaster.show({ text: display_message || message, variant }); - const oAuthStatus = status; - AnalyticsUtil.logEvent("UPDATE_DATASOURCE", { - dsName, - oAuthStatus, - orgId, - pluginName, - }); } else if (queryDatasourceId) { dispatch(loadFilePickerAction()); dispatch(getOAuthAccessToken(queryDatasourceId)); diff --git a/app/client/src/pages/common/datasourceAuth/index.tsx b/app/client/src/pages/common/datasourceAuth/index.tsx index 1ac0986cb4..8607ceda52 100644 --- a/app/client/src/pages/common/datasourceAuth/index.tsx +++ b/app/client/src/pages/common/datasourceAuth/index.tsx @@ -58,6 +58,7 @@ interface Props { triggerSave?: boolean; isFormDirty?: boolean; datasourceDeleteTrigger: () => void; + scopeValue?: string; } export type DatasourceFormButtonTypes = Record; @@ -121,6 +122,7 @@ function DatasourceAuth({ shouldDisplayAuthMessage = true, triggerSave, isFormDirty, + scopeValue, }: Props) { const authType = formData && "authType" in formData @@ -181,20 +183,19 @@ function DatasourceAuth({ if (status && shouldNotify) { const display_message = search.get("display_message"); const variant = Variant.danger; - + const oauthReason = status; + AnalyticsUtil.logEvent("DATASOURCE_AUTHORIZE_RESULT", { + dsName, + oauthReason, + orgId, + pluginName, + }); if (status !== AuthorizationStatus.SUCCESS) { const message = status === AuthorizationStatus.APPSMITH_ERROR ? OAUTH_AUTHORIZATION_APPSMITH_ERROR : OAUTH_AUTHORIZATION_FAILED; Toaster.show({ text: display_message || message, variant }); - const oAuthStatus = status; - AnalyticsUtil.logEvent("UPDATE_DATASOURCE", { - dsName, - oAuthStatus, - orgId, - pluginName, - }); } else { dispatch(getOAuthAccessToken(datasourceId)); } @@ -295,6 +296,12 @@ function DatasourceAuth({ ), ); } + AnalyticsUtil.logEvent("DATASOURCE_AUTHORIZE_CLICK", { + dsName, + orgId, + pluginName, + scopeValue, + }); }; const createMode = datasourceId === TEMP_DATASOURCE_ID; diff --git a/app/client/src/sagas/DatasourcesSagas.ts b/app/client/src/sagas/DatasourcesSagas.ts index 3d6e1eb20b..451375604d 100644 --- a/app/client/src/sagas/DatasourcesSagas.ts +++ b/app/client/src/sagas/DatasourcesSagas.ts @@ -40,6 +40,7 @@ import { getDatasourceActionRouteInfo, getPlugin, getEditorConfig, + getPluginNameFromId, } from "selectors/entitiesSelector"; import type { UpdateDatasourceSuccessAction, @@ -88,6 +89,7 @@ import { DATASOURCE_DELETE, DATASOURCE_UPDATE, DATASOURCE_VALID, + FILES_NOT_SELECTED_EVENT, GSHEET_AUTHORISED_FILE_IDS_KEY, OAUTH_APPSMITH_TOKEN_NOT_FOUND, OAUTH_AUTHORIZATION_APPSMITH_ERROR, @@ -1232,6 +1234,23 @@ function* filePickerActionCallbackSaga( // Once files are selected in case of import, set this flag set(datasource, "isConfigured", true); + // event in case files are not selected + if (action === FilePickerActionStatus.CANCEL) { + const oauthReason = createMessage(FILES_NOT_SELECTED_EVENT); + const dsName = datasource?.name; + const orgId = datasource?.workspaceId; + const pluginName: string = yield select( + getPluginNameFromId, + datasource?.pluginId, + ); + AnalyticsUtil.logEvent("DATASOURCE_AUTHORIZE_RESULT", { + dsName, + oauthReason, + orgId, + pluginName, + }); + } + // Once users selects/cancels the file selection, // Sending sheet ids selected as part of datasource // config properties in order to save it in database diff --git a/app/client/src/selectors/entitiesSelector.ts b/app/client/src/selectors/entitiesSelector.ts index c7c70225db..102b0cb684 100644 --- a/app/client/src/selectors/entitiesSelector.ts +++ b/app/client/src/selectors/entitiesSelector.ts @@ -41,6 +41,7 @@ import { InstallState } from "reducers/uiReducers/libraryReducer"; import recommendedLibraries from "pages/Editor/Explorer/Libraries/recommendedLibraries"; import type { TJSLibrary } from "workers/common/JSLibrary"; import { getEntityNameAndPropertyPath } from "@appsmith/workers/Evaluation/evaluationUtils"; +import { getFormValues } from "redux-form"; export const getEntities = (state: AppState): AppState["entities"] => state.entities; @@ -1037,3 +1038,28 @@ export const getAllDatasourceTableKeys = createSelector( return tables; }, ); + +export const getDatasourceScopeValue = ( + state: AppState, + datasourceId: string, + formName: string, +) => { + const formData = getFormValues(formName)(state) as Datasource; + const { plugins } = state.entities; + const { formConfigs } = plugins; + const datasource = getDatasource(state, datasourceId); + const pluginId = get(datasource, "pluginId", ""); + const formConfig = formConfigs[pluginId]; + if (!formConfig || (!!formConfig && formConfig.length === 0)) { + return null; + } + const configProperty = "datasourceConfiguration.authentication.scopeString"; + const scopeValue = get(formData, configProperty); + const options = formConfig[0]?.children?.find( + (child: any) => child?.configProperty === configProperty, + )?.options; + const label = options?.find( + (option: any) => option.value === scopeValue, + )?.label; + return label; +}; diff --git a/app/client/src/utils/AnalyticsUtil.tsx b/app/client/src/utils/AnalyticsUtil.tsx index 4eaafc438a..46510bb57d 100644 --- a/app/client/src/utils/AnalyticsUtil.tsx +++ b/app/client/src/utils/AnalyticsUtil.tsx @@ -299,6 +299,8 @@ export type EventName = | "CONVERSION_FAILURE" | "CONVERT_AUTO_TO_FIXED" | "CONVERT_FIXED_TO_AUTO" + | "DATASOURCE_AUTHORIZE_CLICK" + | "DATASOURCE_AUTHORIZE_RESULT" | AI_EVENTS; export type AI_EVENTS =