From 0cf9a90cfbc39fc561cab2e6f0ed4bcd222fabf6 Mon Sep 17 00:00:00 2001 From: Marshall Polaris Date: Thu, 18 Aug 2022 15:46:11 -0700 Subject: [PATCH] Remove some dead code related to tags, categories, and old feed stuff (#765) * Remove dead image storage code * Kill tag page * Kill tag and categories related component UI * Kill some old algo feed kind of code --- common/recommended-contracts.ts | 187 ---------------------- web/components/feed/category-selector.tsx | 167 ------------------- web/components/tags-input.tsx | 55 ------- web/components/tags-list.tsx | 86 ---------- web/hooks/use-algo-feed.ts | 51 ------ web/lib/firebase/contracts.ts | 11 -- web/lib/firebase/users.ts | 45 +----- web/pages/tag/[tag].tsx | 24 --- 8 files changed, 1 insertion(+), 625 deletions(-) delete mode 100644 common/recommended-contracts.ts delete mode 100644 web/components/feed/category-selector.tsx delete mode 100644 web/components/tags-input.tsx delete mode 100644 web/components/tags-list.tsx delete mode 100644 web/hooks/use-algo-feed.ts delete mode 100644 web/pages/tag/[tag].tsx diff --git a/common/recommended-contracts.ts b/common/recommended-contracts.ts deleted file mode 100644 index 3a6eca38..00000000 --- a/common/recommended-contracts.ts +++ /dev/null @@ -1,187 +0,0 @@ -import { union, sum, sumBy, sortBy, groupBy, mapValues } from 'lodash' -import { Bet } from './bet' -import { Contract } from './contract' -import { ClickEvent } from './tracking' -import { filterDefined } from './util/array' -import { addObjects } from './util/object' - -export const MAX_FEED_CONTRACTS = 75 - -export const getRecommendedContracts = ( - contractsById: { [contractId: string]: Contract }, - yourBetOnContractIds: string[] -) => { - const contracts = Object.values(contractsById) - const yourContracts = filterDefined( - yourBetOnContractIds.map((contractId) => contractsById[contractId]) - ) - - const yourContractIds = new Set(yourContracts.map((c) => c.id)) - const notYourContracts = contracts.filter((c) => !yourContractIds.has(c.id)) - - const yourWordFrequency = contractsToWordFrequency(yourContracts) - const otherWordFrequency = contractsToWordFrequency(notYourContracts) - const words = union( - Object.keys(yourWordFrequency), - Object.keys(otherWordFrequency) - ) - - const yourWeightedFrequency = Object.fromEntries( - words.map((word) => { - const [yourFreq, otherFreq] = [ - yourWordFrequency[word] ?? 0, - otherWordFrequency[word] ?? 0, - ] - - const score = yourFreq / (yourFreq + otherFreq + 0.0001) - - return [word, score] - }) - ) - - // console.log( - // 'your weighted frequency', - // _.sortBy(_.toPairs(yourWeightedFrequency), ([, freq]) => -freq) - // ) - - const scoredContracts = contracts.map((contract) => { - const wordFrequency = contractToWordFrequency(contract) - - const score = sumBy(Object.keys(wordFrequency), (word) => { - const wordFreq = wordFrequency[word] ?? 0 - const weight = yourWeightedFrequency[word] ?? 0 - return wordFreq * weight - }) - - return { - contract, - score, - } - }) - - return sortBy(scoredContracts, (scored) => -scored.score).map( - (scored) => scored.contract - ) -} - -const contractToText = (contract: Contract) => { - const { description, question, tags, creatorUsername } = contract - return `${creatorUsername} ${question} ${tags.join(' ')} ${description}` -} - -const MAX_CHARS_IN_WORD = 100 - -const getWordsCount = (text: string) => { - const normalizedText = text.replace(/[^a-zA-Z]/g, ' ').toLowerCase() - const words = normalizedText - .split(' ') - .filter((word) => word) - .filter((word) => word.length <= MAX_CHARS_IN_WORD) - - const counts: { [word: string]: number } = {} - for (const word of words) { - if (counts[word]) counts[word]++ - else counts[word] = 1 - } - return counts -} - -const toFrequency = (counts: { [word: string]: number }) => { - const total = sum(Object.values(counts)) - return mapValues(counts, (count) => count / total) -} - -const contractToWordFrequency = (contract: Contract) => - toFrequency(getWordsCount(contractToText(contract))) - -const contractsToWordFrequency = (contracts: Contract[]) => { - const frequencySum = contracts - .map(contractToWordFrequency) - .reduce(addObjects, {}) - - return toFrequency(frequencySum) -} - -export const getWordScores = ( - contracts: Contract[], - contractViewCounts: { [contractId: string]: number }, - clicks: ClickEvent[], - bets: Bet[] -) => { - const contractClicks = groupBy(clicks, (click) => click.contractId) - const contractBets = groupBy(bets, (bet) => bet.contractId) - - const yourContracts = contracts.filter( - (c) => - contractViewCounts[c.id] || contractClicks[c.id] || contractBets[c.id] - ) - const yourTfIdf = calculateContractTfIdf(yourContracts) - - const contractWordScores = mapValues(yourTfIdf, (wordsTfIdf, contractId) => { - const viewCount = contractViewCounts[contractId] ?? 0 - const clickCount = contractClicks[contractId]?.length ?? 0 - const betCount = contractBets[contractId]?.length ?? 0 - - const factor = - -1 * Math.log(viewCount + 1) + - 10 * Math.log(betCount + clickCount / 4 + 1) - - return mapValues(wordsTfIdf, (tfIdf) => tfIdf * factor) - }) - - const wordScores = Object.values(contractWordScores).reduce(addObjects, {}) - const minScore = Math.min(...Object.values(wordScores)) - const maxScore = Math.max(...Object.values(wordScores)) - const normalizedWordScores = mapValues( - wordScores, - (score) => (score - minScore) / (maxScore - minScore) - ) - - // console.log( - // 'your word scores', - // _.sortBy(_.toPairs(normalizedWordScores), ([, score]) => -score).slice(0, 100), - // _.sortBy(_.toPairs(normalizedWordScores), ([, score]) => -score).slice(-100) - // ) - - return normalizedWordScores -} - -export function getContractScore( - contract: Contract, - wordScores: { [word: string]: number } -) { - if (Object.keys(wordScores).length === 0) return 1 - - const wordFrequency = contractToWordFrequency(contract) - const score = sumBy(Object.keys(wordFrequency), (word) => { - const wordFreq = wordFrequency[word] ?? 0 - const weight = wordScores[word] ?? 0 - return wordFreq * weight - }) - - return score -} - -// Caluculate Term Frequency-Inverse Document Frequency (TF-IDF): -// https://medium.datadriveninvestor.com/tf-idf-in-natural-language-processing-8db8ef4a7736 -function calculateContractTfIdf(contracts: Contract[]) { - const contractFreq = contracts.map((c) => contractToWordFrequency(c)) - const contractWords = contractFreq.map((freq) => Object.keys(freq)) - - const wordsCount: { [word: string]: number } = {} - for (const words of contractWords) { - for (const word of words) { - wordsCount[word] = (wordsCount[word] ?? 0) + 1 - } - } - - const wordIdf = mapValues(wordsCount, (count) => - Math.log(contracts.length / count) - ) - const contractWordsTfIdf = contractFreq.map((wordFreq) => - mapValues(wordFreq, (freq, word) => freq * wordIdf[word]) - ) - return Object.fromEntries( - contracts.map((c, i) => [c.id, contractWordsTfIdf[i]]) - ) -} diff --git a/web/components/feed/category-selector.tsx b/web/components/feed/category-selector.tsx deleted file mode 100644 index a39f7402..00000000 --- a/web/components/feed/category-selector.tsx +++ /dev/null @@ -1,167 +0,0 @@ -import clsx from 'clsx' -import { PencilIcon } from '@heroicons/react/outline' -import { union, difference } from 'lodash' - -import { Row } from '../layout/row' -import { CATEGORIES, category, CATEGORY_LIST } from '../../../common/categories' -import { Modal } from '../layout/modal' -import { Col } from '../layout/col' -import { useState } from 'react' -import { updateUser, User } from 'web/lib/firebase/users' -import { Checkbox } from '../checkbox' -import { track } from 'web/lib/service/analytics' - -export function CategorySelector(props: { - category: string - setCategory: (category: string) => void - className?: string -}) { - const { className, category, setCategory } = props - - return ( - -
- { - setCategory('all') - }} - /> - - { - setCategory('following') - }} - /> - - {CATEGORY_LIST.map((cat) => ( - { - setCategory(cat) - }} - /> - ))} - - ) -} - -function CategoryButton(props: { - category: string - isFollowed: boolean - toggle: () => void - className?: string -}) { - const { toggle, category, isFollowed, className } = props - - return ( -
- {category} -
- ) -} - -export function EditCategoriesButton(props: { - user: User - className?: string -}) { - const { user, className } = props - const [isOpen, setIsOpen] = useState(false) - - return ( -
{ - setIsOpen(true) - track('edit categories button') - }} - > - - Categories - -
- ) -} - -function CategorySelectorModal(props: { - user: User - isOpen: boolean - setIsOpen: (isOpen: boolean) => void -}) { - const { user, isOpen, setIsOpen } = props - const followedCategories = - user?.followedCategories === undefined - ? CATEGORY_LIST - : user.followedCategories - - const selectAll = - user.followedCategories === undefined || - followedCategories.length < CATEGORY_LIST.length - - return ( - - - - - {CATEGORY_LIST.map((cat) => ( - { - updateUser(user.id, { - followedCategories: checked - ? difference(followedCategories, [cat]) - : union([cat], followedCategories), - }) - }} - /> - ))} - - - - ) -} diff --git a/web/components/tags-input.tsx b/web/components/tags-input.tsx deleted file mode 100644 index dd8a2f1d..00000000 --- a/web/components/tags-input.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import clsx from 'clsx' -import { useState } from 'react' -import { parseWordsAsTags } from 'common/util/parse' -import { Contract, updateContract } from 'web/lib/firebase/contracts' -import { Col } from './layout/col' -import { Row } from './layout/row' -import { TagsList } from './tags-list' -import { MAX_TAG_LENGTH } from 'common/contract' - -export function TagsInput(props: { contract: Contract; className?: string }) { - const { contract, className } = props - const { tags } = contract - - const [tagText, setTagText] = useState('') - const newTags = parseWordsAsTags(`${tags.join(' ')} ${tagText}`) - - const [isSubmitting, setIsSubmitting] = useState(false) - - const updateTags = async () => { - setIsSubmitting(true) - await updateContract(contract.id, { - tags: newTags, - lowercaseTags: newTags.map((tag) => tag.toLowerCase()), - }) - setIsSubmitting(false) - setTagText('') - } - - return ( - - - - - setTagText(e.target.value || '')} - onKeyDown={(e) => { - if (e.key === 'Enter' && !e.shiftKey) { - e.preventDefault() - updateTags() - } - }} - /> - - - - ) -} diff --git a/web/components/tags-list.tsx b/web/components/tags-list.tsx deleted file mode 100644 index a13bcd35..00000000 --- a/web/components/tags-list.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import clsx from 'clsx' -import { CATEGORIES, category } from '../../common/categories' -import { Col } from './layout/col' - -import { Row } from './layout/row' -import { SiteLink } from './site-link' - -function Hashtag(props: { tag: string; noLink?: boolean }) { - const { tag, noLink } = props - const category = CATEGORIES[tag.replace('#', '').toLowerCase() as category] - - const body = ( -
- {category ? '#' + category : tag} -
- ) - - if (noLink) return body - return ( - - {body} - - ) -} - -export function TagsList(props: { - tags: string[] - className?: string - noLink?: boolean - noLabel?: boolean - label?: string -}) { - const { tags, className, noLink, noLabel, label } = props - return ( - - {!noLabel &&
{label || 'Tags'}
} - {tags.map((tag) => ( - - ))} -
- ) -} - -export function FoldTag(props: { fold: { slug: string; name: string } }) { - const { fold } = props - const { slug, name } = fold - - return ( - -
- {name} -
-
- ) -} - -export function FoldTagList(props: { - folds: { slug: string; name: string }[] - noLabel?: boolean - className?: string -}) { - const { folds, noLabel, className } = props - return ( - - {!noLabel &&
Communities
} - - {folds.length > 0 && ( - <> - {folds.map((fold) => ( - - ))} - - )} - - - ) -} diff --git a/web/hooks/use-algo-feed.ts b/web/hooks/use-algo-feed.ts deleted file mode 100644 index e195936f..00000000 --- a/web/hooks/use-algo-feed.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { useState, useEffect } from 'react' -import type { feed } from 'common/feed' -import { useTimeSinceFirstRender } from './use-time-since-first-render' -import { trackLatency } from 'web/lib/firebase/tracking' -import { User } from 'common/user' -import { getCategoryFeeds, getUserFeed } from 'web/lib/firebase/users' -import { - getRecentBetsAndComments, - getTopWeeklyContracts, -} from 'web/lib/firebase/contracts' - -export const useAlgoFeed = ( - user: User | null | undefined, - category: string -) => { - const [allFeed, setAllFeed] = useState() - const [categoryFeeds, setCategoryFeeds] = useState<{ [x: string]: feed }>() - - const getTime = useTimeSinceFirstRender() - - useEffect(() => { - if (user) { - getUserFeed(user.id).then((feed) => { - if (feed.length === 0) { - getDefaultFeed().then((feed) => setAllFeed(feed)) - } else setAllFeed(feed) - - trackLatency(user.id, 'feed', getTime()) - console.log('"all" feed load time', getTime()) - }) - - getCategoryFeeds(user.id).then((feeds) => { - setCategoryFeeds(feeds) - console.log('category feeds load time', getTime()) - }) - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [user?.id]) - - const feed = category === 'all' ? allFeed : categoryFeeds?.[category] - - return feed -} - -const getDefaultFeed = async () => { - const contracts = await getTopWeeklyContracts() - const feed = await Promise.all( - contracts.map((c) => getRecentBetsAndComments(c)) - ) - return feed -} diff --git a/web/lib/firebase/contracts.ts b/web/lib/firebase/contracts.ts index b31b8d04..243a453a 100644 --- a/web/lib/firebase/contracts.ts +++ b/web/lib/firebase/contracts.ts @@ -22,7 +22,6 @@ import { createRNG, shuffle } from 'common/util/random' import { getCpmmProbability } from 'common/calculate-cpmm' import { formatMoney, formatPercent } from 'common/util/format' import { DAY_MS } from 'common/util/time' -import { MAX_FEED_CONTRACTS } from 'common/recommended-contracts' import { Bet } from 'common/bet' import { Comment } from 'common/comment' import { ENV_CONFIG } from 'common/envs/constants' @@ -285,16 +284,6 @@ export async function getContractsBySlugs(slugs: string[]) { return sortBy(data, (contract) => -1 * contract.volume24Hours) } -const topWeeklyQuery = query( - contracts, - where('isResolved', '==', false), - orderBy('volume7Days', 'desc'), - limit(MAX_FEED_CONTRACTS) -) -export async function getTopWeeklyContracts() { - return await getValues(topWeeklyQuery) -} - const closingSoonQuery = query( contracts, where('isResolved', '==', false), diff --git a/web/lib/firebase/users.ts b/web/lib/firebase/users.ts index ac0eb099..6cfee163 100644 --- a/web/lib/firebase/users.ts +++ b/web/lib/firebase/users.ts @@ -14,20 +14,10 @@ import { onSnapshot, } from 'firebase/firestore' import { getAuth } from 'firebase/auth' -import { ref, getStorage, uploadBytes, getDownloadURL } from 'firebase/storage' import { GoogleAuthProvider, signInWithPopup } from 'firebase/auth' -import { zip } from 'lodash' import { app, db } from './init' import { PortfolioMetrics, PrivateUser, User } from 'common/user' -import { - coll, - getValue, - getValues, - listenForValue, - listenForValues, -} from './utils' -import { feed } from 'common/feed' -import { CATEGORY_LIST } from 'common/categories' +import { coll, getValues, listenForValue, listenForValues } from './utils' import { safeLocalStorage } from '../util/local' import { filterDefined } from 'common/util/array' import { addUserToGroupViaId } from 'web/lib/firebase/groups' @@ -202,20 +192,6 @@ export async function firebaseLogout() { await auth.signOut() } -const storage = getStorage(app) -// Example: uploadData('avatars/ajfi8iejsf.png', data) -export async function uploadData( - path: string, - data: ArrayBuffer | Blob | Uint8Array -) { - const uploadRef = ref(storage, path) - // Uploaded files should be cached for 1 day, then revalidated - // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control - const metadata = { cacheControl: 'public, max-age=86400, must-revalidate' } - await uploadBytes(uploadRef, data, metadata) - return await getDownloadURL(uploadRef) -} - export async function listUsers(userIds: string[]) { if (userIds.length > 10) { throw new Error('Too many users requested at once; Firestore limits to 10') @@ -263,25 +239,6 @@ export function getUsers() { return getValues(users) } -export async function getUserFeed(userId: string) { - const feedDoc = doc(privateUsers, userId, 'cache', 'feed') - const userFeed = await getValue<{ - feed: feed - }>(feedDoc) - return userFeed?.feed ?? [] -} - -export async function getCategoryFeeds(userId: string) { - const cacheCollection = collection(privateUsers, userId, 'cache') - const feedData = await Promise.all( - CATEGORY_LIST.map((category) => - getValue<{ feed: feed }>(doc(cacheCollection, `feed-${category}`)) - ) - ) - const feeds = feedData.map((data) => data?.feed ?? []) - return Object.fromEntries(zip(CATEGORY_LIST, feeds) as [string, feed][]) -} - export async function follow(userId: string, followedUserId: string) { const followDoc = doc(collection(users, userId, 'follows'), followedUserId) await setDoc(followDoc, { diff --git a/web/pages/tag/[tag].tsx b/web/pages/tag/[tag].tsx deleted file mode 100644 index f2554f49..00000000 --- a/web/pages/tag/[tag].tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { useRouter } from 'next/router' -import { useUser } from 'web/hooks/use-user' -import { ContractSearch } from '../../components/contract-search' -import { Page } from '../../components/page' -import { Title } from '../../components/title' - -export default function TagPage() { - const router = useRouter() - const user = useUser() - const { tag } = router.query as { tag: string } - if (!router.isReady) return
- - return ( - - - <ContractSearch - user={user} - defaultSort="newest" - defaultFilter="all" - additionalFilter={{ tag }} - /> - </Page> - ) -}