Merge branch 'main' into theoremone
This commit is contained in:
commit
a48755b283
18
common/util/promise.ts
Normal file
18
common/util/promise.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
export const batchedWaitAll = async <T>(
|
||||||
|
createPromises: (() => Promise<T>)[],
|
||||||
|
batchSize = 10
|
||||||
|
) => {
|
||||||
|
const numBatches = Math.ceil(createPromises.length / batchSize)
|
||||||
|
const result: T[] = []
|
||||||
|
for (let batchIndex = 0; batchIndex < numBatches; batchIndex++) {
|
||||||
|
const from = batchIndex * batchSize
|
||||||
|
const to = from + batchSize
|
||||||
|
|
||||||
|
const promises = createPromises.slice(from, to).map((f) => f())
|
||||||
|
|
||||||
|
const batch = await Promise.all(promises)
|
||||||
|
result.push(...batch)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ import * as _ from 'lodash'
|
||||||
import { getValues } from './utils'
|
import { getValues } from './utils'
|
||||||
import { Contract } from '../../common/contract'
|
import { Contract } from '../../common/contract'
|
||||||
import { Bet } from '../../common/bet'
|
import { Bet } from '../../common/bet'
|
||||||
|
import { batchedWaitAll } from '../../common/util/promise'
|
||||||
|
|
||||||
const firestore = admin.firestore()
|
const firestore = admin.firestore()
|
||||||
|
|
||||||
|
@ -17,8 +18,8 @@ export const updateContractMetrics = functions.pubsub
|
||||||
firestore.collection('contracts')
|
firestore.collection('contracts')
|
||||||
)
|
)
|
||||||
|
|
||||||
await Promise.all(
|
await batchedWaitAll(
|
||||||
contracts.map(async (contract) => {
|
contracts.map((contract) => async () => {
|
||||||
const volume24Hours = await computeVolumeFrom(contract, oneDay)
|
const volume24Hours = await computeVolumeFrom(contract, oneDay)
|
||||||
const volume7Days = await computeVolumeFrom(contract, oneDay * 7)
|
const volume7Days = await computeVolumeFrom(contract, oneDay * 7)
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { Contract } from '../../common/contract'
|
||||||
import { Bet } from '../../common/bet'
|
import { Bet } from '../../common/bet'
|
||||||
import { User } from '../../common/user'
|
import { User } from '../../common/user'
|
||||||
import { calculatePayout } from '../../common/calculate'
|
import { calculatePayout } from '../../common/calculate'
|
||||||
|
import { batchedWaitAll } from '../../common/util/promise'
|
||||||
|
|
||||||
const firestore = admin.firestore()
|
const firestore = admin.firestore()
|
||||||
|
|
||||||
|
@ -22,8 +23,8 @@ export const updateUserMetrics = functions.pubsub
|
||||||
contracts.map((contract) => [contract.id, contract])
|
contracts.map((contract) => [contract.id, contract])
|
||||||
)
|
)
|
||||||
|
|
||||||
await Promise.all(
|
await batchedWaitAll(
|
||||||
users.map(async (user) => {
|
users.map((user) => async () => {
|
||||||
const [investmentValue, creatorVolume] = await Promise.all([
|
const [investmentValue, creatorVolume] = await Promise.all([
|
||||||
computeInvestmentValue(user, contractsDict),
|
computeInvestmentValue(user, contractsDict),
|
||||||
computeTotalPool(user, contractsDict),
|
computeTotalPool(user, contractsDict),
|
||||||
|
|
|
@ -334,7 +334,9 @@ const getTweetText = (contract: Contract, isCreator: boolean) => {
|
||||||
contract
|
contract
|
||||||
)} chance, place your bets here:`
|
)} chance, place your bets here:`
|
||||||
: `Submit your own answer:`
|
: `Submit your own answer:`
|
||||||
const url = `https://manifold.markets${contractPath(contract)}`
|
|
||||||
|
const timeParam = `${Date.now()}`.substring(7)
|
||||||
|
const url = `https://manifold.markets${contractPath(contract)}?t=${timeParam}`
|
||||||
|
|
||||||
return `${tweetQuestion}\n\n${tweetDescription}\n\n${url}`
|
return `${tweetQuestion}\n\n${tweetDescription}\n\n${url}`
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,10 +93,12 @@ function Timestamp(props: { time: number }) {
|
||||||
|
|
||||||
function FeedBet(props: { activityItem: any; feedType: FeedType }) {
|
function FeedBet(props: { activityItem: any; feedType: FeedType }) {
|
||||||
const { activityItem, feedType } = props
|
const { activityItem, feedType } = props
|
||||||
const { id, contractId, amount, outcome, createdTime } = activityItem
|
const { id, contractId, amount, outcome, createdTime, contract } =
|
||||||
|
activityItem
|
||||||
const user = useUser()
|
const user = useUser()
|
||||||
const isSelf = user?.id == activityItem.userId
|
const isSelf = user?.id == activityItem.userId
|
||||||
// The creator can comment if the bet was posted in the last hour
|
const isCreator = contract.creatorId == activityItem.userId
|
||||||
|
// You can comment if your bet was posted in the last hour
|
||||||
const canComment = isSelf && Date.now() - createdTime < 60 * 60 * 1000
|
const canComment = isSelf && Date.now() - createdTime < 60 * 60 * 1000
|
||||||
|
|
||||||
const [comment, setComment] = useState('')
|
const [comment, setComment] = useState('')
|
||||||
|
@ -113,6 +115,8 @@ function FeedBet(props: { activityItem: any; feedType: FeedType }) {
|
||||||
<div>
|
<div>
|
||||||
{isSelf ? (
|
{isSelf ? (
|
||||||
<Avatar avatarUrl={user?.avatarUrl} />
|
<Avatar avatarUrl={user?.avatarUrl} />
|
||||||
|
) : isCreator ? (
|
||||||
|
<Avatar avatarUrl={contract.creatorAvatarUrl} />
|
||||||
) : (
|
) : (
|
||||||
<div className="relative px-1">
|
<div className="relative px-1">
|
||||||
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-gray-200">
|
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-gray-200">
|
||||||
|
@ -123,7 +127,10 @@ function FeedBet(props: { activityItem: any; feedType: FeedType }) {
|
||||||
</div>
|
</div>
|
||||||
<div className="min-w-0 flex-1 py-1.5">
|
<div className="min-w-0 flex-1 py-1.5">
|
||||||
<div className="text-sm text-gray-500">
|
<div className="text-sm text-gray-500">
|
||||||
<span>{isSelf ? 'You' : 'A trader'}</span> {bought} {money}
|
<span>
|
||||||
|
{isSelf ? 'You' : isCreator ? contract.creatorName : 'A trader'}
|
||||||
|
</span>{' '}
|
||||||
|
{bought} {money}
|
||||||
<MaybeOutcomeLabel outcome={outcome} feedType={feedType} />
|
<MaybeOutcomeLabel outcome={outcome} feedType={feedType} />
|
||||||
<Timestamp time={createdTime} />
|
<Timestamp time={createdTime} />
|
||||||
{canComment && (
|
{canComment && (
|
||||||
|
@ -494,7 +501,7 @@ function FeedClose(props: { contract: Contract }) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function toFeedBet(bet: Bet) {
|
function toFeedBet(bet: Bet, contract: Contract) {
|
||||||
return {
|
return {
|
||||||
id: bet.id,
|
id: bet.id,
|
||||||
contractId: bet.contractId,
|
contractId: bet.contractId,
|
||||||
|
@ -504,6 +511,7 @@ function toFeedBet(bet: Bet) {
|
||||||
outcome: bet.outcome,
|
outcome: bet.outcome,
|
||||||
createdTime: bet.createdTime,
|
createdTime: bet.createdTime,
|
||||||
date: fromNow(bet.createdTime),
|
date: fromNow(bet.createdTime),
|
||||||
|
contract,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -533,12 +541,13 @@ const DAY_IN_MS = 24 * 60 * 60 * 1000
|
||||||
// Group together bets that are:
|
// Group together bets that are:
|
||||||
// - Within `windowMs` of the first in the group
|
// - Within `windowMs` of the first in the group
|
||||||
// - Do not have a comment
|
// - Do not have a comment
|
||||||
// - Were not created by this user
|
// - Were not created by this user or the contract creator
|
||||||
// Return a list of ActivityItems
|
// Return a list of ActivityItems
|
||||||
function groupBets(
|
function groupBets(
|
||||||
bets: Bet[],
|
bets: Bet[],
|
||||||
comments: Comment[],
|
comments: Comment[],
|
||||||
windowMs: number,
|
windowMs: number,
|
||||||
|
contract: Contract,
|
||||||
userId?: string
|
userId?: string
|
||||||
) {
|
) {
|
||||||
const commentsMap = mapCommentsByBetId(comments)
|
const commentsMap = mapCommentsByBetId(comments)
|
||||||
|
@ -548,25 +557,25 @@ function groupBets(
|
||||||
// Turn the current group into an ActivityItem
|
// Turn the current group into an ActivityItem
|
||||||
function pushGroup() {
|
function pushGroup() {
|
||||||
if (group.length == 1) {
|
if (group.length == 1) {
|
||||||
items.push(toActivityItem(group[0]))
|
items.push(toActivityItem(group[0], false))
|
||||||
} else if (group.length > 1) {
|
} else if (group.length > 1) {
|
||||||
items.push({ type: 'betgroup', bets: [...group], id: group[0].id })
|
items.push({ type: 'betgroup', bets: [...group], id: group[0].id })
|
||||||
}
|
}
|
||||||
group = []
|
group = []
|
||||||
}
|
}
|
||||||
|
|
||||||
function toActivityItem(bet: Bet) {
|
function toActivityItem(bet: Bet, isPublic: boolean) {
|
||||||
const comment = commentsMap[bet.id]
|
const comment = commentsMap[bet.id]
|
||||||
return comment ? toFeedComment(bet, comment) : toFeedBet(bet)
|
return comment ? toFeedComment(bet, comment) : toFeedBet(bet, contract)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const bet of bets) {
|
for (const bet of bets) {
|
||||||
const isCreator = userId === bet.userId
|
const isCreator = userId === bet.userId || contract.creatorId === bet.userId
|
||||||
|
|
||||||
if (commentsMap[bet.id] || isCreator) {
|
if (commentsMap[bet.id] || isCreator) {
|
||||||
pushGroup()
|
pushGroup()
|
||||||
// Create a single item for this
|
// Create a single item for this
|
||||||
items.push(toActivityItem(bet))
|
items.push(toActivityItem(bet, true))
|
||||||
} else {
|
} else {
|
||||||
if (
|
if (
|
||||||
group.length > 0 &&
|
group.length > 0 &&
|
||||||
|
@ -801,7 +810,7 @@ export function ContractFeed(props: {
|
||||||
|
|
||||||
const allItems: ActivityItem[] = [
|
const allItems: ActivityItem[] = [
|
||||||
{ type: 'start', id: '0' },
|
{ type: 'start', id: '0' },
|
||||||
...groupBets(bets, comments, groupWindow, user?.id),
|
...groupBets(bets, comments, groupWindow, contract, user?.id),
|
||||||
]
|
]
|
||||||
if (contract.closeTime && contract.closeTime <= Date.now()) {
|
if (contract.closeTime && contract.closeTime <= Date.now()) {
|
||||||
allItems.push({ type: 'close', id: `${contract.closeTime}` })
|
allItems.push({ type: 'close', id: `${contract.closeTime}` })
|
||||||
|
@ -851,7 +860,7 @@ export function ContractActivityFeed(props: {
|
||||||
|
|
||||||
const allItems: ActivityItem[] = [
|
const allItems: ActivityItem[] = [
|
||||||
{ type: 'start', id: '0' },
|
{ type: 'start', id: '0' },
|
||||||
...groupBets(bets, comments, DAY_IN_MS, user?.id),
|
...groupBets(bets, comments, DAY_IN_MS, contract, user?.id),
|
||||||
]
|
]
|
||||||
if (contract.closeTime && contract.closeTime <= Date.now()) {
|
if (contract.closeTime && contract.closeTime <= Date.now()) {
|
||||||
allItems.push({ type: 'close', id: `${contract.closeTime}` })
|
allItems.push({ type: 'close', id: `${contract.closeTime}` })
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { useState, useEffect } from 'react'
|
||||||
import { IS_PRIVATE_MANIFOLD } from '../../common/envs/constants'
|
import { IS_PRIVATE_MANIFOLD } from '../../common/envs/constants'
|
||||||
|
|
||||||
type PropzProps = {
|
type PropzProps = {
|
||||||
|
// Params from the router query
|
||||||
params: any
|
params: any
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,9 +13,7 @@ type PropzProps = {
|
||||||
// TODO: Could cache the result using stale-while-revalidate: https://swr.vercel.app/
|
// TODO: Could cache the result using stale-while-revalidate: https://swr.vercel.app/
|
||||||
export function usePropz(
|
export function usePropz(
|
||||||
initialProps: Object,
|
initialProps: Object,
|
||||||
getStaticPropz: (props: PropzProps) => Promise<any>,
|
getStaticPropz: (props: PropzProps) => Promise<any>
|
||||||
// Dynamic routes will need the query params from the router
|
|
||||||
needParams?: boolean
|
|
||||||
) {
|
) {
|
||||||
// If props were successfully server-side generated, just use those
|
// If props were successfully server-side generated, just use those
|
||||||
if (!_.isEmpty(initialProps)) {
|
if (!_.isEmpty(initialProps)) {
|
||||||
|
@ -27,10 +26,9 @@ export function usePropz(
|
||||||
|
|
||||||
const [propz, setPropz] = useState<any>(undefined)
|
const [propz, setPropz] = useState<any>(undefined)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (needParams && _.isEmpty(params)) {
|
if (router.isReady) {
|
||||||
return
|
|
||||||
}
|
|
||||||
getStaticPropz({ params }).then((result) => setPropz(result.props))
|
getStaticPropz({ params }).then((result) => setPropz(result.props))
|
||||||
|
}
|
||||||
}, [params])
|
}, [params])
|
||||||
return propz
|
return propz
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,7 +79,7 @@ export default function ContractPage(props: {
|
||||||
slug: string
|
slug: string
|
||||||
folds: Fold[]
|
folds: Fold[]
|
||||||
}) {
|
}) {
|
||||||
props = usePropz(props, getStaticPropz, true) ?? {
|
props = usePropz(props, getStaticPropz) ?? {
|
||||||
contract: null,
|
contract: null,
|
||||||
username: '',
|
username: '',
|
||||||
comments: [],
|
comments: [],
|
||||||
|
|
|
@ -118,7 +118,7 @@ export default function FoldPage(props: {
|
||||||
creatorScores: { [userId: string]: number }
|
creatorScores: { [userId: string]: number }
|
||||||
topCreators: User[]
|
topCreators: User[]
|
||||||
}) {
|
}) {
|
||||||
props = usePropz(props, getStaticPropz, true) ?? {
|
props = usePropz(props, getStaticPropz) ?? {
|
||||||
fold: null,
|
fold: null,
|
||||||
curator: null,
|
curator: null,
|
||||||
contracts: [],
|
contracts: [],
|
||||||
|
|
Loading…
Reference in New Issue
Block a user