web/satellite: add tracking event for segment.io (#3641)

This commit is contained in:
Malcolm Bouzi 2019-12-11 15:07:15 -05:00 committed by Yingrong Zhao
parent 6ce22be744
commit 0253eff2ec
21 changed files with 139 additions and 16 deletions

View File

@ -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",

View File

@ -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",

View File

@ -10,8 +10,8 @@
</div>
<div class='delete-account__form-container'>
<p class='delete-account__form-container__confirmation-text'>Are you sure you want to delete your account? If you do so, all your information, projects and API Keys will be deleted forever (drop from the satellite).</p>
<HeaderedInput
label='Enter your password'
<HeaderedInput
label='Enter your password'
placeholder='Your Password'
class='full-input'
width='100%'
@ -55,7 +55,9 @@ 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 { validatePassword } from '@/utils/validation';
import { LocalData } from '../../utils/localData';
@Component({
components: {
@ -94,6 +96,9 @@ export default class DeleteAccountPopup extends Vue {
try {
await this.auth.delete(this.password);
await this.$notify.success('Account was successfully deleted');
this.$segment.track(SegmentEvent.USER_DELETED, {
email: this.$store.getters.user.email,
});
AuthToken.remove();

View File

@ -37,6 +37,7 @@ import VPagination from '@/components/common/VPagination.vue';
import { RouteConfig } from '@/router';
import { BillingHistoryItem } from '@/types/payments';
import { SegmentEvent } from '@/utils/constants/analyticsEventNames';
@Component({
components: {
@ -46,6 +47,13 @@ import { BillingHistoryItem } from '@/types/payments';
},
})
export default class BillingHistory extends Vue {
public mounted(): void {
this.$segment.track(SegmentEvent.BILLING_HISTORY_VIEWED, {
project_id: this.$store.getters.selectedProject.id,
invoice_count: this.$store.state.paymentsModule.billingHistory.length,
});
}
public get billingHistoryItems(): BillingHistoryItem[] {
return this.$store.state.paymentsModule.billingHistory;
}

View File

@ -79,6 +79,7 @@ import VButton from '@/components/common/VButton.vue';
import { PAYMENTS_ACTIONS } from '@/store/modules/payments';
import { CreditCard } from '@/types/payments';
import { NOTIFICATION_ACTIONS } from '@/utils/constants/actionNames';
import { SegmentEvent } from '@/utils/constants/analyticsEventNames';
import { PaymentMethodsBlockState } from '@/utils/constants/billingEnums';
const {
@ -109,6 +110,9 @@ export default class PaymentMethods extends Vue {
public mounted() {
try {
this.$segment.track(SegmentEvent.PAYMENT_METHODS_VIEWED, {
project_id: this.$store.getters.selectedProject.id,
});
this.$store.dispatch(GET_CREDIT_CARDS);
} catch (error) {
this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, error.message);
@ -178,6 +182,10 @@ export default class PaymentMethods extends Vue {
await this.$notify.error(error.message);
}
this.$segment.track(SegmentEvent.PAYMENT_METHOD_ADDED, {
project_id: this.$store.getters.selectedProject.id,
});
this.tokenDepositValue = this.DEFAULT_TOKEN_DEPOSIT_VALUE;
try {
await this.$store.dispatch(GET_BILLING_HISTORY);
@ -190,6 +198,9 @@ export default class PaymentMethods extends Vue {
public async onConfirmAddStripe(): Promise<void> {
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) {

View File

@ -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<void> {
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<void> {
@ -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);
}

View File

@ -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) {

View File

@ -18,7 +18,7 @@
</div>
<input
class="delete-project-input"
type="text"
type="text"
placeholder="Enter Project Name"
v-model="projectName"
@keyup="resetError"
@ -34,8 +34,8 @@
/>
<VButton
label="Delete"
width="205px"
height="48px"
width="205px"
height="48px"
class="red"
:on-press="onDeleteProjectClick"
:is-disabled="isDeleteButtonDisabled"
@ -66,6 +66,7 @@ import {
APP_STATE_ACTIONS,
PM_ACTIONS,
} from '@/utils/constants/actionNames';
import { SegmentEvent } from '@/utils/constants/analyticsEventNames';
@Component({
components: {
@ -97,7 +98,9 @@ export default class DeleteProjectPopup extends Vue {
try {
await this.$store.dispatch(PROJECTS_ACTIONS.DELETE, this.$store.getters.selectedProject.id);
this.$segment.track(SegmentEvent.PROJECT_DELETED, {
project_id: this.$store.getters.selectedProject.id,
});
await this.$notify.success('Project was successfully deleted');
await this.selectProject();

View File

@ -70,6 +70,7 @@ import {
APP_STATE_ACTIONS,
PM_ACTIONS,
} from '@/utils/constants/actionNames';
import { SegmentEvent } from '@/utils/constants/analyticsEventNames';
@Component({
components: {
@ -115,6 +116,9 @@ export default class NewProjectPopup extends Vue {
try {
const project = await this.createProject();
this.createdProjectId = project.id;
this.$segment.track(SegmentEvent.PROJECT_CREATED, {
project_id: this.createdProjectId,
});
} catch (e) {
this.isLoading = false;
await this.$notify.error(e.message);

View File

@ -67,6 +67,7 @@ import { RouteConfig } from '@/router';
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
import { UpdateProjectModel } from '@/types/projects';
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
import { SegmentEvent } from '@/utils/constants/analyticsEventNames';
import { LocalData } from '@/utils/localData';
@Component({
@ -85,6 +86,9 @@ export default class ProjectDetailsArea extends Vue {
public async mounted(): Promise<void> {
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);
}

View File

@ -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<void> {
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');
}

View File

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

View File

@ -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<void> {
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 {

View File

@ -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',
}

View File

@ -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) {

View File

@ -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 {

View File

@ -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;

View File

@ -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'];

View File

@ -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);

View File

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

View File

@ -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);