Send only the collapsed activity feed items to client instead of all bets and comments

This commit is contained in:
James Grugett 2022-02-06 16:26:06 -06:00
parent 1c8c2a4126
commit e2657c75a3
5 changed files with 185 additions and 95 deletions

View File

@ -6,7 +6,6 @@ import {
CheckIcon,
DotsVerticalIcon,
LockClosedIcon,
StarIcon,
UserIcon,
UsersIcon,
XIcon,
@ -43,6 +42,7 @@ import { fromNow } from '../lib/util/time'
import BetRow from './bet-row'
import { parseTags } from '../../common/util/parse'
import { Avatar } from './avatar'
import { User } from '../../common/user'
function FeedComment(props: {
activityItem: any
@ -470,7 +470,12 @@ function groupBets(
if (group.length == 1) {
items.push(toActivityItem(group[0]))
} 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,
createdTime: group[group.length - 1].createdTime,
})
}
group = []
}
@ -504,6 +509,52 @@ function groupBets(
return items as ActivityItem[]
}
export function getContractFeedItems(
contract: Contract,
bets: Bet[],
comments: Comment[],
user: User | null | undefined,
options: { feedType: 'activity' | 'market'; expanded: boolean }
) {
const { feedType, expanded } = options
const groupWindow = feedType == 'activity' ? 10 * DAY_IN_MS : DAY_IN_MS
const allItems: ActivityItem[] = [
{ type: 'start', id: '0', createdTime: contract.createdTime },
...groupBets(
bets.filter((bet) => !bet.isAnte),
comments,
groupWindow,
user?.id
),
]
if (contract.closeTime && contract.closeTime <= Date.now()) {
allItems.push({
type: 'close',
id: `${contract.closeTime}`,
createdTime: contract.closeTime,
})
}
if (contract.resolution) {
allItems.push({
type: 'resolve',
id: `${contract.resolutionTime}`,
createdTime: contract.resolutionTime,
})
}
// If there are more than 5 items, only show the first, an expand item, and last 3
let items = allItems
if (!expanded && allItems.length > 5 && feedType == 'activity') {
items = [
allItems[0],
{ type: 'expand', id: 'expand' },
...allItems.slice(-3),
]
}
return items
}
function BetGroupSpan(props: { bets: Bet[]; outcome: 'YES' | 'NO' }) {
const { bets, outcome } = props
@ -583,9 +634,7 @@ function FeedExpand(props: { setExpanded: (expanded: boolean) => void }) {
)
}
// Missing feed items:
// - Bet sold?
type ActivityItem = {
export type ActivityItem = {
id: string
type:
| 'bet'
@ -595,48 +644,34 @@ type ActivityItem = {
| 'close'
| 'resolve'
| 'expand'
createdTime?: number
}
export function ContractFeed(props: {
contract: Contract
bets: Bet[]
comments: Comment[]
activityItems: ActivityItem[]
// Feed types: 'activity' = Activity feed, 'market' = Comments feed on a market
feedType: 'activity' | 'market'
betRowClassName?: string
}) {
const { contract, feedType, betRowClassName } = props
const { contract, activityItems, feedType, betRowClassName } = props
const { id } = contract
const [expanded, setExpanded] = useState(false)
const user = useUser()
let bets = useBets(id) ?? props.bets
let bets = useBets(id)
bets = withoutAnteBets(contract, bets)
const comments = useComments(id) ?? props.comments
const comments = useComments(id)
const groupWindow = feedType == 'activity' ? 10 * DAY_IN_MS : DAY_IN_MS
const allItems = [
{ type: 'start', id: 0 },
...groupBets(bets, comments, groupWindow, user?.id),
]
if (contract.closeTime && contract.closeTime <= Date.now()) {
allItems.push({ type: 'close', id: `${contract.closeTime}` })
}
if (contract.resolution) {
allItems.push({ type: 'resolve', id: `${contract.resolutionTime}` })
}
// If there are more than 5 items, only show the first, an expand item, and last 3
let items = allItems
if (!expanded && allItems.length > 5 && feedType == 'activity') {
items = [
allItems[0],
{ type: 'expand', id: 'expand' },
...allItems.slice(-3),
]
}
const items =
bets && comments
? getContractFeedItems(contract, bets, comments, user, {
feedType,
expanded,
})
: activityItems
return (
<div className="flow-root pr-2 md:pr-0">

View File

@ -14,7 +14,7 @@ import { Row } from './layout/row'
import { Linkify } from './linkify'
import clsx from 'clsx'
import { ContractDetails, ResolutionOrChance } from './contract-card'
import { ContractFeed } from './contract-feed'
import { ContractFeed, getContractFeedItems } from './contract-feed'
import { TweetButton } from './tweet-button'
import { Bet } from '../../common/bet'
import { Comment } from '../../common/comment'
@ -46,6 +46,11 @@ export const ContractOverview = (props: {
const url = `https://manifold.markets${contractPath(contract)}`
const tweetText = `${tweetQuestion}\n\n${tweetDescription}\n\n${url}`
const activityItems = getContractFeedItems(contract, bets, comments, user, {
feedType: 'market',
expanded: true,
})
return (
<Col className={clsx('mb-6', className)}>
<Row className="justify-between gap-4 px-2">
@ -131,8 +136,7 @@ export const ContractOverview = (props: {
<ContractFeed
contract={contract}
bets={bets}
comments={comments}
activityItems={activityItems}
feedType="market"
betRowClassName="md:hidden !mt-0"
/>

View File

@ -1,5 +1,9 @@
import _ from 'lodash'
import { ContractFeed } from '../components/contract-feed'
import {
ActivityItem,
ContractFeed,
getContractFeedItems,
} from '../components/contract-feed'
import { Page } from '../components/page'
import { Contract } from '../lib/firebase/contracts'
import { Comment } from '../lib/firebase/comments'
@ -85,27 +89,86 @@ export function findActiveContracts(
return contracts.slice(0, MAX_ACTIVE_CONTRACTS)
}
export function getActivity(
contracts: Contract[],
contractBets: Bet[][],
contractComments: Comment[][]
) {
const contractActivityItems = contracts.map((contract, i) => {
const bets = contractBets[i]
const comments = contractComments[i]
return getContractFeedItems(contract, bets, comments, undefined, {
expanded: false,
feedType: 'activity',
})
})
const hotMarketTimes: { [contractId: string]: number } = {}
// Add recent top-trading contracts, ordered by last bet.
const DAY_IN_MS = 24 * 60 * 60 * 1000
const contractTotalBets = _.map(contractBets, (bets) => {
const recentBets = bets.filter(
(bet) => bet.createdTime > Date.now() - DAY_IN_MS
)
return _.sumBy(recentBets, (bet) => bet.amount)
})
const topTradedContracts = _.sortBy(
contractTotalBets.map((total, index) => [total, index] as [number, number]),
([total, _]) => -1 * total
).slice(0, MAX_HOT_MARKETS)
for (const [_total, index] of topTradedContracts) {
const lastBet = _.last(contractBets[index])
if (lastBet) {
hotMarketTimes[contracts[index].id] = lastBet.createdTime
}
}
const orderedContracts = _.sortBy(
contracts.map((c, index) => [c, index] as const),
([contract, index]) => {
const activeTypes = ['start', 'comment', 'close', 'resolve']
const { createdTime } = _.last(
contractActivityItems[index].filter((item) =>
activeTypes.includes(item.type)
)
) as ActivityItem
const activeTime = createdTime ?? 0
const hotTime = hotMarketTimes[contract.id] ?? 0
return -1 * Math.max(activeTime, hotTime)
}
)
return {
contracts: orderedContracts.map(([_, index]) => contracts[index]),
contractActivityItems: orderedContracts.map(
([_, index]) => contractActivityItems[index]
),
}
}
export function ActivityFeed(props: {
contracts: Contract[]
contractBets: Bet[][]
contractComments: Comment[][]
contractActivityItems: ActivityItem[][]
}) {
const { contracts, contractBets, contractComments } = props
const { contracts, contractActivityItems } = props
return contracts.length > 0 ? (
<Col className="items-center">
<Col className="w-full max-w-3xl">
<Col className="w-full bg-white self-center divide-gray-300 divide-y">
{contracts.map((contract, i) => (
<div key={contract.id} className="py-6 px-2 sm:px-4">
<ContractFeed
contract={contract}
bets={contractBets[i]}
comments={contractComments[i]}
feedType="activity"
/>
</div>
))}
{contracts.map((contract, i) => {
return (
<div key={contract.id} className="py-6 px-2 sm:px-4">
<ContractFeed
contract={contract}
activityItems={contractActivityItems[i]}
feedType="activity"
/>
</div>
)
})}
</Col>
</Col>
</Col>
@ -117,7 +180,7 @@ export function ActivityFeed(props: {
export default function ActivityPage() {
return (
<Page>
<ActivityFeed contracts={[]} contractBets={[]} contractComments={[]} />
<ActivityFeed contracts={[]} contractActivityItems={[]} />
</Page>
)
}

View File

@ -39,6 +39,7 @@ import { FollowFoldButton } from '../../../components/follow-fold-button'
import FeedCreate from '../../../components/feed-create'
import { SEO } from '../../../components/SEO'
import { useTaggedContracts } from '../../../hooks/use-contracts'
import { getContractFeedItems } from '../../../components/contract-feed'
export async function getStaticProps(props: { params: { slugs: string[] } }) {
const { slugs } = props.params
@ -175,6 +176,16 @@ export default function FoldPage(props: {
(contract) => contractsMap[contract.id]
)
const contractActivityItems = activeContracts.map((contract, index) =>
getContractFeedItems(
contract,
activeContractBets[index],
activeContractComments[index],
user,
{ feedType: 'activity', expanded: false }
)
)
if (fold === null || !foldSubpages.includes(page) || slugs[2]) {
return <Custom404 />
}
@ -260,8 +271,7 @@ export default function FoldPage(props: {
<>
<ActivityFeed
contracts={activeContracts}
contractBets={activeContractBets}
contractComments={activeContractComments}
contractActivityItems={contractActivityItems}
/>
{activeContracts.length === 0 && (
<div className="text-gray-500 mt-4 mx-2 lg:mx-0">

View File

@ -4,9 +4,9 @@ import _ from 'lodash'
import { Contract, listAllContracts } from '../lib/firebase/contracts'
import { Page } from '../components/page'
import { ActivityFeed, findActiveContracts } from './activity'
import { Comment, listAllComments } from '../lib/firebase/comments'
import { Bet, listAllBets } from '../lib/firebase/bets'
import { ActivityFeed, getActivity } from './activity'
import { listAllComments } from '../lib/firebase/comments'
import { listAllBets } from '../lib/firebase/bets'
import FeedCreate from '../components/feed-create'
import { Spacer } from '../components/layout/spacer'
import { Col } from '../components/layout/col'
@ -22,6 +22,7 @@ import { SearchIcon } from '@heroicons/react/outline'
import { Row } from '../components/layout/row'
import { SparklesIcon } from '@heroicons/react/solid'
import { useFollowedFolds } from '../hooks/use-fold'
import { ActivityItem } from '../components/contract-feed'
export async function getStaticProps() {
const [contracts, folds] = await Promise.all([
@ -36,9 +37,7 @@ export async function getStaticProps() {
return {
props: {
contracts,
contractBets,
contractComments,
...getActivity(contracts, contractBets, contractComments),
folds,
},
@ -48,18 +47,14 @@ export async function getStaticProps() {
const Home = (props: {
contracts: Contract[]
contractBets: Bet[][]
contractComments: Comment[][]
contractActivityItems: ActivityItem[][]
folds: Fold[]
}) => {
const { contractBets, contractComments, folds } = props
const { contractActivityItems, folds } = props
const user = useUser()
const contracts = useUpdatedContracts(props.contracts)
const contractIdToIndex = _.fromPairs(
contracts.map((contract, index) => [contract.id, index])
)
const followedFoldIds = useFollowedFolds(user)
const followedFolds = filterDefined(
@ -76,36 +71,20 @@ const Home = (props: {
const feedContracts =
followedFoldIds && yourBetContracts
? contracts.filter(
(contract) =>
contract.lowercaseTags.some((tag) => tagSet.has(tag)) ||
yourBetContracts.has(contract.id)
)
? contracts
.filter(
(contract) =>
contract.lowercaseTags.some((tag) => tagSet.has(tag)) ||
yourBetContracts.has(contract.id)
)
.slice(0, 75)
: undefined
const oneDayMS = 24 * 60 * 60 * 1000
const recentBets = (feedContracts ?? [])
.map((c) => contractBets[contractIdToIndex[c.id]])
.flat()
.filter((bet) => bet.createdTime > Date.now() - oneDayMS)
const feedComments = (feedContracts ?? [])
.map((c) => contractComments[contractIdToIndex[c.id]])
.flat()
const feedContractSet = new Set(feedContracts?.map((contract) => contract.id))
const activeContracts =
feedContracts &&
findActiveContracts(feedContracts, feedComments, recentBets, 365)
const activeBets = activeContracts
? activeContracts.map(
(contract) => contractBets[contractIdToIndex[contract.id]]
)
: []
const activeComments = activeContracts
? activeContracts.map(
(contract) => contractComments[contractIdToIndex[contract.id]]
)
: []
const feedActivityItems = contractActivityItems.filter((_, index) =>
feedContractSet.has(contracts[index].id)
)
if (user === null) {
Router.replace('/')
@ -143,11 +122,10 @@ const Home = (props: {
<SparklesIcon className="inline w-5 h-5" aria-hidden="true" />
Recent activity
</Row>
{activeContracts ? (
{feedContracts ? (
<ActivityFeed
contracts={activeContracts}
contractBets={activeBets}
contractComments={activeComments}
contracts={feedContracts}
contractActivityItems={feedActivityItems}
/>
) : (
<LoadingIndicator className="mt-4" />