Merge branch 'main' into theoremone

This commit is contained in:
Austin Chen 2022-03-08 18:45:04 -08:00
commit a48755b283
8 changed files with 54 additions and 25 deletions

18
common/util/promise.ts Normal file
View 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
}

View File

@ -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)

View File

@ -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),

View File

@ -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}`
} }

View File

@ -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}` })

View File

@ -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
} }

View File

@ -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: [],

View File

@ -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: [],