Skip to content

Commit

Permalink
Analytics User Properties (#803)
Browse files Browse the repository at this point in the history
  • Loading branch information
zachary-kent authored Mar 25, 2023
1 parent 9cb2f74 commit ccc4dcf
Show file tree
Hide file tree
Showing 9 changed files with 99 additions and 55 deletions.
13 changes: 13 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"@babel/preset-env": "^7.13.12",
"@babel/preset-typescript": "^7.13.0",
"@tsconfig/node14": "^1.0.3",
"@types/gtag.js": "^0.0.12",
"@types/jest": "^26.0.21",
"@types/minimist": "^1.2.2",
"@types/node-fetch": "^2.5.8",
Expand Down
9 changes: 5 additions & 4 deletions src/components/BottomBar/BottomBarState.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { reactive } from 'vue';
import { GTag, GTagEvent } from '../../gtag';
import { VueGtag } from 'vue-gtag-next';
import { GTagEvent } from '../../gtag';
import { checkNotNull } from '../../utilities';

import {
Expand Down Expand Up @@ -117,7 +118,7 @@ export const addCourseToBottomBar = (course: FirestoreSemesterCourse): void => {
]);
};

export const toggleBottomBar = (gtag?: GTag): void => {
export const toggleBottomBar = (gtag?: VueGtag): void => {
vueForBottomBar.isExpanded = !vueForBottomBar.isExpanded;
if (vueForBottomBar.isExpanded) {
GTagEvent(gtag, 'bottom-bar-open');
Expand All @@ -126,7 +127,7 @@ export const toggleBottomBar = (gtag?: GTag): void => {
}
};

export const closeBottomBar = (gtag?: GTag): void => {
export const closeBottomBar = (gtag?: VueGtag): void => {
vueForBottomBar.isExpanded = false;
GTagEvent(gtag, 'bottom-bar-close');
};
Expand All @@ -135,7 +136,7 @@ export const changeBottomBarCourseFocus = (index: number): void => {
vueForBottomBar.bottomCourseFocus = index;
};

export const deleteBottomBarCourse = (index: number, gtag?: GTag): void => {
export const deleteBottomBarCourse = (index: number, gtag?: VueGtag): void => {
GTagEvent(gtag, 'bottom-bar-delete-tab');
vueForBottomBar.bottomCourses = vueForBottomBar.bottomCourses.filter((_, i) => i !== index);
if (vueForBottomBar.bottomCourseFocus >= vueForBottomBar.bottomCourses.length) {
Expand Down
15 changes: 8 additions & 7 deletions src/global-firestore-data/user-semesters.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { doc, updateDoc } from 'firebase/firestore';

import { VueGtag } from 'vue-gtag-next';
import { semestersCollection } from '../firebase-config';
import store from '../store';
import { GTag, GTagEvent } from '../gtag';
import { GTagEvent } from '../gtag';
import { sortedSemesters } from '../utilities';

import {
Expand Down Expand Up @@ -68,7 +69,7 @@ export const semesterEquals = (
export const addSemester = (
year: number,
season: FirestoreSemesterSeason,
gtag?: GTag,
gtag?: VueGtag,
courses: readonly FirestoreSemesterCourse[] = []
): void => {
GTagEvent(gtag, 'add-semester');
Expand All @@ -78,7 +79,7 @@ export const addSemester = (
export const deleteSemester = (
year: number,
season: FirestoreSemesterSeason,
gtag?: GTag
gtag?: VueGtag
): void => {
GTagEvent(gtag, 'delete-semester');
const semester = store.state.semesters.find(sem => semesterEquals(sem, year, season));
Expand All @@ -93,7 +94,7 @@ export const addCourseToSemester = (
season: FirestoreSemesterSeason,
newCourse: FirestoreSemesterCourse,
choiceUpdater: (choice: FirestoreCourseOptInOptOutChoices) => FirestoreCourseOptInOptOutChoices,
gtag?: GTag
gtag?: VueGtag
): void => {
GTagEvent(gtag, 'add-course');
editSemesters(oldSemesters => {
Expand All @@ -115,7 +116,7 @@ export const deleteCourseFromSemester = (
year: number,
season: FirestoreSemesterSeason,
courseUniqueID: number,
gtag?: GTag
gtag?: VueGtag
): void => {
GTagEvent(gtag, 'delete-course');
const semester = store.state.semesters.find(sem => semesterEquals(sem, year, season));
Expand All @@ -135,7 +136,7 @@ export const deleteCourseFromSemester = (
export const deleteAllCoursesFromSemester = (
year: number,
season: FirestoreSemesterSeason,
gtag?: GTag
gtag?: VueGtag
): void => {
GTagEvent(gtag, 'delete-semester-courses');
const semester = store.state.semesters.find(sem => semesterEquals(sem, year, season));
Expand All @@ -150,7 +151,7 @@ export const deleteAllCoursesFromSemester = (
}
};

export const deleteCourseFromSemesters = (courseUniqueID: number, gtag?: GTag): void => {
export const deleteCourseFromSemesters = (courseUniqueID: number, gtag?: VueGtag): void => {
GTagEvent(gtag, 'delete-course');
editSemesters(oldSemesters =>
oldSemesters.map(semester => {
Expand Down
21 changes: 16 additions & 5 deletions src/gtag.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
import { VueGtag, query } from 'vue-gtag-next';

type EventPayload = { event_category: string; event_label: string; value: number };
type LoginEventPayload = { method: string };

export type GTag = {
event(eventType: string, eventPayload: LoginEventPayload | EventPayload): void;
/**
* Set a user's properties for analytics
*
* @param gtag the `VueGtag` instance to query
* @param properties the user's properties
*/
export const setUserProperties = (onboardingData: AppOnboardingData) => {
const gtag = query as Gtag.Gtag;
gtag('set', 'user_properties', {
major: onboardingData.major,
gradYear: onboardingData.gradYear,
});
};

/** GTagLoginEvent represents the gtag that tracks when users login. */
export const GTagLoginEvent = (gtag: GTag | undefined, method: string): void => {
export const GTagLoginEvent = (gtag: VueGtag | undefined, method: string): void => {
if (!gtag) return;
gtag.event('login', { method });
};
Expand Down Expand Up @@ -41,7 +52,7 @@ type EventType =
* @param gtag is the global site tag that sends events to Google Analytics
* @param eventType specifies the type of event that the gtag sends
*/
export const GTagEvent = (gtag: GTag | undefined, eventType: EventType): void => {
export const GTagEvent = (gtag: VueGtag | undefined, eventType: EventType): void => {
if (!gtag) return;
let eventPayload: EventPayload | undefined;
switch (eventType) {
Expand Down
2 changes: 1 addition & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ auth.onAuthStateChanged(() => {
app.use(router);
// Enable Google analytics with custom events
app.use(VueGtag, {
property: { id: 'UA-124837875-2' },
property: { id: 'G-BQ6CTZQPSF' },
});
app.use(store);
app.mount('#app');
Expand Down
77 changes: 43 additions & 34 deletions src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
getFirstPlan,
} from './utilities';
import featureFlagCheckers from './feature-flags';
import { setUserProperties } from './gtag';

type SimplifiedFirebaseUser = { readonly displayName: string; readonly email: string };

Expand Down Expand Up @@ -161,45 +162,53 @@ const store: TypedVuexStore = new TypedVuexStore({
});

const autoRecomputeDerivedData = (): (() => void) =>
store.subscribe((payload, state) => {
if (payload.type === 'setOrderByNewest') {
store.commit('setSemesters', sortedSemesters(state.semesters, state.orderByNewest));
}
// Recompute courses
if (payload.type === 'setSemesters') {
const allCourseSet = new Set<string>();
const duplicatedCourseCodeSet = new Set<string>();
const courseMap: Record<number, FirestoreSemesterCourse> = {};
const courseToSemesterMap: Record<number, FirestoreSemester> = {};
state.semesters.forEach(semester => {
semester.courses.forEach(course => {
if (isPlaceholderCourse(course)) {
return;
}
store.subscribe((mutation, state) => {
switch (mutation.type) {
case 'setOnboardingData': {
setUserProperties(mutation.payload);
break;
}
case 'setOrderByNewest': {
store.commit('setSemesters', sortedSemesters(state.semesters, state.orderByNewest));
break;
}
case 'setSemesters': {
const allCourseSet = new Set<string>();
const duplicatedCourseCodeSet = new Set<string>();
const courseMap: Record<number, FirestoreSemesterCourse> = {};
const courseToSemesterMap: Record<number, FirestoreSemester> = {};
state.semesters.forEach(semester => {
semester.courses.forEach(course => {
if (isPlaceholderCourse(course)) {
return;
}

const { code } = course;
if (allCourseSet.has(code)) {
duplicatedCourseCodeSet.add(code);
} else {
allCourseSet.add(code);
}
courseMap[course.uniqueID] = course;
courseToSemesterMap[course.uniqueID] = semester;
const { code } = course;
if (allCourseSet.has(code)) {
duplicatedCourseCodeSet.add(code);
} else {
allCourseSet.add(code);
}
courseMap[course.uniqueID] = course;
courseToSemesterMap[course.uniqueID] = semester;
});
});
});
const derivedCourseData: DerivedCoursesData = {
duplicatedCourseCodeSet,
courseMap,
courseToSemesterMap,
};
store.commit('setDerivedCourseData', derivedCourseData);
const derivedCourseData: DerivedCoursesData = {
duplicatedCourseCodeSet,
courseMap,
courseToSemesterMap,
};
store.commit('setDerivedCourseData', derivedCourseData);
break;
}
default:
}
// Recompute requirements
if (
payload.type === 'setOnboardingData' ||
payload.type === 'setSemesters' ||
payload.type === 'setToggleableRequirementChoices' ||
payload.type === 'setOverriddenFulfillmentChoices'
mutation.type === 'setOnboardingData' ||
mutation.type === 'setSemesters' ||
mutation.type === 'setToggleableRequirementChoices' ||
mutation.type === 'setOverriddenFulfillmentChoices'
) {
if (state.onboardingData.college !== '') {
store.commit(
Expand Down
6 changes: 6 additions & 0 deletions src/vue-gtag-next.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export {};

declare module 'vue-gtag-next' {
// eslint-disable-next-line import/prefer-default-export
export const query: Gtag.Gtag;
}
10 changes: 6 additions & 4 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"baseUrl": ".",
"types": [
"node",
"jest"
"jest",
"@types/gtag.js"
],
"paths": {
"@/*": [
Expand All @@ -34,9 +35,10 @@
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
, "scripts/population/courses-json-generator.ts" ],
"tests/**/*.tsx",
"scripts/population/courses-json-generator.ts"
],
"exclude": [
"node_modules"
]
}
}

0 comments on commit ccc4dcf

Please sign in to comment.