Denormalize user display fields onto bets (#853)

* Denormalize user display fields onto bets

* Make bet denormalization script fast enough to run it on prod

* Make `placeBet`/`sellShares` immediately post denormalized info
This commit is contained in:
Marshall Polaris 2022-09-14 01:33:59 -07:00 committed by GitHub
parent 1ebb505752
commit 7144e57c93
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 180 additions and 116 deletions

View File

@ -15,9 +15,13 @@ import { Answer } from './answer'
export const HOUSE_LIQUIDITY_PROVIDER_ID = 'IPTOzEqrpkWmEzh6hwvAyY9PqFb2' // @ManifoldMarkets' id export const HOUSE_LIQUIDITY_PROVIDER_ID = 'IPTOzEqrpkWmEzh6hwvAyY9PqFb2' // @ManifoldMarkets' id
export const DEV_HOUSE_LIQUIDITY_PROVIDER_ID = '94YYTk1AFWfbWMpfYcvnnwI1veP2' // @ManifoldMarkets' id export const DEV_HOUSE_LIQUIDITY_PROVIDER_ID = '94YYTk1AFWfbWMpfYcvnnwI1veP2' // @ManifoldMarkets' id
export const UNIQUE_BETTOR_LIQUIDITY_AMOUNT = 20 export const UNIQUE_BETTOR_LIQUIDITY_AMOUNT = 20
type NormalizedBet<T extends Bet = Bet> = Omit<
T,
'userAvatarUrl' | 'userName' | 'userUsername'
>
export function getCpmmInitialLiquidity( export function getCpmmInitialLiquidity(
providerId: string, providerId: string,
contract: CPMMBinaryContract, contract: CPMMBinaryContract,
@ -53,7 +57,7 @@ export function getAnteBets(
const { createdTime } = contract const { createdTime } = contract
const yesBet: Bet = { const yesBet: NormalizedBet = {
id: yesAnteId, id: yesAnteId,
userId: creator.id, userId: creator.id,
contractId: contract.id, contractId: contract.id,
@ -67,7 +71,7 @@ export function getAnteBets(
fees: noFees, fees: noFees,
} }
const noBet: Bet = { const noBet: NormalizedBet = {
id: noAnteId, id: noAnteId,
userId: creator.id, userId: creator.id,
contractId: contract.id, contractId: contract.id,
@ -95,7 +99,7 @@ export function getFreeAnswerAnte(
const { createdTime } = contract const { createdTime } = contract
const anteBet: Bet = { const anteBet: NormalizedBet = {
id: anteBetId, id: anteBetId,
userId: anteBettorId, userId: anteBettorId,
contractId: contract.id, contractId: contract.id,
@ -125,7 +129,7 @@ export function getMultipleChoiceAntes(
const { createdTime } = contract const { createdTime } = contract
const bets: Bet[] = answers.map((answer, i) => ({ const bets: NormalizedBet[] = answers.map((answer, i) => ({
id: betDocIds[i], id: betDocIds[i],
userId: creator.id, userId: creator.id,
contractId: contract.id, contractId: contract.id,
@ -175,7 +179,7 @@ export function getNumericAnte(
range(0, bucketCount).map((_, i) => [i, betAnte]) range(0, bucketCount).map((_, i) => [i, betAnte])
) )
const anteBet: NumericBet = { const anteBet: NormalizedBet<NumericBet> = {
id: newBetId, id: newBetId,
userId: anteBettorId, userId: anteBettorId,
contractId: contract.id, contractId: contract.id,

View File

@ -3,6 +3,12 @@ import { Fees } from './fees'
export type Bet = { export type Bet = {
id: string id: string
userId: string userId: string
// denormalized for bet lists
userAvatarUrl?: string
userUsername: string
userName: string
contractId: string contractId: string
createdTime: number createdTime: number

View File

@ -31,7 +31,10 @@ import {
floatingLesserEqual, floatingLesserEqual,
} from './util/math' } from './util/math'
export type CandidateBet<T extends Bet = Bet> = Omit<T, 'id' | 'userId'> export type CandidateBet<T extends Bet = Bet> = Omit<
T,
'id' | 'userId' | 'userAvatarUrl' | 'userName' | 'userUsername'
>
export type BetInfo = { export type BetInfo = {
newBet: CandidateBet newBet: CandidateBet
newPool?: { [outcome: string]: number } newPool?: { [outcome: string]: number }
@ -322,7 +325,7 @@ export const getNewBinaryDpmBetInfo = (
export const getNewMultiBetInfo = ( export const getNewMultiBetInfo = (
outcome: string, outcome: string,
amount: number, amount: number,
contract: FreeResponseContract | MultipleChoiceContract, contract: FreeResponseContract | MultipleChoiceContract
) => { ) => {
const { pool, totalShares, totalBets } = contract const { pool, totalShares, totalBets } = contract

View File

@ -9,7 +9,10 @@ import { CPMMContract, DPMContract } from './contract'
import { DPM_CREATOR_FEE, DPM_PLATFORM_FEE, Fees } from './fees' import { DPM_CREATOR_FEE, DPM_PLATFORM_FEE, Fees } from './fees'
import { sumBy } from 'lodash' import { sumBy } from 'lodash'
export type CandidateBet<T extends Bet> = Omit<T, 'id' | 'userId'> export type CandidateBet<T extends Bet> = Omit<
T,
'id' | 'userId' | 'userAvatarUrl' | 'userName' | 'userUsername'
>
export const getSellBetInfo = (bet: Bet, contract: DPMContract) => { export const getSellBetInfo = (bet: Bet, contract: DPMContract) => {
const { pool, totalShares, totalBets } = contract const { pool, totalShares, totalBets } = contract

View File

@ -2,6 +2,7 @@ import * as admin from 'firebase-admin'
import { z } from 'zod' import { z } from 'zod'
import { getUser } from './utils' import { getUser } from './utils'
import { Bet } from '../../common/bet'
import { Contract } from '../../common/contract' import { Contract } from '../../common/contract'
import { Comment } from '../../common/comment' import { Comment } from '../../common/comment'
import { User } from '../../common/user' import { User } from '../../common/user'
@ -68,10 +69,21 @@ export const changeUser = async (
.get() .get()
const answerUpdate: Partial<Answer> = removeUndefinedProps(update) const answerUpdate: Partial<Answer> = removeUndefinedProps(update)
const betsSnap = await firestore
.collectionGroup('bets')
.where('userId', '==', user.id)
.get()
const betsUpdate: Partial<Bet> = removeUndefinedProps({
userName: update.name,
userUsername: update.username,
userAvatarUrl: update.avatarUrl,
})
const bulkWriter = firestore.bulkWriter() const bulkWriter = firestore.bulkWriter()
commentSnap.docs.forEach((d) => bulkWriter.update(d.ref, commentUpdate)) commentSnap.docs.forEach((d) => bulkWriter.update(d.ref, commentUpdate))
answerSnap.docs.forEach((d) => bulkWriter.update(d.ref, answerUpdate)) answerSnap.docs.forEach((d) => bulkWriter.update(d.ref, answerUpdate))
contracts.docs.forEach((d) => bulkWriter.update(d.ref, contractUpdate)) contracts.docs.forEach((d) => bulkWriter.update(d.ref, contractUpdate))
betsSnap.docs.forEach((d) => bulkWriter.update(d.ref, betsUpdate))
await bulkWriter.flush() await bulkWriter.flush()
console.log('Done writing!') console.log('Done writing!')

View File

@ -61,6 +61,12 @@ export const onCreateBet = functions
const bettor = await getUser(bet.userId) const bettor = await getUser(bet.userId)
if (!bettor) return if (!bettor) return
await change.ref.update({
userAvatarUrl: bettor.avatarUrl,
userName: bettor.name,
userUsername: bettor.username,
})
await updateUniqueBettorsAndGiveCreatorBonus(contract, eventId, bettor) await updateUniqueBettorsAndGiveCreatorBonus(contract, eventId, bettor)
await notifyFills(bet, contract, eventId, bettor) await notifyFills(bet, contract, eventId, bettor)
await updateBettingStreak(bettor, bet, contract, eventId) await updateBettingStreak(bettor, bet, contract, eventId)

View File

@ -139,7 +139,14 @@ export const placebet = newEndpoint({}, async (req, auth) => {
} }
const betDoc = contractDoc.collection('bets').doc() const betDoc = contractDoc.collection('bets').doc()
trans.create(betDoc, { id: betDoc.id, userId: user.id, ...newBet }) trans.create(betDoc, {
id: betDoc.id,
userId: user.id,
userAvatarUrl: user.avatarUrl,
userUsername: user.username,
userName: user.name,
...newBet,
})
log('Created new bet document.') log('Created new bet document.')
if (makers) { if (makers) {

View File

@ -3,12 +3,7 @@
import * as admin from 'firebase-admin' import * as admin from 'firebase-admin'
import { initAdmin } from './script-init' import { initAdmin } from './script-init'
import { import { findDiffs, describeDiff, applyDiff } from './denormalize'
DocumentCorrespondence,
findDiffs,
describeDiff,
applyDiff,
} from './denormalize'
import { DocumentSnapshot, Transaction } from 'firebase-admin/firestore' import { DocumentSnapshot, Transaction } from 'firebase-admin/firestore'
initAdmin() initAdmin()
@ -79,43 +74,36 @@ if (require.main === module) {
getAnswersByUserId(transaction), getAnswersByUserId(transaction),
]) ])
const usersContracts = Array.from( const usersContracts = Array.from(usersById.entries(), ([id, doc]) => {
usersById.entries(), return [doc, contractsByUserId.get(id) || []] as const
([id, doc]): DocumentCorrespondence => { })
return [doc, contractsByUserId.get(id) || []] const contractDiffs = findDiffs(usersContracts, [
}
)
const contractDiffs = findDiffs(
usersContracts,
'avatarUrl', 'avatarUrl',
'creatorAvatarUrl' 'creatorAvatarUrl',
) ])
console.log(`Found ${contractDiffs.length} contracts with mismatches.`) console.log(`Found ${contractDiffs.length} contracts with mismatches.`)
contractDiffs.forEach((d) => { contractDiffs.forEach((d) => {
console.log(describeDiff(d)) console.log(describeDiff(d))
applyDiff(transaction, d) applyDiff(transaction, d)
}) })
const usersComments = Array.from( const usersComments = Array.from(usersById.entries(), ([id, doc]) => {
usersById.entries(), return [doc, commentsByUserId.get(id) || []] as const
([id, doc]): DocumentCorrespondence => { })
return [doc, commentsByUserId.get(id) || []] const commentDiffs = findDiffs(usersComments, [
} 'avatarUrl',
) 'userAvatarUrl',
const commentDiffs = findDiffs(usersComments, 'avatarUrl', 'userAvatarUrl') ])
console.log(`Found ${commentDiffs.length} comments with mismatches.`) console.log(`Found ${commentDiffs.length} comments with mismatches.`)
commentDiffs.forEach((d) => { commentDiffs.forEach((d) => {
console.log(describeDiff(d)) console.log(describeDiff(d))
applyDiff(transaction, d) applyDiff(transaction, d)
}) })
const usersAnswers = Array.from( const usersAnswers = Array.from(usersById.entries(), ([id, doc]) => {
usersById.entries(), return [doc, answersByUserId.get(id) || []] as const
([id, doc]): DocumentCorrespondence => { })
return [doc, answersByUserId.get(id) || []] const answerDiffs = findDiffs(usersAnswers, ['avatarUrl', 'avatarUrl'])
}
)
const answerDiffs = findDiffs(usersAnswers, 'avatarUrl', 'avatarUrl')
console.log(`Found ${answerDiffs.length} answers with mismatches.`) console.log(`Found ${answerDiffs.length} answers with mismatches.`)
answerDiffs.forEach((d) => { answerDiffs.forEach((d) => {
console.log(describeDiff(d)) console.log(describeDiff(d))

View File

@ -0,0 +1,38 @@
// Filling in the user-based fields on bets.
import * as admin from 'firebase-admin'
import { initAdmin } from './script-init'
import { findDiffs, describeDiff, getDiffUpdate } from './denormalize'
import { log, writeAsync } from '../utils'
initAdmin()
const firestore = admin.firestore()
// not in a transaction for speed -- may need to be run more than once
async function denormalize() {
const users = await firestore.collection('users').get()
log(`Found ${users.size} users.`)
for (const userDoc of users.docs) {
const userBets = await firestore
.collectionGroup('bets')
.where('userId', '==', userDoc.id)
.get()
const mapping = [[userDoc, userBets.docs] as const] as const
const diffs = findDiffs(
mapping,
['avatarUrl', 'userAvatarUrl'],
['name', 'userName'],
['username', 'userUsername']
)
log(`Found ${diffs.length} bets with mismatched user data.`)
const updates = diffs.map((d) => {
log(describeDiff(d))
return getDiffUpdate(d)
})
await writeAsync(firestore, updates)
}
}
if (require.main === module) {
denormalize().catch((e) => console.error(e))
}

View File

@ -3,12 +3,7 @@
import * as admin from 'firebase-admin' import * as admin from 'firebase-admin'
import { zip } from 'lodash' import { zip } from 'lodash'
import { initAdmin } from './script-init' import { initAdmin } from './script-init'
import { import { findDiffs, describeDiff, applyDiff } from './denormalize'
DocumentCorrespondence,
findDiffs,
describeDiff,
applyDiff,
} from './denormalize'
import { log } from '../utils' import { log } from '../utils'
import { Transaction } from 'firebase-admin/firestore' import { Transaction } from 'firebase-admin/firestore'
@ -41,17 +36,20 @@ async function denormalize() {
) )
) )
log(`Found ${bets.length} bets associated with comments.`) log(`Found ${bets.length} bets associated with comments.`)
const mapping = zip(bets, betComments)
.map(([bet, comment]): DocumentCorrespondence => {
return [bet!, [comment!]] // eslint-disable-line
})
.filter(([bet, _]) => bet.exists) // dev DB has some invalid bet IDs
const amountDiffs = findDiffs(mapping, 'amount', 'betAmount') // dev DB has some invalid bet IDs
const outcomeDiffs = findDiffs(mapping, 'outcome', 'betOutcome') const mapping = zip(bets, betComments)
log(`Found ${amountDiffs.length} comments with mismatched amounts.`) .filter(([bet, _]) => bet!.exists) // eslint-disable-line
log(`Found ${outcomeDiffs.length} comments with mismatched outcomes.`) .map(([bet, comment]) => {
const diffs = amountDiffs.concat(outcomeDiffs) return [bet!, [comment!]] as const // eslint-disable-line
})
const diffs = findDiffs(
mapping,
['amount', 'betAmount'],
['outcome', 'betOutcome']
)
log(`Found ${diffs.length} comments with mismatched data.`)
diffs.slice(0, 500).forEach((d) => { diffs.slice(0, 500).forEach((d) => {
log(describeDiff(d)) log(describeDiff(d))
applyDiff(trans, d) applyDiff(trans, d)

View File

@ -2,12 +2,7 @@
import * as admin from 'firebase-admin' import * as admin from 'firebase-admin'
import { initAdmin } from './script-init' import { initAdmin } from './script-init'
import { import { findDiffs, describeDiff, applyDiff } from './denormalize'
DocumentCorrespondence,
findDiffs,
describeDiff,
applyDiff,
} from './denormalize'
import { DocumentSnapshot, Transaction } from 'firebase-admin/firestore' import { DocumentSnapshot, Transaction } from 'firebase-admin/firestore'
initAdmin() initAdmin()
@ -43,16 +38,15 @@ async function denormalize() {
getContractsById(transaction), getContractsById(transaction),
getCommentsByContractId(transaction), getCommentsByContractId(transaction),
]) ])
const mapping = Object.entries(contractsById).map( const mapping = Object.entries(contractsById).map(([id, doc]) => {
([id, doc]): DocumentCorrespondence => { return [doc, commentsByContractId.get(id) || []] as const
return [doc, commentsByContractId.get(id) || []] })
} const diffs = findDiffs(
mapping,
['slug', 'contractSlug'],
['question', 'contractQuestion']
) )
const slugDiffs = findDiffs(mapping, 'slug', 'contractSlug') console.log(`Found ${diffs.length} comments with mismatched data.`)
const qDiffs = findDiffs(mapping, 'question', 'contractQuestion')
console.log(`Found ${slugDiffs.length} comments with mismatched slugs.`)
console.log(`Found ${qDiffs.length} comments with mismatched questions.`)
const diffs = slugDiffs.concat(qDiffs)
diffs.slice(0, 500).forEach((d) => { diffs.slice(0, 500).forEach((d) => {
console.log(describeDiff(d)) console.log(describeDiff(d))
applyDiff(transaction, d) applyDiff(transaction, d)

View File

@ -2,32 +2,40 @@
// another set of documents. // another set of documents.
import { DocumentSnapshot, Transaction } from 'firebase-admin/firestore' import { DocumentSnapshot, Transaction } from 'firebase-admin/firestore'
import { isEqual, zip } from 'lodash'
import { UpdateSpec } from '../utils'
export type DocumentValue = { export type DocumentValue = {
doc: DocumentSnapshot doc: DocumentSnapshot
field: string fields: string[]
val: unknown vals: unknown[]
} }
export type DocumentCorrespondence = [DocumentSnapshot, DocumentSnapshot[]] export type DocumentMapping = readonly [
DocumentSnapshot,
readonly DocumentSnapshot[]
]
export type DocumentDiff = { export type DocumentDiff = {
src: DocumentValue src: DocumentValue
dest: DocumentValue dest: DocumentValue
} }
type PathPair = readonly [string, string]
export function findDiffs( export function findDiffs(
docs: DocumentCorrespondence[], docs: readonly DocumentMapping[],
srcPath: string, ...paths: PathPair[]
destPath: string
) { ) {
const diffs: DocumentDiff[] = [] const diffs: DocumentDiff[] = []
const srcPaths = paths.map((p) => p[0])
const destPaths = paths.map((p) => p[1])
for (const [srcDoc, destDocs] of docs) { for (const [srcDoc, destDocs] of docs) {
const srcVal = srcDoc.get(srcPath) const srcVals = srcPaths.map((p) => srcDoc.get(p))
for (const destDoc of destDocs) { for (const destDoc of destDocs) {
const destVal = destDoc.get(destPath) const destVals = destPaths.map((p) => destDoc.get(p))
if (destVal !== srcVal) { if (!isEqual(srcVals, destVals)) {
diffs.push({ diffs.push({
src: { doc: srcDoc, field: srcPath, val: srcVal }, src: { doc: srcDoc, fields: srcPaths, vals: srcVals },
dest: { doc: destDoc, field: destPath, val: destVal }, dest: { doc: destDoc, fields: destPaths, vals: destVals },
}) })
} }
} }
@ -37,12 +45,19 @@ export function findDiffs(
export function describeDiff(diff: DocumentDiff) { export function describeDiff(diff: DocumentDiff) {
function describeDocVal(x: DocumentValue): string { function describeDocVal(x: DocumentValue): string {
return `${x.doc.ref.path}.${x.field}: ${x.val}` return `${x.doc.ref.path}.[${x.fields.join('|')}]: [${x.vals.join('|')}]`
} }
return `${describeDocVal(diff.src)} -> ${describeDocVal(diff.dest)}` return `${describeDocVal(diff.src)} -> ${describeDocVal(diff.dest)}`
} }
export function applyDiff(transaction: Transaction, diff: DocumentDiff) { export function getDiffUpdate(diff: DocumentDiff) {
const { src, dest } = diff return {
transaction.update(dest.doc.ref, dest.field, src.val) doc: diff.dest.doc.ref,
fields: Object.fromEntries(zip(diff.dest.fields, diff.src.vals)),
} as UpdateSpec
}
export function applyDiff(transaction: Transaction, diff: DocumentDiff) {
const update = getDiffUpdate(diff)
transaction.update(update.doc, update.fields)
} }

View File

@ -112,6 +112,9 @@ export const sellshares = newEndpoint({}, async (req, auth) => {
transaction.create(newBetDoc, { transaction.create(newBetDoc, {
id: newBetDoc.id, id: newBetDoc.id,
userId: user.id, userId: user.id,
userAvatarUrl: user.avatarUrl,
userUsername: user.username,
userName: user.name,
...newBet, ...newBet,
}) })
transaction.update( transaction.update(

View File

@ -6,7 +6,6 @@ import { formatMoney } from 'common/util/format'
import { groupBy, mapValues, sumBy, sortBy, keyBy } from 'lodash' import { groupBy, mapValues, sumBy, sortBy, keyBy } from 'lodash'
import { useState, useMemo, useEffect } from 'react' import { useState, useMemo, useEffect } from 'react'
import { CommentTipMap } from 'web/hooks/use-tip-txns' import { CommentTipMap } from 'web/hooks/use-tip-txns'
import { useUserById } from 'web/hooks/use-user'
import { listUsers, User } from 'web/lib/firebase/users' import { listUsers, User } from 'web/lib/firebase/users'
import { FeedBet } from '../feed/feed-bets' import { FeedBet } from '../feed/feed-bets'
import { FeedComment } from '../feed/feed-comments' import { FeedComment } from '../feed/feed-comments'
@ -88,7 +87,7 @@ export function ContractTopTrades(props: {
// Now find the betId with the highest profit // Now find the betId with the highest profit
const topBetId = sortBy(bets, (b) => -profitById[b.id])[0]?.id const topBetId = sortBy(bets, (b) => -profitById[b.id])[0]?.id
const topBettor = useUserById(betsById[topBetId]?.userId) const topBettor = betsById[topBetId]?.userName
// And also the commentId of the comment with the highest profit // And also the commentId of the comment with the highest profit
const topCommentId = sortBy( const topCommentId = sortBy(
@ -121,7 +120,7 @@ export function ContractTopTrades(props: {
<FeedBet contract={contract} bet={betsById[topBetId]} /> <FeedBet contract={contract} bet={betsById[topBetId]} />
</div> </div>
<div className="mt-2 ml-2 text-sm text-gray-500"> <div className="mt-2 ml-2 text-sm text-gray-500">
{topBettor?.name} made {formatMoney(profitById[topBetId] || 0)}! {topBettor} made {formatMoney(profitById[topBetId] || 0)}!
</div> </div>
</> </>
)} )}

View File

@ -1,8 +1,7 @@
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { Contract } from 'common/contract' import { Contract } from 'common/contract'
import { Bet } from 'common/bet' import { Bet } from 'common/bet'
import { User } from 'common/user' import { useUser } from 'web/hooks/use-user'
import { useUser, useUserById } from 'web/hooks/use-user'
import { Row } from 'web/components/layout/row' import { Row } from 'web/components/layout/row'
import { Avatar, EmptyAvatar } from 'web/components/avatar' import { Avatar, EmptyAvatar } from 'web/components/avatar'
import clsx from 'clsx' import clsx from 'clsx'
@ -18,29 +17,20 @@ import { UserLink } from 'web/components/user-link'
export function FeedBet(props: { contract: Contract; bet: Bet }) { export function FeedBet(props: { contract: Contract; bet: Bet }) {
const { contract, bet } = props const { contract, bet } = props
const { userId, createdTime } = bet const { userAvatarUrl, userUsername, createdTime } = bet
const showUser = dayjs(createdTime).isAfter('2022-06-01')
const isBeforeJune2022 = dayjs(createdTime).isBefore('2022-06-01')
// eslint-disable-next-line react-hooks/rules-of-hooks
const bettor = isBeforeJune2022 ? undefined : useUserById(userId)
const user = useUser()
const isSelf = user?.id === userId
return ( return (
<Row className="items-center gap-2 pt-3"> <Row className="items-center gap-2 pt-3">
{isSelf ? ( {showUser ? (
<Avatar avatarUrl={user.avatarUrl} username={user.username} /> <Avatar avatarUrl={userAvatarUrl} username={userUsername} />
) : bettor ? (
<Avatar avatarUrl={bettor.avatarUrl} username={bettor.username} />
) : ( ) : (
<EmptyAvatar className="mx-1" /> <EmptyAvatar className="mx-1" />
)} )}
<BetStatusText <BetStatusText
bet={bet} bet={bet}
contract={contract} contract={contract}
isSelf={isSelf} hideUser={!showUser}
bettor={bettor}
className="flex-1" className="flex-1"
/> />
</Row> </Row>
@ -50,13 +40,13 @@ export function FeedBet(props: { contract: Contract; bet: Bet }) {
export function BetStatusText(props: { export function BetStatusText(props: {
contract: Contract contract: Contract
bet: Bet bet: Bet
isSelf: boolean hideUser?: boolean
bettor?: User
hideOutcome?: boolean hideOutcome?: boolean
className?: string className?: string
}) { }) {
const { bet, contract, bettor, isSelf, hideOutcome, className } = props const { bet, contract, hideUser, hideOutcome, className } = props
const { outcomeType } = contract const { outcomeType } = contract
const self = useUser()
const isPseudoNumeric = outcomeType === 'PSEUDO_NUMERIC' const isPseudoNumeric = outcomeType === 'PSEUDO_NUMERIC'
const isFreeResponse = outcomeType === 'FREE_RESPONSE' const isFreeResponse = outcomeType === 'FREE_RESPONSE'
const { amount, outcome, createdTime, challengeSlug } = bet const { amount, outcome, createdTime, challengeSlug } = bet
@ -101,10 +91,10 @@ export function BetStatusText(props: {
return ( return (
<div className={clsx('text-sm text-gray-500', className)}> <div className={clsx('text-sm text-gray-500', className)}>
{bettor ? ( {!hideUser ? (
<UserLink name={bettor.name} username={bettor.username} /> <UserLink name={bet.userName} username={bet.userUsername} />
) : ( ) : (
<span>{isSelf ? 'You' : 'A trader'}</span> <span>{self?.id === bet.userId ? 'You' : 'A trader'}</span>
)}{' '} )}{' '}
{bought} {money} {bought} {money}
{outOfTotalAmount} {outOfTotalAmount}

View File

@ -4,7 +4,7 @@ import { getFormattedMappedValue } from 'common/pseudo-numeric'
import { formatMoney, formatPercent } from 'common/util/format' import { formatMoney, formatPercent } from 'common/util/format'
import { sortBy } from 'lodash' import { sortBy } from 'lodash'
import { useState } from 'react' import { useState } from 'react'
import { useUser, useUserById } from 'web/hooks/use-user' import { useUser } from 'web/hooks/use-user'
import { cancelBet } from 'web/lib/firebase/api' import { cancelBet } from 'web/lib/firebase/api'
import { Avatar } from './avatar' import { Avatar } from './avatar'
import { Button } from './button' import { Button } from './button'
@ -109,16 +109,14 @@ function LimitBet(props: {
setIsCancelling(true) setIsCancelling(true)
} }
const user = useUserById(bet.userId)
return ( return (
<tr> <tr>
{!isYou && ( {!isYou && (
<td> <td>
<Avatar <Avatar
size={'sm'} size={'sm'}
avatarUrl={user?.avatarUrl} avatarUrl={bet.userAvatarUrl}
username={user?.username} username={bet.userUsername}
/> />
</td> </td>
)} )}