Try new way of calculating rankings for large groups

This commit is contained in:
Ian Philips 2022-07-14 09:09:12 -06:00
parent eb6b1b9f89
commit 4eba3c8124
5 changed files with 63 additions and 67 deletions

View File

@ -7,7 +7,7 @@ import {
listenForGroups,
listenForMemberGroups,
} from 'web/lib/firebase/groups'
import { getUser } from 'web/lib/firebase/users'
import { getUser, getUsers } from 'web/lib/firebase/users'
import { filterDefined } from 'common/util/array'
export const useGroup = (groupId: string | undefined) => {
@ -85,9 +85,14 @@ export function useMembers(group: Group, max?: number) {
}
export async function listMembers(group: Group, max?: number) {
return await Promise.all(
group.memberIds.slice(0, max ? max : group.memberIds.length).map(getUser)
)
const { memberIds } = group
const numToRetrieve = max ?? memberIds.length
if (memberIds.length === 0) return []
if (numToRetrieve)
return (await getUsers()).filter((user) =>
group.memberIds.includes(user.id)
)
return await Promise.all(group.memberIds.slice(0, numToRetrieve).map(getUser))
}
export const useGroupsWithContract = (contractId: string | undefined) => {

View File

@ -124,6 +124,14 @@ export async function listContracts(creatorId: string): Promise<Contract[]> {
return snapshot.docs.map((doc) => doc.data())
}
export async function listContractsByGroupSlug(
slug: string
): Promise<Contract[]> {
const q = query(contracts, where('groupSlugs', 'array-contains', slug))
const snapshot = await getDocs(q)
return snapshot.docs.map((doc) => doc.data())
}
export async function listTaggedContractsCaseInsensitive(
tag: string
): Promise<Contract[]> {

View File

@ -8,7 +8,7 @@ import {
} from 'firebase/firestore'
import { sortBy, uniq } from 'lodash'
import { Group } from 'common/group'
import { getContractFromId, updateContract } from './contracts'
import { updateContract } from './contracts'
import {
coll,
getValue,
@ -16,7 +16,6 @@ import {
listenForValue,
listenForValues,
} from './utils'
import { filterDefined } from 'common/util/array'
import { Contract } from 'common/contract'
export const groups = coll<Group>('groups')
@ -54,21 +53,6 @@ export async function getGroupBySlug(slug: string) {
return docs.length === 0 ? null : docs[0].data()
}
export async function getGroupContracts(group: Group) {
const { contractIds } = group
const contracts =
filterDefined(
await Promise.all(
contractIds.map(async (contractId) => {
return await getContractFromId(contractId)
})
)
) ?? []
return [...contracts]
}
export function listenForGroup(
groupId: string,
setGroup: (group: Group | null) => void

View File

@ -3,14 +3,13 @@ import { take, sortBy, debounce } from 'lodash'
import { Group } from 'common/group'
import { Page } from 'web/components/page'
import { listAllBets } from 'web/lib/firebase/bets'
import { Contract } from 'web/lib/firebase/contracts'
import { Contract, listContractsByGroupSlug } from 'web/lib/firebase/contracts'
import {
groupPath,
getGroupBySlug,
updateGroup,
addUserToGroup,
addContractToGroup,
getGroupContracts,
} from 'web/lib/firebase/groups'
import { Row } from 'web/components/layout/row'
import { UserLink } from 'web/components/user-page'
@ -22,7 +21,7 @@ import {
} from 'web/lib/firebase/users'
import { Col } from 'web/components/layout/col'
import { useUser } from 'web/hooks/use-user'
import { listMembers, useGroup, useMembers } from 'web/hooks/use-group'
import { listMembers, useGroup } from 'web/hooks/use-group'
import { useRouter } from 'next/router'
import { scoreCreators, scoreTraders } from 'common/scoring'
import { Leaderboard } from 'web/components/leaderboard'
@ -62,14 +61,11 @@ export async function getStaticPropz(props: { params: { slugs: string[] } }) {
const { slugs } = props.params
const group = await getGroupBySlug(slugs[0])
const members =
group && group.memberIds.length < 100 ? await listMembers(group) : []
const members = group && (await listMembers(group))
const creatorPromise = group ? getUser(group.creatorId) : null
const contracts =
group && group.contractIds.length < 100
? await getGroupContracts(group).catch((_) => [])
: []
(group && (await listContractsByGroupSlug(group.slug))) ?? []
const bets = await Promise.all(
contracts.map((contract: Contract) => listAllBets(contract.id))
@ -77,10 +73,12 @@ export async function getStaticPropz(props: { params: { slugs: string[] } }) {
const creatorScores = scoreCreators(contracts)
const traderScores = scoreTraders(contracts, bets)
const [topCreators, topTraders] = await Promise.all([
toTopUsers(creatorScores),
toTopUsers(traderScores),
])
const [topCreators, topTraders] =
(members && [
toTopUsers(creatorScores, members),
toTopUsers(traderScores, members),
]) ??
[]
const creator = await creatorPromise
@ -99,14 +97,14 @@ export async function getStaticPropz(props: { params: { slugs: string[] } }) {
}
}
async function toTopUsers(userScores: { [userId: string]: number }) {
function toTopUsers(userScores: { [userId: string]: number }, users: User[]) {
const topUserPairs = take(
sortBy(Object.entries(userScores), ([_, score]) => -1 * score),
10
).filter(([_, score]) => score >= 0.5)
const topUsers = await Promise.all(
topUserPairs.map(([userId]) => getUser(userId))
const topUsers = topUserPairs.map(
([userId]) => users.filter((user) => user.id === userId)[0]
)
return topUsers.filter((user) => user)
}
@ -199,6 +197,7 @@ export default function GroupPage(props: {
creator={creator}
isCreator={!!isCreator}
user={user}
members={members}
/>
</Col>
)
@ -327,8 +326,9 @@ function GroupOverview(props: {
creator: User
user: User | null | undefined
isCreator: boolean
members: User[]
}) {
const { group, creator, isCreator, user } = props
const { group, creator, isCreator, user, members } = props
const anyoneCanJoinChoices: { [key: string]: string } = {
Closed: 'false',
Open: 'true',
@ -403,7 +403,7 @@ function GroupOverview(props: {
</Row>
)}
<Col className={'mt-2'}>
<GroupMemberSearch group={group} />
<GroupMemberSearch members={members} />
</Col>
</Col>
</>
@ -426,10 +426,9 @@ function SearchBar(props: { setQuery: (query: string) => void }) {
)
}
function GroupMemberSearch(props: { group: Group }) {
function GroupMemberSearch(props: { members: User[] }) {
const [query, setQuery] = useState('')
const { group } = props
const members = useMembers(group, 100)
const { members } = props
// TODO use find-active-contracts to sort by?
const matches = sortBy(members, [(member) => member.name]).filter(
@ -455,29 +454,6 @@ function GroupMemberSearch(props: { group: Group }) {
)
}
export function GroupMembersList(props: { group: Group }) {
const { group } = props
const maxMembersToShow = 3
const members = useMembers(group, maxMembersToShow).filter(
(m) => m.id !== group.creatorId
)
if (group.memberIds.length === 1) return <div />
return (
<div className="text-neutral flex flex-wrap gap-1">
<span className={'text-gray-500'}>Other members</span>
{members.slice(0, maxMembersToShow).map((member, i) => (
<div key={member.id} className={'flex-shrink'}>
<UserLink name={member.name} username={member.username} />
{members.length > 1 && i !== members.length - 1 && <span>,</span>}
</div>
))}
{group.memberIds.length > maxMembersToShow && (
<span> & {group.memberIds.length - maxMembersToShow} more</span>
)}
</div>
)
}
function SortedLeaderboard(props: {
users: User[]
scoreFunction: (user: User) => number

View File

@ -1,23 +1,23 @@
import { sortBy, debounce } from 'lodash'
import Link from 'next/link'
import { useEffect, useState } from 'react'
import React, { useEffect, useState } from 'react'
import { Group } from 'common/group'
import { CreateGroupButton } from 'web/components/groups/create-group-button'
import { Col } from 'web/components/layout/col'
import { Row } from 'web/components/layout/row'
import { Page } from 'web/components/page'
import { Title } from 'web/components/title'
import { useGroups, useMemberGroupIds } from 'web/hooks/use-group'
import { useGroups, useMemberGroupIds, useMembers } from 'web/hooks/use-group'
import { useUser } from 'web/hooks/use-user'
import { groupPath, listAllGroups } from 'web/lib/firebase/groups'
import { getUser, User } from 'web/lib/firebase/users'
import { Tabs } from 'web/components/layout/tabs'
import { GroupMembersList } from 'web/pages/group/[...slugs]'
import { checkAgainstQuery } from 'web/hooks/use-sort-and-query-params'
import { SiteLink } from 'web/components/site-link'
import clsx from 'clsx'
import { Avatar } from 'web/components/avatar'
import { JoinOrLeaveGroupButton } from 'web/components/groups/groups-button'
import { UserLink } from 'web/components/user-page'
export async function getStaticProps() {
const groups = await listAllGroups().catch((_) => [])
@ -201,6 +201,29 @@ export function GroupCard(props: { group: Group; creator: User | undefined }) {
)
}
function GroupMembersList(props: { group: Group }) {
const { group } = props
const maxMembersToShow = 3
const members = useMembers(group, maxMembersToShow).filter(
(m) => m.id !== group.creatorId
)
if (group.memberIds.length === 1) return <div />
return (
<div className="text-neutral flex flex-wrap gap-1">
<span className={'text-gray-500'}>Other members</span>
{members.slice(0, maxMembersToShow).map((member, i) => (
<div key={member.id} className={'flex-shrink'}>
<UserLink name={member.name} username={member.username} />
{members.length > 1 && i !== members.length - 1 && <span>,</span>}
</div>
))}
{group.memberIds.length > maxMembersToShow && (
<span> & {group.memberIds.length - maxMembersToShow} more</span>
)}
</div>
)
}
export function GroupLink(props: { group: Group; className?: string }) {
const { group, className } = props