Skip to content

Commit 82e8fb3

Browse files
feat(analytics): add tutor/tutee/parent graphs
1 parent 4a98bd6 commit 82e8fb3

File tree

3 files changed

+229
-17
lines changed

3 files changed

+229
-17
lines changed

components/analytics/index.tsx

Lines changed: 201 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,17 @@ function Card<T extends Record<string, number> & { week: number }>({
5353
monday.setDate(monday.getDate() - 7 - monday.getDay() + 1);
5454
return monday.valueOf() === new Date(d.week).valueOf();
5555
});
56-
return today ? today[content[0].dataKey] : 0;
56+
return (today ?? data[data.length - 1])[content[0].dataKey];
5757
}, [data, content]);
5858

5959
return (
6060
<div className='card'>
6161
<article className='header'>
6262
<header>
6363
<h2 className={cn({ loading: num === undefined })}>
64-
{content && content[0].rate && num !== undefined ? formatRate(num) : num}
64+
{content && content[0].rate && num !== undefined
65+
? formatRate(num)
66+
: num}
6567
</h2>
6668
<h3>{title}</h3>
6769
</header>
@@ -199,6 +201,30 @@ export default function Analytics(): JSX.Element {
199201
})),
200202
[data]
201203
);
204+
const tutors = useMemo(
205+
() =>
206+
data?.tutors.map((d) => ({
207+
...d,
208+
week: new Date(d.week).valueOf(),
209+
})),
210+
[data]
211+
);
212+
const tutees = useMemo(
213+
() =>
214+
data?.tutees.map((d) => ({
215+
...d,
216+
week: new Date(d.week).valueOf(),
217+
})),
218+
[data]
219+
);
220+
const parents = useMemo(
221+
() =>
222+
data?.parents.map((d) => ({
223+
...d,
224+
week: new Date(d.week).valueOf(),
225+
})),
226+
[data]
227+
);
202228

203229
// TODO: Ensure that the scale on the chart isn't dependent on the data points
204230
// being equally spaced out. Instead, it should be relative to the data point
@@ -313,9 +339,9 @@ export default function Analytics(): JSX.Element {
313339
]}
314340
color='#81C784'
315341
>
316-
The growth rate of the number of meetings per week. This graph
317-
should look very similar to the growth rate of users with meetings
318-
per week; both metrics are directly correlated.
342+
The growth rate of the number of meetings per week. This graph should
343+
look very similar to the growth rate of users with meetings per week;
344+
both metrics are directly correlated.
319345
</Card>
320346
<Card
321347
title={
@@ -382,9 +408,9 @@ export default function Analytics(): JSX.Element {
382408
color='#F06292'
383409
>
384410
The number of users created per week. You’ll notice this graph
385-
correlates well with the <strong>Total users</strong> graph below
386-
it; as the number of new users spikes, the total number of users
387-
will spike too.
411+
correlates well with the <strong>Total users</strong> graph below it;
412+
as the number of new users spikes, the total number of users will
413+
spike too.
388414
</Card>
389415
<Card
390416
title={
@@ -406,8 +432,8 @@ export default function Analytics(): JSX.Element {
406432
]}
407433
color='#F06292'
408434
>
409-
The growth rate of the number of users created per week. Note that
410-
all of these growth rate graphs depict the derivatives of their
435+
The growth rate of the number of users created per week. Note that all
436+
of these growth rate graphs depict the derivatives of their
411437
corresponding weekly metrics; they are graphs of the slopes.
412438
</Card>
413439
<Card
@@ -434,9 +460,8 @@ export default function Analytics(): JSX.Element {
434460
<Link href='https://hbr.org/2010/02/entrepreneurs-beware-of-vanity-metrics'>
435461
vanity metric
436462
</Link>
437-
; a number that looks good on paper but isn’t action oriented. Use
438-
it for press releases or marketing, but not to measure actual
439-
growth.
463+
; a number that looks good on paper but isn’t action oriented. Use it
464+
for press releases or marketing, but not to measure actual growth.
440465
</Card>
441466
<Card
442467
title={
@@ -458,7 +483,169 @@ export default function Analytics(): JSX.Element {
458483
]}
459484
color='#9575CD'
460485
>
461-
The weekly growth rate of the total number of users. Again, this is
486+
The weekly growth rate of the total number of users. Again, this is a{' '}
487+
<Link href='https://hbr.org/2010/02/entrepreneurs-beware-of-vanity-metrics'>
488+
vanity metric
489+
</Link>
490+
; this growth rate will <i>always</i> be positive and thus will{' '}
491+
<i>never</i> provide meaningful feedback on how growth is doing.
492+
</Card>
493+
<Card
494+
title={
495+
<>
496+
Tutors
497+
<br />
498+
in all time
499+
</>
500+
}
501+
data={tutors}
502+
header='Total tutors'
503+
content={[
504+
{ dataKey: 'total', dataLabel: 'total tutors' },
505+
{
506+
dataKey: 'total_growth',
507+
dataLabel: 'from previous week',
508+
rate: true,
509+
},
510+
]}
511+
color='#9575CD'
512+
>
513+
The total number of tutors. This is a{' '}
514+
<Link href='https://hbr.org/2010/02/entrepreneurs-beware-of-vanity-metrics'>
515+
vanity metric
516+
</Link>
517+
; a number that looks good on paper but isn’t action oriented. Use it
518+
for press releases or marketing, but not to measure actual growth.
519+
</Card>
520+
<Card
521+
title={
522+
<>
523+
Weekly growth rate of
524+
<br />
525+
the total number of tutors
526+
</>
527+
}
528+
data={tutors}
529+
header='Total tutors'
530+
content={[
531+
{
532+
dataKey: 'total_growth',
533+
dataLabel: 'from previous week',
534+
rate: true,
535+
},
536+
{ dataKey: 'total', dataLabel: 'total tutors' },
537+
]}
538+
color='#9575CD'
539+
>
540+
The weekly growth rate of the total number of tutors. Again, this is a{' '}
541+
<Link href='https://hbr.org/2010/02/entrepreneurs-beware-of-vanity-metrics'>
542+
vanity metric
543+
</Link>
544+
; this growth rate will <i>always</i> be positive and thus will{' '}
545+
<i>never</i> provide meaningful feedback on how growth is doing.
546+
</Card>
547+
<Card
548+
title={
549+
<>
550+
Tutees
551+
<br />
552+
in all time
553+
</>
554+
}
555+
data={tutees}
556+
header='Total tutees'
557+
content={[
558+
{ dataKey: 'total', dataLabel: 'total tutees' },
559+
{
560+
dataKey: 'total_growth',
561+
dataLabel: 'from previous week',
562+
rate: true,
563+
},
564+
]}
565+
color='#9575CD'
566+
>
567+
The total number of tutees. This is a{' '}
568+
<Link href='https://hbr.org/2010/02/entrepreneurs-beware-of-vanity-metrics'>
569+
vanity metric
570+
</Link>
571+
; a number that looks good on paper but isn’t action oriented. Use it
572+
for press releases or marketing, but not to measure actual growth.
573+
</Card>
574+
<Card
575+
title={
576+
<>
577+
Weekly growth rate of
578+
<br />
579+
the total number of tutees
580+
</>
581+
}
582+
data={tutees}
583+
header='Total tutees'
584+
content={[
585+
{
586+
dataKey: 'total_growth',
587+
dataLabel: 'from previous week',
588+
rate: true,
589+
},
590+
{ dataKey: 'total', dataLabel: 'total tutees' },
591+
]}
592+
color='#9575CD'
593+
>
594+
The weekly growth rate of the total number of tutees. Again, this is a{' '}
595+
<Link href='https://hbr.org/2010/02/entrepreneurs-beware-of-vanity-metrics'>
596+
vanity metric
597+
</Link>
598+
; this growth rate will <i>always</i> be positive and thus will{' '}
599+
<i>never</i> provide meaningful feedback on how growth is doing.
600+
</Card>
601+
<Card
602+
title={
603+
<>
604+
Parents
605+
<br />
606+
in all time
607+
</>
608+
}
609+
data={parents}
610+
header='Total parents'
611+
content={[
612+
{ dataKey: 'total', dataLabel: 'total parents' },
613+
{
614+
dataKey: 'total_growth',
615+
dataLabel: 'from previous week',
616+
rate: true,
617+
},
618+
]}
619+
color='#9575CD'
620+
>
621+
The total number of parents. This is a{' '}
622+
<Link href='https://hbr.org/2010/02/entrepreneurs-beware-of-vanity-metrics'>
623+
vanity metric
624+
</Link>
625+
; a number that looks good on paper but isn’t action oriented. Use it
626+
for press releases or marketing, but not to measure actual growth.
627+
</Card>
628+
<Card
629+
title={
630+
<>
631+
Weekly growth rate of
632+
<br />
633+
the total number of parents
634+
</>
635+
}
636+
data={parents}
637+
header='Total parents'
638+
content={[
639+
{
640+
dataKey: 'total_growth',
641+
dataLabel: 'from previous week',
642+
rate: true,
643+
},
644+
{ dataKey: 'total', dataLabel: 'total parents' },
645+
]}
646+
color='#9575CD'
647+
>
648+
The weekly growth rate of the total number of parents. Again, this is
462649
a{' '}
463650
<Link href='https://hbr.org/2010/02/entrepreneurs-beware-of-vanity-metrics'>
464651
vanity metric

db/analytics.pgsql

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ language sql stable;
2525

2626
-- Users (and growth rate and total).
2727
drop function if exists users;
28-
create or replace function users(org_id text, time_zone text)
28+
create or replace function users(org_id text, time_zone text, tag user_tag)
2929
returns table (week timestamptz, users bigint, growth float, total numeric, total_growth float)
3030
as $$
3131
select *, (total::float / lag(total) over (order by week) - 1) total_growth
@@ -43,7 +43,8 @@ as $$
4343
users
4444
inner join relation_orgs on relation_orgs.user = users.id
4545
where
46-
relation_orgs.org = org_id
46+
(relation_orgs.org = org_id) and
47+
(tag is null or tag = any (users.tags))
4748
group by week
4849
) as _
4950
) as _;

pages/api/orgs/[id]/analytics.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ export interface DBServiceHours {
3838
export interface AnalyticsRes {
3939
usersWithMeetings: DBUsersWithMeetings[];
4040
users: DBUsers[];
41+
tutors: DBUsers[];
42+
tutees: DBUsers[];
43+
parents: DBUsers[];
4144
meetings: DBMeetings[];
4245
serviceHours: DBServiceHours[];
4346
}
@@ -58,11 +61,17 @@ export default async function analyticsAPI(
5861
const [
5962
{ data: usersWithMeetings, error: usersWithMeetingsError },
6063
{ data: users, error: usersError },
64+
{ data: tutors, error: tutorsError },
65+
{ data: tutees, error: tuteesError },
66+
{ data: parents, error: parentsError },
6167
{ data: meetings, error: meetingsError },
6268
{ data: serviceHours, error: serviceHoursError },
6369
] = await Promise.all([
6470
supabase.rpc<DBUsersWithMeetings>('users_with_meetings', props),
65-
supabase.rpc<DBUsers>('users', props),
71+
supabase.rpc<DBUsers>('users', { ...props, tag: null }),
72+
supabase.rpc<DBUsers>('users', { ...props, tag: 'tutor' }),
73+
supabase.rpc<DBUsers>('users', { ...props, tag: 'tutee' }),
74+
supabase.rpc<DBUsers>('users', { ...props, tag: 'parent' }),
6675
supabase.rpc<DBMeetings>('meetings', props),
6776
supabase.rpc<DBServiceHours>('service_hours', props),
6877
]);
@@ -74,6 +83,18 @@ export default async function analyticsAPI(
7483
const msg = 'Error fetching "users" analytics';
7584
throw new APIError(`${msg}: ${usersError?.message}`, 500);
7685
}
86+
if (tutorsError || !tutors) {
87+
const msg = 'Error fetching "tutors" analytics';
88+
throw new APIError(`${msg}: ${tutorsError?.message}`, 500);
89+
}
90+
if (tuteesError || !tutees) {
91+
const msg = 'Error fetching "tutees" analytics';
92+
throw new APIError(`${msg}: ${tuteesError?.message}`, 500);
93+
}
94+
if (parentsError || !parents) {
95+
const msg = 'Error fetching "parents" analytics';
96+
throw new APIError(`${msg}: ${parentsError?.message}`, 500);
97+
}
7798
if (meetingsError || !meetings) {
7899
const msg = 'Error fetching "meetings" analytics';
79100
throw new APIError(`${msg}: ${meetingsError?.message}`, 500);
@@ -85,6 +106,9 @@ export default async function analyticsAPI(
85106
res.status(200).json({
86107
usersWithMeetings,
87108
users,
109+
tutors,
110+
tutees,
111+
parents,
88112
meetings,
89113
serviceHours,
90114
});

0 commit comments

Comments
 (0)