🚸 (results) Add time filter to results table as…
This commit is contained in:
89
apps/builder/src/features/analytics/api/getStats.ts
Normal file
89
apps/builder/src/features/analytics/api/getStats.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import prisma from '@typebot.io/lib/prisma'
|
||||
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
||||
import { TRPCError } from '@trpc/server'
|
||||
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'
|
||||
|
||||
export const getStats = authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'GET',
|
||||
path: '/v1/typebots/{typebotId}/analytics/stats',
|
||||
protect: true,
|
||||
summary: 'Get results stats',
|
||||
tags: ['Analytics'],
|
||||
},
|
||||
})
|
||||
.input(
|
||||
z.object({
|
||||
typebotId: z.string(),
|
||||
timeFilter: z.enum(timeFilterValues).default(defaultTimeFilter),
|
||||
})
|
||||
)
|
||||
.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',
|
||||
})
|
||||
|
||||
const date = parseDateFromTimeFilter(timeFilter)
|
||||
|
||||
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 stats: Stats = {
|
||||
totalViews,
|
||||
totalStarts,
|
||||
totalCompleted,
|
||||
}
|
||||
|
||||
return {
|
||||
stats,
|
||||
}
|
||||
})
|
||||
@@ -1,8 +1,10 @@
|
||||
import { router } from '@/helpers/server/trpc'
|
||||
import { getTotalAnswers } from './getTotalAnswers'
|
||||
import { getTotalVisitedEdges } from './getTotalVisitedEdges'
|
||||
import { getStats } from './getStats'
|
||||
|
||||
export const analyticsRouter = router({
|
||||
getTotalAnswers,
|
||||
getTotalVisitedEdges,
|
||||
getStats,
|
||||
})
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
} from '@chakra-ui/react'
|
||||
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
|
||||
import { Stats } from '@typebot.io/schemas'
|
||||
import React, { useState } from 'react'
|
||||
import React from 'react'
|
||||
import { StatsCards } from './StatsCards'
|
||||
import { ChangePlanModal } from '@/features/billing/components/ChangePlanModal'
|
||||
import { Graph } from '@/features/graph/components/Graph'
|
||||
@@ -15,14 +15,22 @@ import { useTranslate } from '@tolgee/react'
|
||||
import { trpc } from '@/lib/trpc'
|
||||
import { isDefined } from '@typebot.io/lib'
|
||||
import { EventsCoordinatesProvider } from '@/features/graph/providers/EventsCoordinateProvider'
|
||||
import { defaultTimeFilter, timeFilterValues } from '../constants'
|
||||
import { timeFilterValues } from '../constants'
|
||||
|
||||
export const AnalyticsGraphContainer = ({ stats }: { stats?: Stats }) => {
|
||||
type Props = {
|
||||
timeFilter: (typeof timeFilterValues)[number]
|
||||
onTimeFilterChange: (timeFilter: (typeof timeFilterValues)[number]) => void
|
||||
stats?: Stats
|
||||
}
|
||||
|
||||
export const AnalyticsGraphContainer = ({
|
||||
timeFilter,
|
||||
onTimeFilterChange,
|
||||
stats,
|
||||
}: Props) => {
|
||||
const { t } = useTranslate()
|
||||
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||
const { typebot, publishedTypebot } = useTypebot()
|
||||
const [timeFilter, setTimeFilter] =
|
||||
useState<(typeof timeFilterValues)[number]>(defaultTimeFilter)
|
||||
const { data } = trpc.analytics.getTotalAnswers.useQuery(
|
||||
{
|
||||
typebotId: typebot?.id as string,
|
||||
@@ -85,7 +93,7 @@ export const AnalyticsGraphContainer = ({ stats }: { stats?: Stats }) => {
|
||||
stats={stats}
|
||||
pos="absolute"
|
||||
timeFilter={timeFilter}
|
||||
setTimeFilter={setTimeFilter}
|
||||
onTimeFilterChange={onTimeFilterChange}
|
||||
/>
|
||||
</Flex>
|
||||
)
|
||||
|
||||
@@ -10,8 +10,8 @@ import {
|
||||
} from '@chakra-ui/react'
|
||||
import { Stats } from '@typebot.io/schemas'
|
||||
import React from 'react'
|
||||
import { DropdownList } from '@/components/DropdownList'
|
||||
import { timeFilterLabels, timeFilterValues } from '../constants'
|
||||
import { timeFilterValues } from '../constants'
|
||||
import { TimeFilterDropdown } from './TimeFilterDropdown'
|
||||
|
||||
const computeCompletionRate =
|
||||
(notAvailableLabel: string) =>
|
||||
@@ -23,12 +23,12 @@ const computeCompletionRate =
|
||||
export const StatsCards = ({
|
||||
stats,
|
||||
timeFilter,
|
||||
setTimeFilter,
|
||||
onTimeFilterChange,
|
||||
...props
|
||||
}: {
|
||||
stats?: Stats
|
||||
timeFilter: (typeof timeFilterValues)[number]
|
||||
setTimeFilter: (timeFilter: (typeof timeFilterValues)[number]) => void
|
||||
onTimeFilterChange: (timeFilter: (typeof timeFilterValues)[number]) => void
|
||||
} & GridProps) => {
|
||||
const { t } = useTranslate()
|
||||
const bg = useColorModeValue('white', 'gray.900')
|
||||
@@ -69,15 +69,9 @@ export const StatsCards = ({
|
||||
<Skeleton w="50%" h="10px" mt="2" />
|
||||
)}
|
||||
</Stat>
|
||||
<DropdownList
|
||||
items={Object.entries(timeFilterLabels).map(([value, label]) => ({
|
||||
label,
|
||||
value,
|
||||
}))}
|
||||
currentItem={timeFilter}
|
||||
onItemSelect={(val) =>
|
||||
setTimeFilter(val as (typeof timeFilterValues)[number])
|
||||
}
|
||||
<TimeFilterDropdown
|
||||
timeFilter={timeFilter}
|
||||
onTimeFilterChange={onTimeFilterChange}
|
||||
backgroundColor="white"
|
||||
boxShadow="md"
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { DropdownList } from '@/components/DropdownList'
|
||||
import { timeFilterLabels, timeFilterValues } from '../constants'
|
||||
import { ButtonProps } from '@chakra-ui/react'
|
||||
|
||||
type Props = {
|
||||
timeFilter: (typeof timeFilterValues)[number]
|
||||
onTimeFilterChange: (timeFilter: (typeof timeFilterValues)[number]) => void
|
||||
} & ButtonProps
|
||||
|
||||
export const TimeFilterDropdown = ({
|
||||
timeFilter,
|
||||
onTimeFilterChange,
|
||||
...props
|
||||
}: Props) => (
|
||||
<DropdownList
|
||||
items={Object.entries(timeFilterLabels).map(([value, label]) => ({
|
||||
label,
|
||||
value,
|
||||
}))}
|
||||
currentItem={timeFilter}
|
||||
onItemSelect={(val) =>
|
||||
onTimeFilterChange(val as (typeof timeFilterValues)[number])
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
Reference in New Issue
Block a user