Files
Compass/web/pages/admin/journeys.tsx
Martin Braquet ba9b3cfb06 Add pretty formatting (#29)
* Test

* Add pretty formatting

* Fix Tests

* Fix Tests

* Fix Tests

* Fix

* Add pretty formatting fix

* Fix

* Test

* Fix tests

* Clean typeckech

* Add prettier check

* Fix api tsconfig

* Fix api tsconfig

* Fix tsconfig

* Fix

* Fix

* Prettier
2026-02-20 17:32:27 +01:00

138 lines
4.9 KiB
TypeScript

import clsx from 'clsx'
import {convertUser} from 'common/supabase/users'
import {Row as rowfor, run} from 'common/supabase/utils'
import {User} from 'common/user'
import {HOUR_MS} from 'common/util/time'
import {groupBy, orderBy} from 'lodash'
import {useEffect, useState} from 'react'
import {Button} from 'web/components/buttons/button'
import {Col} from 'web/components/layout/col'
import {Row} from 'web/components/layout/row'
import {NoSEO} from 'web/components/NoSEO'
import {UserAvatarAndBadge} from 'web/components/widgets/user-link'
import {useAdmin} from 'web/hooks/use-admin'
import {usePersistentQueryState} from 'web/hooks/use-persistent-query-state'
import {useIsAuthorized} from 'web/hooks/use-user'
import {db} from 'web/lib/supabase/db'
export default function Journeys() {
const [eventsByUser, setEventsByUser] = useState<Record<string, rowfor<'user_events'>[]>>({})
const [hoursFromNowQ, setHoursFromNowQ] = usePersistentQueryState('h', '5')
const hoursFromNow = parseInt(hoursFromNowQ ?? '5')
const [unBannedUsers, setUnBannedUsers] = useState<User[]>([])
const [bannedUsers, setBannedUsers] = useState<User[]>([])
const isAuthed = useIsAuthorized()
const getEvents = async () => {
const start = Date.now() - hoursFromNow * HOUR_MS
const users = await run(db.from('users').select('id').gt('data->createdTime', start))
const events = await run(
db
.from('user_events')
.select('*')
.in(
'user_id',
users.data.map((u) => u.id),
),
)
const eventsByUser = groupBy(
orderBy(events.data as rowfor<'user_events'>[], 'ts', 'asc'),
'user_id',
)
setEventsByUser(eventsByUser)
}
const getUsers = async () => {
const userData = await run(db.from('users').select().in('id', Object.keys(eventsByUser)))
const users = userData.data.map(convertUser)
setBannedUsers(users.filter((u) => u.isBannedFromPosting))
setUnBannedUsers(users.filter((u) => !u.isBannedFromPosting))
}
useEffect(() => {
getUsers()
}, [JSON.stringify(Object.keys(eventsByUser))])
useEffect(() => {
if (!isAuthed) return
getEvents()
}, [hoursFromNow, isAuthed])
const isAdmin = useAdmin()
if (!isAdmin) return <></>
return (
<Row>
<NoSEO />
<div className="text-ink-900 mx-8">
<div className={'text-primary-700 my-1 text-2xl'}>User Journeys</div>
<Row className={'items-center gap-2'}>
Viewing journeys from {unBannedUsers.length} unbanned users ({bannedUsers.length} banned).
Showing users created: {hoursFromNow}h ago.
<Button
color={'indigo-outline'}
size={'xs'}
onClick={() => {
setHoursFromNowQ((hoursFromNow + 1).toString())
}}
>
+1h
</Button>
</Row>
<Row className={'flex-wrap gap-2 scroll-auto'}>
{Object.keys(eventsByUser).map((userId) => {
if (bannedUsers.find((u) => u.id === userId)) return null
const events = eventsByUser[userId]
const eventGroups: {[key: string]: any[]} = {}
let eventName = ''
let groupKey = ''
events.forEach((event, index) => {
if (event.name !== eventName) groupKey = `${event.name}_${index}`
if (!eventGroups[groupKey]) eventGroups[groupKey] = []
eventGroups[groupKey].push(event)
eventName = event.name
})
const user = unBannedUsers.find((u) => u.id === userId)
return (
<Col className={'mt-4 min-w-[15rem]'} key={userId}>
<Row
className={clsx(
'rounded-md p-1',
// user && isUserLikelySpammer(user) ? 'bg-amber-100' : ''
)}
>
{user ? <UserAvatarAndBadge user={user} /> : userId}
</Row>
<ul>
<li>{new Date(events[0].ts!).toLocaleString()}</li>
</ul>
<Col>
{Object.values(eventGroups).map((group, index) => {
const name = group[0].name
const times = group.length
const timePeriod =
new Date(group[times - 1].ts!).valueOf() - new Date(group[0].ts!).valueOf()
const duration = Math.round(timePeriod / 1000)
return (
<li key={index}>
{name} {times > 1 ? `${times}x` : ' '}
{duration > 1 ? ` (${duration}s)` : ' '}
</li>
)
})}
</Col>
<ul>
<li>{new Date(events[events.length - 1].ts!).toLocaleString()}</li>
</ul>
</Col>
)
})}
</Row>
</div>
</Row>
)
}