From 0253eff2ec3e4a26488d653e46138360ea8dd8a7 Mon Sep 17 00:00:00 2001 From: Malcolm Bouzi Date: Wed, 11 Dec 2019 15:07:15 -0500 Subject: [PATCH] web/satellite: add tracking event for segment.io (#3641) --- web/satellite/package-lock.json | 3 ++- web/satellite/package.json | 2 +- .../components/account/DeleteAccountPopup.vue | 9 +++++-- .../billing/billingHistory/BillingHistory.vue | 8 ++++++ .../billing/paymentMethods/PaymentMethods.vue | 14 ++++++++++ .../src/components/apiKeys/ApiKeysArea.vue | 8 ++++++ .../apiKeys/ApiKeysCreationPopup.vue | 5 ++++ .../components/project/DeleteProjectPopup.vue | 11 +++++--- .../components/project/NewProjectPopup.vue | 4 +++ .../src/components/project/ProjectDetails.vue | 4 +++ .../src/components/project/UsageReport.vue | 27 +++++++++++++++++++ .../src/components/team/AddUserPopup.vue | 6 +++++ .../components/team/ProjectMembersArea.vue | 5 ++++ .../utils/constants/analyticsEventNames.ts | 19 ++++++++++--- web/satellite/src/utils/plugins/segment.ts | 6 ++--- .../views/forgotPassword/ForgotPassword.vue | 5 ++-- web/satellite/src/views/login/LoginArea.vue | 4 +++ .../src/views/register/RegisterArea.vue | 6 +++++ .../tests/unit/apiKeys/ApiKeysArea.spec.ts | 3 +++ .../unit/apiKeys/ApiKeysCreationPopup.spec.ts | 3 +++ .../projectMembers/ProjectMembersArea.spec.ts | 3 +++ 21 files changed, 139 insertions(+), 16 deletions(-) diff --git a/web/satellite/package-lock.json b/web/satellite/package-lock.json index 51f031e58..0d4dde516 100644 --- a/web/satellite/package-lock.json +++ b/web/satellite/package-lock.json @@ -1073,7 +1073,8 @@ "@types/segment-analytics": { "version": "0.0.32", "resolved": "https://registry.npmjs.org/@types/segment-analytics/-/segment-analytics-0.0.32.tgz", - "integrity": "sha512-p0SHnfHfZwemTeaISvu8WUXtw81A4AOU4GEr7B9S9jLx2iu/z/4aLT0flRJtHVusVAlMeJSdobn0uD/oR1sFEw==" + "integrity": "sha512-p0SHnfHfZwemTeaISvu8WUXtw81A4AOU4GEr7B9S9jLx2iu/z/4aLT0flRJtHVusVAlMeJSdobn0uD/oR1sFEw==", + "dev": true }, "@types/sinon": { "version": "7.0.13", diff --git a/web/satellite/package.json b/web/satellite/package.json index 999657a00..16baf365a 100644 --- a/web/satellite/package.json +++ b/web/satellite/package.json @@ -10,7 +10,6 @@ "dev": "vue-cli-service build --mode development" }, "dependencies": { - "@types/segment-analytics": "0.0.32", "apollo-cache-inmemory": "1.6.3", "apollo-client": "2.6.4", "apollo-link": "1.2.12", @@ -37,6 +36,7 @@ "@vue/cli-plugin-typescript": "3.11.0", "@vue/cli-plugin-unit-jest": "3.11.0", "@vue/cli-service": "3.11.0", + "@types/segment-analytics": "0.0.32", "@vue/test-utils": "1.0.0-beta.29", "babel-core": "7.0.0-bridge.0", "compression-webpack-plugin": "3.0.0", diff --git a/web/satellite/src/components/account/DeleteAccountPopup.vue b/web/satellite/src/components/account/DeleteAccountPopup.vue index 81f2e7e7b..b9a5583be 100644 --- a/web/satellite/src/components/account/DeleteAccountPopup.vue +++ b/web/satellite/src/components/account/DeleteAccountPopup.vue @@ -10,8 +10,8 @@
- { await this.$refs.stripeCardInput.onSubmit(); + this.$segment.track(SegmentEvent.PAYMENT_METHOD_ADDED, { + project_id: this.$store.getters.selectedProject.id, + }); } public async addCard(token: string) { @@ -210,6 +221,9 @@ export default class PaymentMethods extends Vue { } await this.$store.dispatch(NOTIFICATION_ACTIONS.SUCCESS, 'Card successfully added'); + this.$segment.track(SegmentEvent.PAYMENT_METHOD_ADDED, { + project_id: this.$store.getters.selectedProject.id, + }); try { await this.$store.dispatch(GET_CREDIT_CARDS); } catch (error) { diff --git a/web/satellite/src/components/apiKeys/ApiKeysArea.vue b/web/satellite/src/components/apiKeys/ApiKeysArea.vue index b85a299e3..044af4938 100644 --- a/web/satellite/src/components/apiKeys/ApiKeysArea.vue +++ b/web/satellite/src/components/apiKeys/ApiKeysArea.vue @@ -123,6 +123,7 @@ import EmptySearchResultIcon from '@/../static/images/common/emptySearchResult.s import { ApiKey, ApiKeyOrderBy } from '@/types/apiKeys'; import { SortDirection } from '@/types/common'; import { API_KEYS_ACTIONS } from '@/utils/constants/actionNames'; +import { SegmentEvent } from '@/utils/constants/analyticsEventNames'; import { EMPTY_STATE_IMAGES } from '@/utils/constants/emptyStatesImages'; import ApiKeysCopyPopup from './ApiKeysCopyPopup.vue'; @@ -179,6 +180,10 @@ export default class ApiKeysArea extends Vue { public async mounted(): Promise { await this.$store.dispatch(FETCH, 1); + this.$segment.track(SegmentEvent.API_KEYS_VIEWED, { + project_id: this.$store.getters.selectedProject.id, + api_keys_count: this.selectedAPIKeysCount, + }); } public async beforeDestroy(): Promise { @@ -220,6 +225,9 @@ export default class ApiKeysArea extends Vue { try { await this.$store.dispatch(DELETE); await this.$notify.success(`API keys deleted successfully`); + this.$segment.track(SegmentEvent.API_KEY_DELETED, { + project_id: this.$store.getters.selectedProject.id, + }); } catch (error) { await this.$notify.error(error.message); } diff --git a/web/satellite/src/components/apiKeys/ApiKeysCreationPopup.vue b/web/satellite/src/components/apiKeys/ApiKeysCreationPopup.vue index 9f8a72d5f..a176afede 100644 --- a/web/satellite/src/components/apiKeys/ApiKeysCreationPopup.vue +++ b/web/satellite/src/components/apiKeys/ApiKeysCreationPopup.vue @@ -35,6 +35,7 @@ import CloseCrossIcon from '@/../static/images/common/closeCross.svg'; import { ApiKey } from '@/types/apiKeys'; import { API_KEYS_ACTIONS } from '@/utils/constants/actionNames'; +import { SegmentEvent } from '@/utils/constants/analyticsEventNames'; const CREATE = API_KEYS_ACTIONS.CREATE; @@ -95,6 +96,10 @@ export default class ApiKeysCreationPopup extends Vue { this.isLoading = false; this.name = ''; + this.$segment.track(SegmentEvent.API_KEY_CREATED, { + project_id: this.$store.getters.selectedProject.id, + }); + try { await this.$store.dispatch(API_KEYS_ACTIONS.FETCH, this.FIRST_PAGE); } catch (error) { diff --git a/web/satellite/src/components/project/DeleteProjectPopup.vue b/web/satellite/src/components/project/DeleteProjectPopup.vue index bdb693b7e..1de92b73d 100644 --- a/web/satellite/src/components/project/DeleteProjectPopup.vue +++ b/web/satellite/src/components/project/DeleteProjectPopup.vue @@ -18,7 +18,7 @@
{ try { await this.$store.dispatch(PROJECTS_ACTIONS.FETCH); + this.$segment.track(SegmentEvent.PROJECT_VIEWED, { + project_id: this.$store.getters.selectedProject.id, + }); } catch (error) { await this.$notify.error(error.message); } diff --git a/web/satellite/src/components/project/UsageReport.vue b/web/satellite/src/components/project/UsageReport.vue index bd4e5967d..4d01393a4 100644 --- a/web/satellite/src/components/project/UsageReport.vue +++ b/web/satellite/src/components/project/UsageReport.vue @@ -66,6 +66,7 @@ import DownloadReportIcon from '@/../static/images/project/downloadReport.svg'; import { RouteConfig } from '@/router'; import { PROJECT_USAGE_ACTIONS } from '@/store/modules/usage'; import { DateRange } from '@/types/usage'; +import { SegmentEvent } from '@/utils/constants/analyticsEventNames'; import { DateFormat } from '@/utils/datepicker'; import { toUnixTimestamp } from '@/utils/time'; @@ -126,6 +127,11 @@ export default class UsageReport extends Vue { public async mounted(): Promise { try { + this.$segment.track(SegmentEvent.REPORT_VIEWED, { + project_id: this.$store.getters.selectedProject.id, + start_date: this.dateRange.startDate, + end_date: this.dateRange.endDate, + }); await this.$store.dispatch(PROJECT_USAGE_ACTIONS.FETCH_CURRENT_ROLLUP); } catch (error) { await this.$notify.error(`Unable to fetch project usage. ${error.message}`); @@ -156,6 +162,11 @@ export default class UsageReport extends Vue { this.onButtonClickAction(event); try { + this.$segment.track(SegmentEvent.REPORT_VIEWED, { + project_id: this.$store.getters.selectedProject.id, + start_date: this.dateRange.startDate, + end_date: this.dateRange.endDate, + }); await this.$store.dispatch(PROJECT_USAGE_ACTIONS.FETCH_CURRENT_ROLLUP); } catch (error) { await this.$notify.error(`Unable to fetch project usage. ${error.message}`); @@ -166,6 +177,11 @@ export default class UsageReport extends Vue { this.onButtonClickAction(event); try { + this.$segment.track(SegmentEvent.REPORT_VIEWED, { + project_id: this.$store.getters.selectedProject.id, + start_date: this.dateRange.startDate, + end_date: this.dateRange.endDate, + }); await this.$store.dispatch(PROJECT_USAGE_ACTIONS.FETCH_PREVIOUS_ROLLUP); } catch (error) { await this.$notify.error(`Unable to fetch project usage. ${error.message}`); @@ -175,6 +191,11 @@ export default class UsageReport extends Vue { public onCustomDateClick(event: any): void { (this as any).$refs.datePicker.showCheck(); this.onButtonClickAction(event); + this.$segment.track(SegmentEvent.REPORT_VIEWED, { + project_id: this.$store.getters.selectedProject.id, + start_date: this.dateRange.startDate, + end_date: this.dateRange.endDate, + }); } public onReportClick(): void { @@ -189,6 +210,12 @@ export default class UsageReport extends Vue { url.searchParams.append('since', toUnixTimestamp(startDate).toString()); url.searchParams.append('before', toUnixTimestamp(endDate).toString()); + this.$segment.track(SegmentEvent.REPORT_DOWNLOADED, { + start_date: startDate, + end_date: endDate, + project_id: projectID, + }); + window.open(url.href, '_blank'); } diff --git a/web/satellite/src/components/team/AddUserPopup.vue b/web/satellite/src/components/team/AddUserPopup.vue index 5902089f2..d3452898c 100644 --- a/web/satellite/src/components/team/AddUserPopup.vue +++ b/web/satellite/src/components/team/AddUserPopup.vue @@ -86,6 +86,7 @@ import DeleteFieldIcon from '@/../static/images/team/deleteField.svg'; import { RouteConfig } from '@/router'; import { EmailInput } from '@/types/EmailInput'; import { APP_STATE_ACTIONS, PM_ACTIONS } from '@/utils/constants/actionNames'; +import { SegmentEvent } from '@/utils/constants/analyticsEventNames'; import { validateEmail } from '@/utils/validation'; @Component({ @@ -173,6 +174,11 @@ export default class AddUserPopup extends Vue { return; } + this.$segment.track(SegmentEvent.TEAM_MEMBER_INVITED, { + project_id: this.$store.getters.selectedProject.id, + invited_emails: emailArray, + }); + await this.$notify.success('Members successfully added to project!'); this.$store.dispatch(PM_ACTIONS.SET_SEARCH_QUERY, ''); diff --git a/web/satellite/src/components/team/ProjectMembersArea.vue b/web/satellite/src/components/team/ProjectMembersArea.vue index b9e36883d..8c48724ac 100644 --- a/web/satellite/src/components/team/ProjectMembersArea.vue +++ b/web/satellite/src/components/team/ProjectMembersArea.vue @@ -48,6 +48,7 @@ import EmptySearchResultIcon from '@/../static/images/common/emptySearchResult.s import { SortDirection } from '@/types/common'; import { ProjectMember, ProjectMemberHeaderState, ProjectMemberOrderBy } from '@/types/projectMembers'; import { PM_ACTIONS } from '@/utils/constants/actionNames'; +import { SegmentEvent } from '@/utils/constants/analyticsEventNames'; const { FETCH, @@ -82,6 +83,10 @@ export default class ProjectMembersArea extends Vue { public async mounted(): Promise { await this.$store.dispatch(FETCH, 1); + this.$segment.track(SegmentEvent.TEAM_VIEWED, { + project_id: this.$store.getters.selectedProject.id, + team_member_count: this.projectMembersTotalCount, + }); } public onMemberClick(member: ProjectMember): void { diff --git a/web/satellite/src/utils/constants/analyticsEventNames.ts b/web/satellite/src/utils/constants/analyticsEventNames.ts index 83963519b..dffe80e78 100644 --- a/web/satellite/src/utils/constants/analyticsEventNames.ts +++ b/web/satellite/src/utils/constants/analyticsEventNames.ts @@ -2,7 +2,20 @@ // See LICENSE for copying information. export enum SegmentEvent { - CLICKED_LOGO = 'clicked-on-logo', - CLICKED_LOGIN = 'clicked-on-login', - CLICKED_BACK_TO_LOGIN = 'clicked-on-back-to-login', + PROJECT_CREATED= 'Project Created', + PROJECT_DELETED= 'Project Deleted', + PROJECT_VIEWED= 'Project Viewed', + USER_DELETED= 'User Deleted', + USER_LOGGED_IN= 'User Logged In', + EMAIL_VERIFIED= 'Email Verified', + API_KEY_CREATED= 'API Key Created', + API_KEY_DELETED= 'API Key Deleted', + API_KEYS_VIEWED= 'API Key Viewed', + PAYMENT_METHODS_VIEWED= 'Payment Methods Viewed', + PAYMENT_METHOD_ADDED= 'Payment Method Added', + REPORT_DOWNLOADED= 'Report Downloaded', + REPORT_VIEWED= 'Report Viewed', + BILLING_HISTORY_VIEWED= 'Billing History Viewed', + TEAM_MEMBER_INVITED= 'Team Member Invited', + TEAM_VIEWED= 'Team Viewed', } diff --git a/web/satellite/src/utils/plugins/segment.ts b/web/satellite/src/utils/plugins/segment.ts index 8ef9ba38e..4abe3d388 100644 --- a/web/satellite/src/utils/plugins/segment.ts +++ b/web/satellite/src/utils/plugins/segment.ts @@ -9,7 +9,7 @@ import { SegmentEvent } from '@/utils/constants/analyticsEventNames'; * Segmentio is a wrapper around segment.io analytics package */ export class Segmentio { - analytics: SegmentAnalytics.AnalyticsJS; + private analytics: SegmentAnalytics.AnalyticsJS; public init(key: string) { if (this.analytics || key.length === 0 || key.includes('SegmentIOPublicKey')) { return; @@ -34,12 +34,12 @@ export class Segmentio { this.analytics.page(); } - public identify() { + public identify(userId: string, traits?: Object, options?: SegmentAnalytics.SegmentOpts, callback?: () => void) { if (!this.analytics) { return; } - this.analytics.identify(); + this.analytics.identify(userId, traits, options, callback); } public track(event: SegmentEvent, properties?: Object, options?: SegmentAnalytics.SegmentOpts, callback?: () => void) { diff --git a/web/satellite/src/views/forgotPassword/ForgotPassword.vue b/web/satellite/src/views/forgotPassword/ForgotPassword.vue index 44cc08b7b..3e647bc8d 100644 --- a/web/satellite/src/views/forgotPassword/ForgotPassword.vue +++ b/web/satellite/src/views/forgotPassword/ForgotPassword.vue @@ -13,6 +13,7 @@ import LogoIcon from '@/../static/images/Logo.svg'; import { AuthHttpApi } from '@/api/auth'; import { RouteConfig } from '@/router'; +import { SegmentEvent } from '@/utils/constants/analyticsEventNames'; import { LOADING_CLASSES } from '@/utils/constants/classConstants'; import { validateEmail } from '@/utils/validation'; @@ -51,11 +52,11 @@ export default class ForgotPassword extends Vue { } public onBackToLoginClick(): void { - this.$router.push(RouteConfig.Login.path); + this.$router.push(RouteConfig.Login.path); } public onLogoClick(): void { - location.reload(); + location.reload(); } private validateFields(): boolean { diff --git a/web/satellite/src/views/login/LoginArea.vue b/web/satellite/src/views/login/LoginArea.vue index 4c5b5e5c2..e595540b3 100644 --- a/web/satellite/src/views/login/LoginArea.vue +++ b/web/satellite/src/views/login/LoginArea.vue @@ -16,6 +16,7 @@ import { AuthHttpApi } from '@/api/auth'; import { RouteConfig } from '@/router'; import { AuthToken } from '@/utils/authToken'; import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames'; +import { SegmentEvent } from '@/utils/constants/analyticsEventNames'; import { AppState } from '@/utils/constants/appStateEnum'; import { LOADING_CLASSES } from '@/utils/constants/classConstants'; import { validateEmail, validatePassword } from '@/utils/validation'; @@ -78,6 +79,9 @@ export default class Login extends Vue { try { this.authToken = await this.auth.token(this.email, this.password); AuthToken.set(this.authToken); + this.$segment.track(SegmentEvent.USER_LOGGED_IN, { + email: this.email, + }); } catch (error) { await this.$notify.error(error.message); this.isLoading = false; diff --git a/web/satellite/src/views/register/RegisterArea.vue b/web/satellite/src/views/register/RegisterArea.vue index 00e1d6ec4..b19de8d97 100644 --- a/web/satellite/src/views/register/RegisterArea.vue +++ b/web/satellite/src/views/register/RegisterArea.vue @@ -17,6 +17,7 @@ import { AuthHttpApi } from '@/api/auth'; import { RouteConfig } from '@/router'; import { User } from '@/types/users'; import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames'; +import { SegmentEvent } from '@/utils/constants/analyticsEventNames'; import { LOADING_CLASSES } from '@/utils/constants/classConstants'; import { LocalData } from '@/utils/localData'; import { validateEmail, validatePassword } from '@/utils/validation'; @@ -176,6 +177,11 @@ export default class RegisterArea extends Vue { LocalData.setUserId(this.userId); + this.$segment.identify(this.userId, { + email: this.$store.getters.user.email, + referralToken: this.referralToken, + }); + // TODO: improve it this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_SUCCESSFUL_REGISTRATION_POPUP); const registrationSuccessPopupRef = this.$refs['register_success_popup']; diff --git a/web/satellite/tests/unit/apiKeys/ApiKeysArea.spec.ts b/web/satellite/tests/unit/apiKeys/ApiKeysArea.spec.ts index 57804aa23..5c0c545f2 100644 --- a/web/satellite/tests/unit/apiKeys/ApiKeysArea.spec.ts +++ b/web/satellite/tests/unit/apiKeys/ApiKeysArea.spec.ts @@ -10,13 +10,16 @@ import { makeNotificationsModule } from '@/store/modules/notifications'; import { makeProjectsModule } from '@/store/modules/projects'; import { ApiKey, ApiKeysPage } from '@/types/apiKeys'; import { Project } from '@/types/projects'; +import { SegmentioPlugin } from '@/utils/plugins/segment'; import { createLocalVue, shallowMount } from '@vue/test-utils'; import { ApiKeysMock } from '../mock/api/apiKeys'; import { ProjectsApiMock } from '../mock/api/projects'; const localVue = createLocalVue(); +const segmentioPlugin = new SegmentioPlugin(); localVue.use(Vuex); +localVue.use(segmentioPlugin); const apiKeysApi = new ApiKeysMock(); const apiKeysModule = makeApiKeysModule(apiKeysApi); diff --git a/web/satellite/tests/unit/apiKeys/ApiKeysCreationPopup.spec.ts b/web/satellite/tests/unit/apiKeys/ApiKeysCreationPopup.spec.ts index 93973429f..5199dec03 100644 --- a/web/satellite/tests/unit/apiKeys/ApiKeysCreationPopup.spec.ts +++ b/web/satellite/tests/unit/apiKeys/ApiKeysCreationPopup.spec.ts @@ -14,12 +14,15 @@ import { ApiKey } from '@/types/apiKeys'; import { Project } from '@/types/projects'; import { API_KEYS_ACTIONS } from '@/utils/constants/actionNames'; import { NotificatorPlugin } from '@/utils/plugins/notificator'; +import { SegmentioPlugin } from '@/utils/plugins/segment'; import { createLocalVue, mount } from '@vue/test-utils'; const localVue = createLocalVue(); localVue.use(Vuex); const notificationPlugin = new NotificatorPlugin(); +const segmentioPlugin = new SegmentioPlugin(); localVue.use(notificationPlugin); +localVue.use(segmentioPlugin); const apiKeysApi = new ApiKeysApiGql(); const apiKeysModule = makeApiKeysModule(apiKeysApi); const projectsApi = new ProjectsApiGql(); diff --git a/web/satellite/tests/unit/projectMembers/ProjectMembersArea.spec.ts b/web/satellite/tests/unit/projectMembers/ProjectMembersArea.spec.ts index fe858ae38..cd24367bb 100644 --- a/web/satellite/tests/unit/projectMembers/ProjectMembersArea.spec.ts +++ b/web/satellite/tests/unit/projectMembers/ProjectMembersArea.spec.ts @@ -10,13 +10,16 @@ import { makeProjectMembersModule, PROJECT_MEMBER_MUTATIONS } from '@/store/modu import { makeProjectsModule } from '@/store/modules/projects'; import { ProjectMember, ProjectMembersPage } from '@/types/projectMembers'; import { Project } from '@/types/projects'; +import { SegmentioPlugin } from '@/utils/plugins/segment'; import { createLocalVue, shallowMount } from '@vue/test-utils'; import { ProjectMembersApiMock } from '../mock/api/projectMembers'; import { ProjectsApiMock } from '../mock/api/projects'; const localVue = createLocalVue(); +const segmentioPlugin = new SegmentioPlugin(); localVue.use(Vuex); +localVue.use(segmentioPlugin); const pmApi = new ProjectMembersApiMock(); const projectMembersModule = makeProjectMembersModule(pmApi);