From 3c6369726e7441c4f98f9a334060c0bf73f4727f Mon Sep 17 00:00:00 2001 From: James Grugett Date: Tue, 19 Apr 2022 01:47:58 -0500 Subject: [PATCH] Add weekly activiation rate --- common/util/time.ts | 2 ++ web/lib/firebase/users.ts | 30 +++++++++++++++++++++ web/pages/analytics.tsx | 56 +++++++++++++++++++++++++++++++++++---- 3 files changed, 83 insertions(+), 5 deletions(-) create mode 100644 common/util/time.ts diff --git a/common/util/time.ts b/common/util/time.ts new file mode 100644 index 00000000..914949e4 --- /dev/null +++ b/common/util/time.ts @@ -0,0 +1,2 @@ +export const HOUR_MS = 60 * 60 * 1000 +export const DAY_MS = 24 * HOUR_MS diff --git a/web/lib/firebase/users.ts b/web/lib/firebase/users.ts index 43253f45..e7805626 100644 --- a/web/lib/firebase/users.ts +++ b/web/lib/firebase/users.ts @@ -18,11 +18,13 @@ import { GoogleAuthProvider, signInWithPopup, } from 'firebase/auth' +import _ from 'lodash' import { app } from './init' import { PrivateUser, User } from '../../../common/user' import { createUser } from './api-call' import { getValues, listenForValue, listenForValues } from './utils' +import { DAY_MS } from '../../../common/util/time' export type { User } @@ -177,3 +179,31 @@ const topCreatorsQuery = query( export function getTopCreators() { return getValues(topCreatorsQuery) } + +export function getUsers() { + return getValues(collection(db, 'users')) +} + +const getUsersQuery = (startTime: number, endTime: number) => + query( + collection(db, 'users'), + where('createdTime', '>=', startTime), + where('createdTime', '<', endTime), + orderBy('createdTime', 'asc') + ) + +export async function getDailyNewUsers( + startTime: number, + numberOfDays: number +) { + const query = getUsersQuery(startTime, startTime + DAY_MS * numberOfDays) + const users = await getValues(query) + + const usersByDay = _.range(0, numberOfDays).map(() => [] as User[]) + for (const user of users) { + const dayIndex = Math.floor((user.createdTime - startTime) / DAY_MS) + usersByDay[dayIndex].push(user) + } + + return usersByDay +} diff --git a/web/pages/analytics.tsx b/web/pages/analytics.tsx index 9683fd44..73225b4c 100644 --- a/web/pages/analytics.tsx +++ b/web/pages/analytics.tsx @@ -14,6 +14,7 @@ import { fromPropz, usePropz } from '../hooks/use-propz' import { getDailyBets } from '../lib/firebase/bets' import { getDailyComments } from '../lib/firebase/comments' import { getDailyContracts } from '../lib/firebase/contracts' +import { getDailyNewUsers } from '../lib/firebase/users' export const getStaticProps = fromPropz(getStaticPropz) export async function getStaticPropz() { @@ -21,11 +22,13 @@ export async function getStaticPropz() { const today = dayjs(dayjs().format('YYYY-MM-DD')) const startDate = today.subtract(numberOfDays, 'day') - const [dailyBets, dailyContracts, dailyComments] = await Promise.all([ - getDailyBets(startDate.valueOf(), numberOfDays), - getDailyContracts(startDate.valueOf(), numberOfDays), - getDailyComments(startDate.valueOf(), numberOfDays), - ]) + const [dailyBets, dailyContracts, dailyComments, dailyNewUsers] = + await Promise.all([ + getDailyBets(startDate.valueOf(), numberOfDays), + getDailyContracts(startDate.valueOf(), numberOfDays), + getDailyComments(startDate.valueOf(), numberOfDays), + getDailyNewUsers(startDate.valueOf(), numberOfDays), + ]) const dailyBetCounts = dailyBets.map((bets) => bets.length) const dailyContractCounts = dailyContracts.map( @@ -87,6 +90,33 @@ export async function getStaticPropz() { return Math.round(retainedFrac * 100 * 100) / 100 }) + const firstBetDict: { [userId: string]: number } = {} + for (let i = 0; i < dailyBets.length; i++) { + const bets = dailyBets[i] + for (const bet of bets) { + if (bet.userId in firstBetDict) continue + firstBetDict[bet.userId] = i + } + } + const weeklyActivationRate = dailyNewUsers.map((_, i) => { + const start = Math.max(0, i - 6) + const end = i + let activatedCount = 0 + let newUsers = 0 + for (let j = start; j <= end; j++) { + const userIds = dailyNewUsers[j].map((user) => user.id) + newUsers += userIds.length + for (const userId of userIds) { + const dayIndex = firstBetDict[userId] + if (dayIndex !== undefined && dayIndex <= end) { + activatedCount++ + } + } + } + const frac = activatedCount / (newUsers || 1) + return Math.round(frac * 100 * 100) / 100 + }) + return { props: { startDate: startDate.valueOf(), @@ -97,6 +127,7 @@ export async function getStaticPropz() { dailyContractCounts, dailyCommentCounts, weekOnWeekRetention, + weeklyActivationRate, }, revalidate: 12 * 60 * 60, // regenerate after half a day } @@ -111,6 +142,7 @@ export default function Analytics(props: { dailyContractCounts: number[] dailyCommentCounts: number[] weekOnWeekRetention: number[] + weeklyActivationRate: number[] }) { props = usePropz(props, getStaticPropz) ?? { startDate: 0, @@ -121,6 +153,7 @@ export default function Analytics(props: { dailyContractCounts: [], dailyCommentCounts: [], weekOnWeekRetention: [], + weeklyActivationRate: [], } return ( @@ -140,6 +173,7 @@ export function CustomAnalytics(props: { dailyContractCounts: number[] dailyCommentCounts: number[] weekOnWeekRetention: number[] + weeklyActivationRate: number[] }) { const { startDate, @@ -150,6 +184,7 @@ export function CustomAnalytics(props: { weeklyActiveUsers, monthlyActiveUsers, weekOnWeekRetention, + weeklyActivationRate, } = props const dailyDividedByWeekly = dailyActiveUsers @@ -268,6 +303,17 @@ export function CustomAnalytics(props: { /> + + <p className="text-gray-500"> + Out of all new users this week, how many placed at least one bet? + </p> + <DailyPercentChart + dailyPercent={weeklyActivationRate.slice(7)} + startDate={oneWeekLaterDate} + small + /> + <Spacer h={8} /> + <Title text="Ratio of Active Users" /> <Tabs defaultIndex={0}