Skip to content

Commit

Permalink
🚸 (results) Improve time filter so that it takes into account user ti…
Browse files Browse the repository at this point in the history
…mezone
  • Loading branch information
baptisteArno committed Mar 15, 2024
1 parent 001e696 commit f6d2b15
Show file tree
Hide file tree
Showing 10 changed files with 282 additions and 144 deletions.
125 changes: 67 additions & 58 deletions apps/builder/src/features/analytics/api/getStats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import { z } from 'zod'
import { canReadTypebots } from '@/helpers/databaseRules'
import { Stats, statsSchema } from '@typebot.io/schemas'
import { defaultTimeFilter, timeFilterValues } from '../constants'
import { parseDateFromTimeFilter } from '../helpers/parseDateFromTimeFilter'
import {
parseFromDateFromTimeFilter,
parseToDateFromTimeFilter,
} from '../helpers/parseDateFromTimeFilter'

export const getStats = authenticatedProcedure
.meta({
Expand All @@ -21,69 +24,75 @@ export const getStats = authenticatedProcedure
z.object({
typebotId: z.string(),
timeFilter: z.enum(timeFilterValues).default(defaultTimeFilter),
timeZone: z.string().optional(),
})
)
.output(z.object({ stats: statsSchema }))
.query(async ({ input: { typebotId, timeFilter }, ctx: { user } }) => {
const typebot = await prisma.typebot.findFirst({
where: canReadTypebots(typebotId, user),
select: { publishedTypebot: true, id: true },
})
if (!typebot?.publishedTypebot)
throw new TRPCError({
code: 'NOT_FOUND',
message: 'Published typebot not found',
.query(
async ({ input: { typebotId, timeFilter, timeZone }, ctx: { user } }) => {
const typebot = await prisma.typebot.findFirst({
where: canReadTypebots(typebotId, user),
select: { publishedTypebot: true, id: true },
})
if (!typebot?.publishedTypebot)
throw new TRPCError({
code: 'NOT_FOUND',
message: 'Published typebot not found',
})

const date = parseDateFromTimeFilter(timeFilter)
const fromDate = parseFromDateFromTimeFilter(timeFilter, timeZone)
const toDate = parseToDateFromTimeFilter(timeFilter, timeZone)

const [totalViews, totalStarts, totalCompleted] = await prisma.$transaction(
[
prisma.result.count({
where: {
typebotId: typebot.id,
isArchived: false,
createdAt: date
? {
gte: date,
}
: undefined,
},
}),
prisma.result.count({
where: {
typebotId: typebot.id,
isArchived: false,
hasStarted: true,
createdAt: date
? {
gte: date,
}
: undefined,
},
}),
prisma.result.count({
where: {
typebotId: typebot.id,
isArchived: false,
isCompleted: true,
createdAt: date
? {
gte: date,
}
: undefined,
},
}),
]
)
const [totalViews, totalStarts, totalCompleted] =
await prisma.$transaction([
prisma.result.count({
where: {
typebotId: typebot.id,
isArchived: false,
createdAt: fromDate
? {
gte: fromDate,
lte: toDate ?? undefined,
}
: undefined,
},
}),
prisma.result.count({
where: {
typebotId: typebot.id,
isArchived: false,
hasStarted: true,
createdAt: fromDate
? {
gte: fromDate,
lte: toDate ?? undefined,
}
: undefined,
},
}),
prisma.result.count({
where: {
typebotId: typebot.id,
isArchived: false,
isCompleted: true,
createdAt: fromDate
? {
gte: fromDate,
lte: toDate ?? undefined,
}
: undefined,
},
}),
])

const stats: Stats = {
totalViews,
totalStarts,
totalCompleted,
}
const stats: Stats = {
totalViews,
totalStarts,
totalCompleted,
}

return {
stats,
return {
stats,
}
}
})
)
82 changes: 45 additions & 37 deletions apps/builder/src/features/analytics/api/getTotalAnswers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import { totalAnswersSchema } from '@typebot.io/schemas/features/analytics'
import { parseGroups } from '@typebot.io/schemas'
import { isInputBlock } from '@typebot.io/lib'
import { defaultTimeFilter, timeFilterValues } from '../constants'
import { parseDateFromTimeFilter } from '../helpers/parseDateFromTimeFilter'
import {
parseFromDateFromTimeFilter,
parseToDateFromTimeFilter,
} from '../helpers/parseDateFromTimeFilter'

export const getTotalAnswers = authenticatedProcedure
.meta({
Expand All @@ -23,48 +26,53 @@ export const getTotalAnswers = authenticatedProcedure
z.object({
typebotId: z.string(),
timeFilter: z.enum(timeFilterValues).default(defaultTimeFilter),
timeZone: z.string().optional(),
})
)
.output(z.object({ totalAnswers: z.array(totalAnswersSchema) }))
.query(async ({ input: { typebotId, timeFilter }, ctx: { user } }) => {
const typebot = await prisma.typebot.findFirst({
where: canReadTypebots(typebotId, user),
select: { publishedTypebot: true },
})
if (!typebot?.publishedTypebot)
throw new TRPCError({
code: 'NOT_FOUND',
message: 'Published typebot not found',
.query(
async ({ input: { typebotId, timeFilter, timeZone }, ctx: { user } }) => {
const typebot = await prisma.typebot.findFirst({
where: canReadTypebots(typebotId, user),
select: { publishedTypebot: true },
})
if (!typebot?.publishedTypebot)
throw new TRPCError({
code: 'NOT_FOUND',
message: 'Published typebot not found',
})

const date = parseDateFromTimeFilter(timeFilter)
const fromDate = parseFromDateFromTimeFilter(timeFilter, timeZone)
const toDate = parseToDateFromTimeFilter(timeFilter, timeZone)

const totalAnswersPerBlock = await prisma.answer.groupBy({
by: ['blockId'],
where: {
result: {
typebotId: typebot.publishedTypebot.typebotId,
createdAt: date
? {
gte: date,
}
: undefined,
},
blockId: {
in: parseGroups(typebot.publishedTypebot.groups, {
typebotVersion: typebot.publishedTypebot.version,
}).flatMap((group) =>
group.blocks.filter(isInputBlock).map((block) => block.id)
),
const totalAnswersPerBlock = await prisma.answer.groupBy({
by: ['blockId'],
where: {
result: {
typebotId: typebot.publishedTypebot.typebotId,
createdAt: fromDate
? {
gte: fromDate,
lte: toDate ?? undefined,
}
: undefined,
},
blockId: {
in: parseGroups(typebot.publishedTypebot.groups, {
typebotVersion: typebot.publishedTypebot.version,
}).flatMap((group) =>
group.blocks.filter(isInputBlock).map((block) => block.id)
),
},
},
},
_count: { _all: true },
})
_count: { _all: true },
})

return {
totalAnswers: totalAnswersPerBlock.map((a) => ({
blockId: a.blockId,
total: a._count._all,
})),
return {
totalAnswers: totalAnswersPerBlock.map((a) => ({
blockId: a.blockId,
total: a._count._all,
})),
}
}
})
)
68 changes: 38 additions & 30 deletions apps/builder/src/features/analytics/api/getTotalVisitedEdges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import { z } from 'zod'
import { canReadTypebots } from '@/helpers/databaseRules'
import { totalVisitedEdgesSchema } from '@typebot.io/schemas'
import { defaultTimeFilter, timeFilterValues } from '../constants'
import { parseDateFromTimeFilter } from '../helpers/parseDateFromTimeFilter'
import {
parseFromDateFromTimeFilter,
parseToDateFromTimeFilter,
} from '../helpers/parseDateFromTimeFilter'

export const getTotalVisitedEdges = authenticatedProcedure
.meta({
Expand All @@ -21,45 +24,50 @@ export const getTotalVisitedEdges = authenticatedProcedure
z.object({
typebotId: z.string(),
timeFilter: z.enum(timeFilterValues).default(defaultTimeFilter),
timeZone: z.string().optional(),
})
)
.output(
z.object({
totalVisitedEdges: z.array(totalVisitedEdgesSchema),
})
)
.query(async ({ input: { typebotId, timeFilter }, ctx: { user } }) => {
const typebot = await prisma.typebot.findFirst({
where: canReadTypebots(typebotId, user),
select: { id: true },
})
if (!typebot?.id)
throw new TRPCError({
code: 'NOT_FOUND',
message: 'Published typebot not found',
.query(
async ({ input: { typebotId, timeFilter, timeZone }, ctx: { user } }) => {
const typebot = await prisma.typebot.findFirst({
where: canReadTypebots(typebotId, user),
select: { id: true },
})
if (!typebot?.id)
throw new TRPCError({
code: 'NOT_FOUND',
message: 'Published typebot not found',
})

const date = parseDateFromTimeFilter(timeFilter)
const fromDate = parseFromDateFromTimeFilter(timeFilter, timeZone)
const toDate = parseToDateFromTimeFilter(timeFilter, timeZone)

const edges = await prisma.visitedEdge.groupBy({
by: ['edgeId'],
where: {
result: {
typebotId: typebot.id,
createdAt: date
? {
gte: date,
}
: undefined,
const edges = await prisma.visitedEdge.groupBy({
by: ['edgeId'],
where: {
result: {
typebotId: typebot.id,
createdAt: fromDate
? {
gte: fromDate,
lte: toDate ?? undefined,
}
: undefined,
},
},
},
_count: { resultId: true },
})
_count: { resultId: true },
})

return {
totalVisitedEdges: edges.map((e) => ({
edgeId: e.edgeId,
total: e._count.resultId,
})),
return {
totalVisitedEdges: edges.map((e) => ({
edgeId: e.edgeId,
total: e._count.resultId,
})),
}
}
})
)
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import { isDefined } from '@typebot.io/lib'
import { EventsCoordinatesProvider } from '@/features/graph/providers/EventsCoordinateProvider'
import { timeFilterValues } from '../constants'

const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone

type Props = {
timeFilter: (typeof timeFilterValues)[number]
onTimeFilterChange: (timeFilter: (typeof timeFilterValues)[number]) => void
Expand All @@ -35,6 +37,7 @@ export const AnalyticsGraphContainer = ({
{
typebotId: typebot?.id as string,
timeFilter,
timeZone,
},
{ enabled: isDefined(publishedTypebot) }
)
Expand All @@ -43,6 +46,7 @@ export const AnalyticsGraphContainer = ({
{
typebotId: typebot?.id as string,
timeFilter,
timeZone,
},
{ enabled: isDefined(publishedTypebot) }
)
Expand Down
6 changes: 5 additions & 1 deletion apps/builder/src/features/analytics/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ export const timeFilterValues = [
'today',
'last7Days',
'last30Days',
'monthToDate',
'lastMonth',
'yearToDate',
'allTime',
] as const
Expand All @@ -13,8 +15,10 @@ export const timeFilterLabels: Record<
today: 'Today',
last7Days: 'Last 7 days',
last30Days: 'Last 30 days',
monthToDate: 'Month to date',
lastMonth: 'Last month',
yearToDate: 'Year to date',
allTime: 'All time',
}

export const defaultTimeFilter = 'today' as const
export const defaultTimeFilter = 'last7Days' as const

0 comments on commit f6d2b15

Please sign in to comment.