diff --git a/common/.eslintrc.js b/common/.eslintrc.js index 35572274..54b878e3 100644 --- a/common/.eslintrc.js +++ b/common/.eslintrc.js @@ -1,4 +1,5 @@ module.exports = { + plugins: ['lodash'], extends: ['eslint:recommended'], env: { browser: true, @@ -14,5 +15,6 @@ module.exports = { rules: { 'no-unused-vars': 'off', 'no-constant-condition': ['error', { checkLoops: false }], + 'lodash/import-scope': [2, 'member'], }, } diff --git a/common/antes.ts b/common/antes.ts index 07251f06..59142be9 100644 --- a/common/antes.ts +++ b/common/antes.ts @@ -1,3 +1,4 @@ +import { range } from 'lodash' import { Bet, NumericBet } from './bet' import { getDpmProbability, getValueFromBucket } from './calculate-dpm' import { @@ -11,7 +12,6 @@ import { import { User } from './user' import { LiquidityProvision } from './liquidity-provision' import { noFees } from './fees' -import * as _ from 'lodash' export const FIXED_ANTE = 100 @@ -127,11 +127,11 @@ export function getNumericAnte( const betShares = Math.sqrt(ante ** 2 / bucketCount) const allOutcomeShares = Object.fromEntries( - _.range(0, bucketCount).map((_, i) => [i, betShares]) + range(0, bucketCount).map((_, i) => [i, betShares]) ) const allBetAmounts = Object.fromEntries( - _.range(0, bucketCount).map((_, i) => [i, betAnte]) + range(0, bucketCount).map((_, i) => [i, betAnte]) ) const anteBet: NumericBet = { diff --git a/common/calculate-cpmm.ts b/common/calculate-cpmm.ts index 75980279..6b14211d 100644 --- a/common/calculate-cpmm.ts +++ b/common/calculate-cpmm.ts @@ -1,4 +1,4 @@ -import * as _ from 'lodash' +import { sum, groupBy, mapValues, sumBy } from 'lodash' import { Binary, CPMM, FullContract } from './contract' import { CREATOR_FEE, Fees, LIQUIDITY_FEE, noFees, PLATFORM_FEE } from './fees' @@ -278,16 +278,16 @@ export function getCpmmLiquidityPoolWeights( return liquidity }) - const shareSum = _.sum(liquidityShares) + const shareSum = sum(liquidityShares) const weights = liquidityShares.map((s, i) => ({ weight: s / shareSum, providerId: liquidities[i].userId, })) - const userWeights = _.groupBy(weights, (w) => w.providerId) - const totalUserWeights = _.mapValues(userWeights, (userWeight) => - _.sumBy(userWeight, (w) => w.weight) + const userWeights = groupBy(weights, (w) => w.providerId) + const totalUserWeights = mapValues(userWeights, (userWeight) => + sumBy(userWeight, (w) => w.weight) ) return totalUserWeights } diff --git a/common/calculate-dpm.ts b/common/calculate-dpm.ts index 2ec9fd88..39b348ab 100644 --- a/common/calculate-dpm.ts +++ b/common/calculate-dpm.ts @@ -1,5 +1,4 @@ -import * as _ from 'lodash' - +import { cloneDeep, range, sum, sumBy, sortBy, mapValues } from 'lodash' import { Bet, NumericBet } from './bet' import { Binary, @@ -24,7 +23,7 @@ export function getDpmOutcomeProbability( }, outcome: string ) { - const squareSum = _.sumBy(Object.values(totalShares), (shares) => shares ** 2) + const squareSum = sumBy(Object.values(totalShares), (shares) => shares ** 2) const shares = totalShares[outcome] ?? 0 return shares ** 2 / squareSum } @@ -32,8 +31,8 @@ export function getDpmOutcomeProbability( export function getDpmOutcomeProbabilities(totalShares: { [outcome: string]: number }) { - const squareSum = _.sumBy(Object.values(totalShares), (shares) => shares ** 2) - return _.mapValues(totalShares, (shares) => shares ** 2 / squareSum) + const squareSum = sumBy(Object.values(totalShares), (shares) => shares ** 2) + return mapValues(totalShares, (shares) => shares ** 2 / squareSum) } export function getNumericBets( @@ -44,20 +43,20 @@ export function getNumericBets( ) { const { bucketCount } = contract const bucketNumber = parseInt(bucket) - const buckets = _.range(0, bucketCount) + const buckets = range(0, bucketCount) const mean = bucketNumber / bucketCount const allDensities = buckets.map((i) => normpdf(i / bucketCount, mean, variance) ) - const densitySum = _.sum(allDensities) + const densitySum = sum(allDensities) const rawBetAmounts = allDensities .map((d) => (d / densitySum) * betAmount) .map((x) => (x >= 1 / bucketCount ? x : 0)) - const rawSum = _.sum(rawBetAmounts) + const rawSum = sum(rawBetAmounts) const scaledBetAmounts = rawBetAmounts.map((x) => (x / rawSum) * betAmount) const bets = scaledBetAmounts @@ -90,26 +89,24 @@ export const getValueFromBucket = ( export const getExpectedValue = (contract: NumericContract) => { const { bucketCount, min, max, totalShares } = contract - const totalShareSum = _.sumBy( + const totalShareSum = sumBy( Object.values(totalShares), (shares) => shares ** 2 ) - const probs = _.range(0, bucketCount).map( + const probs = range(0, bucketCount).map( (i) => totalShares[i] ** 2 / totalShareSum ) - const values = _.range(0, bucketCount).map( + const values = range(0, bucketCount).map( (i) => // use mid point within bucket 0.5 * (min + (i / bucketCount) * (max - min)) + 0.5 * (min + ((i + 1) / bucketCount) * (max - min)) ) - const weightedValues = _.range(0, bucketCount).map( - (i) => probs[i] * values[i] - ) + const weightedValues = range(0, bucketCount).map((i) => probs[i] * values[i]) - const expectation = _.sum(weightedValues) + const expectation = sum(weightedValues) const rounded = Math.round(expectation * 1e2) / 1e2 return rounded } @@ -150,7 +147,7 @@ export function calculateDpmShares( bet: number, betChoice: string ) { - const squareSum = _.sumBy(Object.values(totalShares), (shares) => shares ** 2) + const squareSum = sumBy(Object.values(totalShares), (shares) => shares ** 2) const shares = totalShares[betChoice] ?? 0 const c = 2 * bet * Math.sqrt(squareSum) @@ -166,9 +163,9 @@ export function calculateNumericDpmShares( ) { const shares: number[] = [] - totalShares = _.cloneDeep(totalShares) + totalShares = cloneDeep(totalShares) - const order = _.sortBy( + const order = sortBy( bets.map(([, amount], i) => [amount, i]), ([amount]) => amount ).map(([, i]) => i) @@ -190,11 +187,11 @@ export function calculateDpmRawShareValue( betChoice: string ) { const currentValue = Math.sqrt( - _.sumBy(Object.values(totalShares), (shares) => shares ** 2) + sumBy(Object.values(totalShares), (shares) => shares ** 2) ) const postSaleValue = Math.sqrt( - _.sumBy(Object.keys(totalShares), (outcome) => + sumBy(Object.keys(totalShares), (outcome) => outcome === betChoice ? Math.max(0, totalShares[outcome] - shares) ** 2 : totalShares[outcome] ** 2 @@ -214,12 +211,12 @@ export function calculateDpmMoneyRatio( const p = getDpmOutcomeProbability(totalShares, outcome) - const actual = _.sum(Object.values(pool)) - shareValue + const actual = sum(Object.values(pool)) - shareValue const betAmount = p * amount const expected = - _.sumBy( + sumBy( Object.keys(totalBets), (outcome) => getDpmOutcomeProbability(totalShares, outcome) * @@ -271,8 +268,8 @@ export function calculateDpmCancelPayout( bet: Bet ) { const { totalBets, pool } = contract - const betTotal = _.sum(Object.values(totalBets)) - const poolTotal = _.sum(Object.values(pool)) + const betTotal = sum(Object.values(totalBets)) + const poolTotal = sum(Object.values(pool)) return (bet.amount / betTotal) * poolTotal } @@ -295,7 +292,7 @@ export function calculateStandardDpmPayout( const { totalShares, phantomShares, pool } = contract if (!totalShares[outcome]) return 0 - const poolTotal = _.sum(Object.values(pool)) + const poolTotal = sum(Object.values(pool)) const total = totalShares[outcome] - (phantomShares ? phantomShares[outcome] : 0) @@ -356,19 +353,19 @@ function calculateMktDpmPayout( let probs: { [outcome: string]: number } if (resolutions) { - const probTotal = _.sum(Object.values(resolutions)) - probs = _.mapValues( + const probTotal = sum(Object.values(resolutions)) + probs = mapValues( totalShares, (_, outcome) => (resolutions[outcome] ?? 0) / probTotal ) } else { - const squareSum = _.sum( + const squareSum = sum( Object.values(totalShares).map((shares) => shares ** 2) ) - probs = _.mapValues(totalShares, (shares) => shares ** 2 / squareSum) + probs = mapValues(totalShares, (shares) => shares ** 2 / squareSum) } - const weightedShareTotal = _.sumBy(Object.keys(totalShares), (outcome) => { + const weightedShareTotal = sumBy(Object.keys(totalShares), (outcome) => { return probs[outcome] * totalShares[outcome] }) @@ -376,7 +373,7 @@ function calculateMktDpmPayout( const poolFrac = outcomeType === 'NUMERIC' - ? _.sumBy( + ? sumBy( Object.keys((bet as NumericBet).allOutcomeShares ?? {}), (outcome) => { return ( @@ -387,7 +384,7 @@ function calculateMktDpmPayout( ) : (probs[outcome] * shares) / weightedShareTotal - const totalPool = _.sum(Object.values(pool)) + const totalPool = sum(Object.values(pool)) const winnings = poolFrac * totalPool return deductDpmFees(amount, winnings) } diff --git a/common/calculate.ts b/common/calculate.ts index 06820edc..d22f507f 100644 --- a/common/calculate.ts +++ b/common/calculate.ts @@ -1,4 +1,4 @@ -import * as _ from 'lodash' +import { maxBy } from 'lodash' import { Bet } from './bet' import { calculateCpmmSale, @@ -180,7 +180,7 @@ export function getContractBetNullMetrics() { export function getTopAnswer(contract: FreeResponseContract) { const { answers } = contract - const top = _.maxBy( + const top = maxBy( answers?.map((answer) => ({ answer, prob: getOutcomeProbability(contract, answer.id), diff --git a/common/contract.ts b/common/contract.ts index 14499cc7..a8f2bd42 100644 --- a/common/contract.ts +++ b/common/contract.ts @@ -1,4 +1,3 @@ -import * as _ from 'lodash' import { Answer } from './answer' import { Fees } from './fees' diff --git a/common/new-bet.ts b/common/new-bet.ts index 1e09d23d..b640c0b5 100644 --- a/common/new-bet.ts +++ b/common/new-bet.ts @@ -1,4 +1,4 @@ -import * as _ from 'lodash' +import { sumBy } from 'lodash' import { Bet, MAX_LOAN_PER_CONTRACT, NumericBet } from './bet' import { @@ -210,7 +210,7 @@ export const getNumericBetsInfo = ( export const getLoanAmount = (yourBets: Bet[], newBetAmount: number) => { const openBets = yourBets.filter((bet) => !bet.isSold && !bet.sale) - const prevLoanAmount = _.sumBy(openBets, (bet) => bet.loanAmount ?? 0) + const prevLoanAmount = sumBy(openBets, (bet) => bet.loanAmount ?? 0) const loanAmount = Math.min( newBetAmount, MAX_LOAN_PER_CONTRACT - prevLoanAmount diff --git a/common/new-contract.ts b/common/new-contract.ts index b0064cc7..d658a943 100644 --- a/common/new-contract.ts +++ b/common/new-contract.ts @@ -1,5 +1,4 @@ -import * as _ from 'lodash' - +import { range } from 'lodash' import { PHANTOM_ANTE } from './antes' import { Binary, @@ -131,7 +130,7 @@ const getNumericProps = ( min: number, max: number ) => { - const buckets = _.range(0, bucketCount).map((i) => i.toString()) + const buckets = range(0, bucketCount).map((i) => i.toString()) const betAnte = ante / bucketCount const pool = Object.fromEntries(buckets.map((answer) => [answer, betAnte])) diff --git a/common/package.json b/common/package.json index 033c193c..54e0e2fa 100644 --- a/common/package.json +++ b/common/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "private": true, "scripts": {}, + "sideEffects": false, "dependencies": { "lodash": "4.17.21" }, diff --git a/common/payouts-dpm.ts b/common/payouts-dpm.ts index 50e0b14c..c138d302 100644 --- a/common/payouts-dpm.ts +++ b/common/payouts-dpm.ts @@ -1,4 +1,4 @@ -import * as _ from 'lodash' +import { sum, groupBy, sumBy, mapValues } from 'lodash' import { Bet, NumericBet } from './bet' import { deductDpmFees, getDpmProbability } from './calculate-dpm' @@ -17,10 +17,10 @@ export const getDpmCancelPayouts = ( bets: Bet[] ) => { const { pool } = contract - const poolTotal = _.sum(Object.values(pool)) + const poolTotal = sum(Object.values(pool)) console.log('resolved N/A, pool M$', poolTotal) - const betSum = _.sumBy(bets, (b) => b.amount) + const betSum = sumBy(bets, (b) => b.amount) const payouts = bets.map((bet) => ({ userId: bet.userId, @@ -42,8 +42,8 @@ export const getDpmStandardPayouts = ( ) => { const winningBets = bets.filter((bet) => bet.outcome === outcome) - const poolTotal = _.sum(Object.values(contract.pool)) - const totalShares = _.sumBy(winningBets, (b) => b.shares) + const poolTotal = sum(Object.values(contract.pool)) + const totalShares = sumBy(winningBets, (b) => b.shares) const payouts = winningBets.map(({ userId, amount, shares }) => { const winnings = (shares / totalShares) * poolTotal @@ -54,7 +54,7 @@ export const getDpmStandardPayouts = ( return { userId, profit, payout } }) - const profits = _.sumBy(payouts, (po) => Math.max(0, po.profit)) + const profits = sumBy(payouts, (po) => Math.max(0, po.profit)) const creatorFee = DPM_CREATOR_FEE * profits const platformFee = DPM_PLATFORM_FEE * profits @@ -93,10 +93,10 @@ export const getNumericDpmPayouts = ( contract: FullContract, bets: NumericBet[] ) => { - const totalShares = _.sumBy(bets, (bet) => bet.allOutcomeShares[outcome] ?? 0) + const totalShares = sumBy(bets, (bet) => bet.allOutcomeShares[outcome] ?? 0) const winningBets = bets.filter((bet) => !!bet.allOutcomeShares[outcome]) - const poolTotal = _.sum(Object.values(contract.pool)) + const poolTotal = sum(Object.values(contract.pool)) const payouts = winningBets.map( ({ userId, allBetAmounts, allOutcomeShares }) => { @@ -112,7 +112,7 @@ export const getNumericDpmPayouts = ( } ) - const profits = _.sumBy(payouts, (po) => Math.max(0, po.profit)) + const profits = sumBy(payouts, (po) => Math.max(0, po.profit)) const creatorFee = DPM_CREATOR_FEE * profits const platformFee = DPM_PLATFORM_FEE * profits @@ -156,7 +156,7 @@ export const getDpmMktPayouts = ( ? getDpmProbability(contract.totalShares) : resolutionProbability - const weightedShareTotal = _.sumBy(bets, (b) => + const weightedShareTotal = sumBy(bets, (b) => b.outcome === 'YES' ? p * b.shares : (1 - p) * b.shares ) @@ -170,7 +170,7 @@ export const getDpmMktPayouts = ( return { userId, profit, payout } }) - const profits = _.sumBy(payouts, (po) => Math.max(0, po.profit)) + const profits = sumBy(payouts, (po) => Math.max(0, po.profit)) const creatorFee = DPM_CREATOR_FEE * profits const platformFee = DPM_PLATFORM_FEE * profits @@ -210,15 +210,15 @@ export const getPayoutsMultiOutcome = ( contract: FullContract, bets: Bet[] ) => { - const poolTotal = _.sum(Object.values(contract.pool)) + const poolTotal = sum(Object.values(contract.pool)) const winningBets = bets.filter((bet) => resolutions[bet.outcome]) - const betsByOutcome = _.groupBy(winningBets, (bet) => bet.outcome) - const sharesByOutcome = _.mapValues(betsByOutcome, (bets) => - _.sumBy(bets, (bet) => bet.shares) + const betsByOutcome = groupBy(winningBets, (bet) => bet.outcome) + const sharesByOutcome = mapValues(betsByOutcome, (bets) => + sumBy(bets, (bet) => bet.shares) ) - const probTotal = _.sum(Object.values(resolutions)) + const probTotal = sum(Object.values(resolutions)) const payouts = winningBets.map(({ userId, outcome, amount, shares }) => { const prob = resolutions[outcome] / probTotal @@ -229,7 +229,7 @@ export const getPayoutsMultiOutcome = ( return { userId, profit, payout } }) - const profits = _.sumBy(payouts, (po) => po.profit) + const profits = sumBy(payouts, (po) => po.profit) const creatorFee = DPM_CREATOR_FEE * profits const platformFee = DPM_PLATFORM_FEE * profits diff --git a/common/payouts-fixed.ts b/common/payouts-fixed.ts index 2729d544..1f4f2f4b 100644 --- a/common/payouts-fixed.ts +++ b/common/payouts-fixed.ts @@ -1,4 +1,4 @@ -import * as _ from 'lodash' +import { sum } from 'lodash' import { Bet } from './bet' import { getProbability } from './calculate' @@ -50,7 +50,7 @@ export const getStandardFixedPayouts = ( 'pool', contract.pool[outcome], 'payouts', - _.sum(payouts), + sum(payouts), 'creator fee', creatorPayout ) @@ -105,7 +105,7 @@ export const getMktFixedPayouts = ( 'pool', p * contract.pool.YES + (1 - p) * contract.pool.NO, 'payouts', - _.sum(payouts), + sum(payouts), 'creator fee', creatorPayout ) diff --git a/common/payouts.ts b/common/payouts.ts index aa1bbeb8..68fb8694 100644 --- a/common/payouts.ts +++ b/common/payouts.ts @@ -1,4 +1,4 @@ -import * as _ from 'lodash' +import { sumBy, groupBy, mapValues } from 'lodash' import { Bet, NumericBet } from './bet' import { @@ -32,16 +32,19 @@ export type Payout = { export const getLoanPayouts = (bets: Bet[]): Payout[] => { const betsWithLoans = bets.filter((bet) => bet.loanAmount) - const betsByUser = _.groupBy(betsWithLoans, (bet) => bet.userId) - const loansByUser = _.mapValues(betsByUser, (bets) => - _.sumBy(bets, (bet) => -(bet.loanAmount ?? 0)) + const betsByUser = groupBy(betsWithLoans, (bet) => bet.userId) + const loansByUser = mapValues(betsByUser, (bets) => + sumBy(bets, (bet) => -(bet.loanAmount ?? 0)) ) - return _.toPairs(loansByUser).map(([userId, payout]) => ({ userId, payout })) + return Object.entries(loansByUser).map(([userId, payout]) => ({ + userId, + payout, + })) } export const groupPayoutsByUser = (payouts: Payout[]) => { - const groups = _.groupBy(payouts, (payout) => payout.userId) - return _.mapValues(groups, (group) => _.sumBy(group, (g) => g.payout)) + const groups = groupBy(payouts, (payout) => payout.userId) + return mapValues(groups, (group) => sumBy(group, (g) => g.payout)) } export type PayoutInfo = { diff --git a/common/recommended-contracts.ts b/common/recommended-contracts.ts index 2de75293..3a6eca38 100644 --- a/common/recommended-contracts.ts +++ b/common/recommended-contracts.ts @@ -1,4 +1,4 @@ -import * as _ from 'lodash' +import { union, sum, sumBy, sortBy, groupBy, mapValues } from 'lodash' import { Bet } from './bet' import { Contract } from './contract' import { ClickEvent } from './tracking' @@ -21,13 +21,13 @@ export const getRecommendedContracts = ( const yourWordFrequency = contractsToWordFrequency(yourContracts) const otherWordFrequency = contractsToWordFrequency(notYourContracts) - const words = _.union( + const words = union( Object.keys(yourWordFrequency), Object.keys(otherWordFrequency) ) - const yourWeightedFrequency = _.fromPairs( - _.map(words, (word) => { + const yourWeightedFrequency = Object.fromEntries( + words.map((word) => { const [yourFreq, otherFreq] = [ yourWordFrequency[word] ?? 0, otherWordFrequency[word] ?? 0, @@ -47,7 +47,7 @@ export const getRecommendedContracts = ( const scoredContracts = contracts.map((contract) => { const wordFrequency = contractToWordFrequency(contract) - const score = _.sumBy(Object.keys(wordFrequency), (word) => { + const score = sumBy(Object.keys(wordFrequency), (word) => { const wordFreq = wordFrequency[word] ?? 0 const weight = yourWeightedFrequency[word] ?? 0 return wordFreq * weight @@ -59,7 +59,7 @@ export const getRecommendedContracts = ( } }) - return _.sortBy(scoredContracts, (scored) => -scored.score).map( + return sortBy(scoredContracts, (scored) => -scored.score).map( (scored) => scored.contract ) } @@ -87,8 +87,8 @@ const getWordsCount = (text: string) => { } const toFrequency = (counts: { [word: string]: number }) => { - const total = _.sum(Object.values(counts)) - return _.mapValues(counts, (count) => count / total) + const total = sum(Object.values(counts)) + return mapValues(counts, (count) => count / total) } const contractToWordFrequency = (contract: Contract) => @@ -108,8 +108,8 @@ export const getWordScores = ( clicks: ClickEvent[], bets: Bet[] ) => { - const contractClicks = _.groupBy(clicks, (click) => click.contractId) - const contractBets = _.groupBy(bets, (bet) => bet.contractId) + const contractClicks = groupBy(clicks, (click) => click.contractId) + const contractBets = groupBy(bets, (bet) => bet.contractId) const yourContracts = contracts.filter( (c) => @@ -117,25 +117,22 @@ export const getWordScores = ( ) 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 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) + const factor = + -1 * Math.log(viewCount + 1) + + 10 * Math.log(betCount + clickCount / 4 + 1) - return _.mapValues(wordsTfIdf, (tfIdf) => tfIdf * factor) - } - ) + 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( + const normalizedWordScores = mapValues( wordScores, (score) => (score - minScore) / (maxScore - minScore) ) @@ -156,7 +153,7 @@ export function getContractScore( if (Object.keys(wordScores).length === 0) return 1 const wordFrequency = contractToWordFrequency(contract) - const score = _.sumBy(Object.keys(wordFrequency), (word) => { + const score = sumBy(Object.keys(wordFrequency), (word) => { const wordFreq = wordFrequency[word] ?? 0 const weight = wordScores[word] ?? 0 return wordFreq * weight @@ -178,11 +175,13 @@ function calculateContractTfIdf(contracts: Contract[]) { } } - const wordIdf = _.mapValues(wordsCount, (count) => + const wordIdf = mapValues(wordsCount, (count) => Math.log(contracts.length / count) ) - const contractWordsTfIdf = _.map(contractFreq, (wordFreq) => - _.mapValues(wordFreq, (freq, word) => freq * wordIdf[word]) + const contractWordsTfIdf = contractFreq.map((wordFreq) => + mapValues(wordFreq, (freq, word) => freq * wordIdf[word]) + ) + return Object.fromEntries( + contracts.map((c, i) => [c.id, contractWordsTfIdf[i]]) ) - return _.fromPairs(contracts.map((c, i) => [c.id, contractWordsTfIdf[i]])) } diff --git a/common/scoring.ts b/common/scoring.ts index b55a29e5..d4855851 100644 --- a/common/scoring.ts +++ b/common/scoring.ts @@ -1,13 +1,13 @@ -import * as _ from 'lodash' +import { groupBy, sumBy, mapValues, partition } from 'lodash' import { Bet } from './bet' import { Binary, Contract, FullContract } from './contract' import { getPayouts } from './payouts' export function scoreCreators(contracts: Contract[], bets: Bet[][]) { - const creatorScore = _.mapValues( - _.groupBy(contracts, ({ creatorId }) => creatorId), - (contracts) => _.sumBy(contracts, ({ pool }) => pool.YES + pool.NO) + const creatorScore = mapValues( + groupBy(contracts, ({ creatorId }) => creatorId), + (contracts) => sumBy(contracts, ({ pool }) => pool.YES + pool.NO) ) return creatorScore @@ -30,7 +30,7 @@ export function scoreUsersByContract( ) { const { resolution, resolutionProbability } = contract - const [closedBets, openBets] = _.partition( + const [closedBets, openBets] = partition( bets, (bet) => bet.isSold || bet.sale ) @@ -58,9 +58,9 @@ export function scoreUsersByContract( const netPayouts = [...resolvePayouts, ...salePayouts, ...investments] - const userScore = _.mapValues( - _.groupBy(netPayouts, (payout) => payout.userId), - (payouts) => _.sumBy(payouts, ({ payout }) => payout) + const userScore = mapValues( + groupBy(netPayouts, (payout) => payout.userId), + (payouts) => sumBy(payouts, ({ payout }) => payout) ) return userScore diff --git a/common/util/object.ts b/common/util/object.ts index db504cc3..031e674c 100644 --- a/common/util/object.ts +++ b/common/util/object.ts @@ -1,4 +1,4 @@ -import * as _ from 'lodash' +import { union } from 'lodash' export const removeUndefinedProps = (obj: T): T => { let newObj: any = {} @@ -14,7 +14,7 @@ export const addObjects = ( obj1: T, obj2: T ) => { - const keys = _.union(Object.keys(obj1), Object.keys(obj2)) + const keys = union(Object.keys(obj1), Object.keys(obj2)) const newObj = {} as any for (let key of keys) { diff --git a/functions/.eslintrc.js b/functions/.eslintrc.js index 24ceca8e..749ab4f5 100644 --- a/functions/.eslintrc.js +++ b/functions/.eslintrc.js @@ -1,4 +1,5 @@ module.exports = { + plugins: ['lodash'], extends: ['eslint:recommended'], ignorePatterns: ['lib'], env: { @@ -17,5 +18,6 @@ module.exports = { rules: { 'no-unused-vars': 'off', 'no-constant-condition': ['error', { checkLoops: false }], + 'lodash/import-scope': [2, 'member'], }, } diff --git a/functions/src/create-fold.ts b/functions/src/create-fold.ts index 36d1d269..2d808673 100644 --- a/functions/src/create-fold.ts +++ b/functions/src/create-fold.ts @@ -1,6 +1,5 @@ import * as functions from 'firebase-functions' import * as admin from 'firebase-admin' -import * as _ from 'lodash' import { getUser } from './utils' import { Contract } from '../../common/contract' @@ -34,7 +33,7 @@ export const createFold = functions.runWith({ minInstances: 1 }).https.onCall( return { status: 'error', message: 'About must be a string' } about = about.trim().slice(0, 140) - if (!_.isArray(tags)) + if (!Array.isArray(tags)) return { status: 'error', message: 'Tags must be an array of strings' } console.log( diff --git a/functions/src/emails.ts b/functions/src/emails.ts index 2b7db259..8aba4fb5 100644 --- a/functions/src/emails.ts +++ b/functions/src/emails.ts @@ -1,5 +1,3 @@ -import * as _ from 'lodash' - import { DOMAIN, PROJECT_ID } from '../../common/envs/constants' import { Answer } from '../../common/answer' import { Bet } from '../../common/bet' diff --git a/functions/src/on-create-bet.ts b/functions/src/on-create-bet.ts index deaa4c4a..af3a6e84 100644 --- a/functions/src/on-create-bet.ts +++ b/functions/src/on-create-bet.ts @@ -1,6 +1,5 @@ import * as functions from 'firebase-functions' import * as admin from 'firebase-admin' -import * as _ from 'lodash' import { getContract } from './utils' import { Bet } from '../../common/bet' diff --git a/functions/src/on-create-comment.ts b/functions/src/on-create-comment.ts index 7699843c..ecbd9ea1 100644 --- a/functions/src/on-create-comment.ts +++ b/functions/src/on-create-comment.ts @@ -1,6 +1,6 @@ import * as functions from 'firebase-functions' import * as admin from 'firebase-admin' -import * as _ from 'lodash' +import { uniq } from 'lodash' import { getContract, getUser, getValues } from './utils' import { Comment } from '../../common/comment' @@ -60,7 +60,7 @@ export const onCreateComment = functions.firestore firestore.collection('contracts').doc(contractId).collection('comments') ) - const recipientUserIds = _.uniq([ + const recipientUserIds = uniq([ contract.creatorId, ...comments.map((comment) => comment.userId), ]).filter((id) => id !== comment.userId) diff --git a/functions/src/redeem-shares.ts b/functions/src/redeem-shares.ts index 08d87a8b..b4c31223 100644 --- a/functions/src/redeem-shares.ts +++ b/functions/src/redeem-shares.ts @@ -1,5 +1,5 @@ import * as admin from 'firebase-admin' -import * as _ from 'lodash' +import { partition, sumBy } from 'lodash' import { Bet } from '../../common/bet' import { getProbability } from '../../common/calculate' @@ -25,14 +25,14 @@ export const redeemShares = async (userId: string, contractId: string) => { .where('userId', '==', userId) ) const bets = betsSnap.docs.map((doc) => doc.data() as Bet) - const [yesBets, noBets] = _.partition(bets, (b) => b.outcome === 'YES') - const yesShares = _.sumBy(yesBets, (b) => b.shares) - const noShares = _.sumBy(noBets, (b) => b.shares) + const [yesBets, noBets] = partition(bets, (b) => b.outcome === 'YES') + const yesShares = sumBy(yesBets, (b) => b.shares) + const noShares = sumBy(noBets, (b) => b.shares) const amount = Math.min(yesShares, noShares) if (amount <= 0) return - const prevLoanAmount = _.sumBy(bets, (bet) => bet.loanAmount ?? 0) + const prevLoanAmount = sumBy(bets, (bet) => bet.loanAmount ?? 0) const loanPaid = Math.min(prevLoanAmount, amount) const netAmount = amount - loanPaid diff --git a/functions/src/resolve-market.ts b/functions/src/resolve-market.ts index 3108fe07..183a5624 100644 --- a/functions/src/resolve-market.ts +++ b/functions/src/resolve-market.ts @@ -1,6 +1,6 @@ import * as functions from 'firebase-functions' import * as admin from 'firebase-admin' -import * as _ from 'lodash' +import { difference, uniq, mapValues, groupBy, sumBy } from 'lodash' import { Contract } from '../../common/contract' import { User } from '../../common/user' @@ -187,13 +187,13 @@ const sendResolutionEmails = async ( resolutionProbability?: number, resolutions?: { [outcome: string]: number } ) => { - const nonWinners = _.difference( - _.uniq(openBets.map(({ userId }) => userId)), + const nonWinners = difference( + uniq(openBets.map(({ userId }) => userId)), Object.keys(userPayouts) ) - const investedByUser = _.mapValues( - _.groupBy(openBets, (bet) => bet.userId), - (bets) => _.sumBy(bets, (bet) => bet.amount) + const investedByUser = mapValues( + groupBy(openBets, (bet) => bet.userId), + (bets) => sumBy(bets, (bet) => bet.amount) ) const emailPayouts = [ ...Object.entries(userPayouts), diff --git a/functions/src/scripts/cache-views.ts b/functions/src/scripts/cache-views.ts index c7145a1e..dc54bdf9 100644 --- a/functions/src/scripts/cache-views.ts +++ b/functions/src/scripts/cache-views.ts @@ -1,5 +1,4 @@ import * as admin from 'firebase-admin' -import * as _ from 'lodash' import { initAdmin } from './script-init' initAdmin() diff --git a/functions/src/scripts/change-user-info.ts b/functions/src/scripts/change-user-info.ts index a6a01e59..64858c46 100644 --- a/functions/src/scripts/change-user-info.ts +++ b/functions/src/scripts/change-user-info.ts @@ -1,5 +1,4 @@ import * as admin from 'firebase-admin' -import * as _ from 'lodash' import { initAdmin } from './script-init' initAdmin() diff --git a/functions/src/scripts/correct-bet-probability.ts b/functions/src/scripts/correct-bet-probability.ts index 3b57dbeb..77321b7c 100644 --- a/functions/src/scripts/correct-bet-probability.ts +++ b/functions/src/scripts/correct-bet-probability.ts @@ -1,5 +1,5 @@ import * as admin from 'firebase-admin' -import * as _ from 'lodash' +import { sortBy } from 'lodash' import { initAdmin } from './script-init' initAdmin() @@ -20,7 +20,7 @@ async function migrateContract( .get() .then((snap) => snap.docs.map((bet) => bet.data() as Bet)) - const lastBet = _.sortBy(bets, (bet) => -bet.createdTime)[0] + const lastBet = sortBy(bets, (bet) => -bet.createdTime)[0] if (lastBet) { const probAfter = getDpmProbability(contract.totalShares) diff --git a/functions/src/scripts/create-private-users.ts b/functions/src/scripts/create-private-users.ts index 8051a447..37168a39 100644 --- a/functions/src/scripts/create-private-users.ts +++ b/functions/src/scripts/create-private-users.ts @@ -1,5 +1,4 @@ import * as admin from 'firebase-admin' -import * as _ from 'lodash' import { initAdmin } from './script-init' initAdmin() diff --git a/functions/src/scripts/get-json-dump.ts b/functions/src/scripts/get-json-dump.ts index b9909132..80510203 100644 --- a/functions/src/scripts/get-json-dump.ts +++ b/functions/src/scripts/get-json-dump.ts @@ -1,5 +1,4 @@ import * as admin from 'firebase-admin' -import * as _ from 'lodash' import * as fs from 'fs' import { initAdmin } from './script-init' diff --git a/functions/src/scripts/lowercase-fold-tags.ts b/functions/src/scripts/lowercase-fold-tags.ts index 80b79a33..e2912e31 100644 --- a/functions/src/scripts/lowercase-fold-tags.ts +++ b/functions/src/scripts/lowercase-fold-tags.ts @@ -1,5 +1,5 @@ import * as admin from 'firebase-admin' -import * as _ from 'lodash' +import { uniq } from 'lodash' import { initAdmin } from './script-init' initAdmin() @@ -19,7 +19,7 @@ async function lowercaseFoldTags() { const foldRef = firestore.doc(`folds/${fold.id}`) const { tags } = fold - const lowercaseTags = _.uniq(tags.map((tag) => tag.toLowerCase())) + const lowercaseTags = uniq(tags.map((tag) => tag.toLowerCase())) console.log('Adding lowercase tags', fold.slug, lowercaseTags) diff --git a/functions/src/scripts/make-contracts-public.ts b/functions/src/scripts/make-contracts-public.ts index 19d2e196..da28d16a 100644 --- a/functions/src/scripts/make-contracts-public.ts +++ b/functions/src/scripts/make-contracts-public.ts @@ -1,5 +1,4 @@ import * as admin from 'firebase-admin' -import * as _ from 'lodash' import { initAdmin } from './script-init' initAdmin() diff --git a/functions/src/scripts/migrate-contract.ts b/functions/src/scripts/migrate-contract.ts index 718cf62e..b3ed6025 100644 --- a/functions/src/scripts/migrate-contract.ts +++ b/functions/src/scripts/migrate-contract.ts @@ -1,5 +1,5 @@ import * as admin from 'firebase-admin' -import * as _ from 'lodash' +import { sumBy } from 'lodash' import { initAdmin } from './script-init' initAdmin() @@ -25,8 +25,8 @@ async function migrateContract(contractRef: DocRef, contract: Contract) { .then((snap) => snap.docs.map((bet) => bet.data() as Bet)) const totalShares = { - YES: _.sumBy(bets, (bet) => (bet.outcome === 'YES' ? bet.shares : 0)), - NO: _.sumBy(bets, (bet) => (bet.outcome === 'NO' ? bet.shares : 0)), + YES: sumBy(bets, (bet) => (bet.outcome === 'YES' ? bet.shares : 0)), + NO: sumBy(bets, (bet) => (bet.outcome === 'NO' ? bet.shares : 0)), } await contractRef.update({ totalShares }) diff --git a/functions/src/scripts/migrate-to-cfmm.ts b/functions/src/scripts/migrate-to-cfmm.ts index 874011ca..65452e6c 100644 --- a/functions/src/scripts/migrate-to-cfmm.ts +++ b/functions/src/scripts/migrate-to-cfmm.ts @@ -1,5 +1,5 @@ import * as admin from 'firebase-admin' -import * as _ from 'lodash' +import { sortBy } from 'lodash' import { initAdmin } from './script-init' initAdmin() @@ -48,7 +48,7 @@ async function recalculateContract(contractRef: DocRef, isCommit = false) { const betsRef = contractRef.collection('bets') const betDocs = await transaction.get(betsRef) - const bets = _.sortBy( + const bets = sortBy( betDocs.docs.map((d) => d.data() as Bet), (b) => b.createdTime ) diff --git a/functions/src/scripts/migrate-to-dpm-2.ts b/functions/src/scripts/migrate-to-dpm-2.ts index 2c6f066f..64294ed9 100644 --- a/functions/src/scripts/migrate-to-dpm-2.ts +++ b/functions/src/scripts/migrate-to-dpm-2.ts @@ -1,5 +1,5 @@ import * as admin from 'firebase-admin' -import * as _ from 'lodash' +import { sortBy, sumBy } from 'lodash' import { initAdmin } from './script-init' initAdmin() @@ -35,7 +35,7 @@ async function recalculateContract( const contract = contractDoc.data() as FullContract const betDocs = await transaction.get(contractRef.collection('bets')) - const bets = _.sortBy( + const bets = sortBy( betDocs.docs.map((d) => d.data() as Bet), (b) => b.createdTime ) @@ -43,8 +43,8 @@ async function recalculateContract( const phantomAnte = startPool.YES + startPool.NO const leftovers = - _.sumBy(bets, (b) => b.amount) - - _.sumBy(bets, (b) => { + sumBy(bets, (b) => b.amount) - + sumBy(bets, (b) => { if (!b.sale) return b.amount const soldBet = bets.find((bet) => bet.id === b.sale?.betId) return soldBet?.amount || 0 diff --git a/functions/src/scripts/pay-out-contract-again.ts b/functions/src/scripts/pay-out-contract-again.ts index 916b9efb..1686ebd9 100644 --- a/functions/src/scripts/pay-out-contract-again.ts +++ b/functions/src/scripts/pay-out-contract-again.ts @@ -1,5 +1,5 @@ import * as admin from 'firebase-admin' -import * as _ from 'lodash' +import { flatten, groupBy, sumBy, mapValues } from 'lodash' import { initAdmin } from './script-init' initAdmin() @@ -35,12 +35,12 @@ async function checkIfPayOutAgain(contractRef: DocRef, contract: Contract) { ) const loanPayouts = getLoanPayouts(openBets) - const groups = _.groupBy( + const groups = groupBy( [...payouts, ...loanPayouts], (payout) => payout.userId ) - const userPayouts = _.mapValues(groups, (group) => - _.sumBy(group, (g) => g.payout) + const userPayouts = mapValues(groups, (group) => + sumBy(group, (g) => g.payout) ) const entries = Object.entries(userPayouts) @@ -93,7 +93,7 @@ async function payOutContractAgain() { ) ) - const flattened = _.flatten(toPayOutAgain.map((d) => d.toBePaidOut)) + const flattened = flatten(toPayOutAgain.map((d) => d.toBePaidOut)) for (const [userId, payout] of flattened) { console.log('Paying out', userId, payout) diff --git a/functions/src/scripts/recalculate-contract-totals.ts b/functions/src/scripts/recalculate-contract-totals.ts index 39942542..5c7cd155 100644 --- a/functions/src/scripts/recalculate-contract-totals.ts +++ b/functions/src/scripts/recalculate-contract-totals.ts @@ -1,5 +1,5 @@ import * as admin from 'firebase-admin' -import * as _ from 'lodash' +import { sumBy } from 'lodash' import { initAdmin } from './script-init' initAdmin() @@ -20,13 +20,13 @@ async function recalculateContract(contractRef: DocRef, contract: Contract) { const openBets = bets.filter((b) => !b.isSold && !b.sale) const totalShares = { - YES: _.sumBy(openBets, (bet) => (bet.outcome === 'YES' ? bet.shares : 0)), - NO: _.sumBy(openBets, (bet) => (bet.outcome === 'NO' ? bet.shares : 0)), + YES: sumBy(openBets, (bet) => (bet.outcome === 'YES' ? bet.shares : 0)), + NO: sumBy(openBets, (bet) => (bet.outcome === 'NO' ? bet.shares : 0)), } const totalBets = { - YES: _.sumBy(openBets, (bet) => (bet.outcome === 'YES' ? bet.amount : 0)), - NO: _.sumBy(openBets, (bet) => (bet.outcome === 'NO' ? bet.amount : 0)), + YES: sumBy(openBets, (bet) => (bet.outcome === 'YES' ? bet.amount : 0)), + NO: sumBy(openBets, (bet) => (bet.outcome === 'NO' ? bet.amount : 0)), } await contractRef.update({ totalShares, totalBets }) diff --git a/functions/src/scripts/remove-answer-ante.ts b/functions/src/scripts/remove-answer-ante.ts index eb49af6c..43c73f05 100644 --- a/functions/src/scripts/remove-answer-ante.ts +++ b/functions/src/scripts/remove-answer-ante.ts @@ -1,5 +1,4 @@ import * as admin from 'firebase-admin' -import * as _ from 'lodash' import { initAdmin } from './script-init' initAdmin() diff --git a/functions/src/scripts/rename-user-contracts.ts b/functions/src/scripts/rename-user-contracts.ts index 9b0f569b..b6b85f80 100644 --- a/functions/src/scripts/rename-user-contracts.ts +++ b/functions/src/scripts/rename-user-contracts.ts @@ -1,5 +1,4 @@ import * as admin from 'firebase-admin' -import * as _ from 'lodash' import { initAdmin } from './script-init' initAdmin() diff --git a/functions/src/scripts/update-contract-tags.ts b/functions/src/scripts/update-contract-tags.ts index 1dda5615..37a2b60a 100644 --- a/functions/src/scripts/update-contract-tags.ts +++ b/functions/src/scripts/update-contract-tags.ts @@ -1,5 +1,5 @@ import * as admin from 'firebase-admin' -import * as _ from 'lodash' +import { uniq } from 'lodash' import { initAdmin } from './script-init' initAdmin() @@ -19,7 +19,7 @@ async function updateContractTags() { for (const contract of contracts) { const contractRef = firestore.doc(`contracts/${contract.id}`) - const tags = _.uniq([ + const tags = uniq([ ...parseTags(contract.question + contract.description), ...(contract.tags ?? []), ]) diff --git a/functions/src/scripts/update-feed.ts b/functions/src/scripts/update-feed.ts index fee7398d..c5cba142 100644 --- a/functions/src/scripts/update-feed.ts +++ b/functions/src/scripts/update-feed.ts @@ -1,5 +1,4 @@ import * as admin from 'firebase-admin' -import * as _ from 'lodash' import { initAdmin } from './script-init' initAdmin() diff --git a/functions/src/scripts/update-last-comment-time.ts b/functions/src/scripts/update-last-comment-time.ts index ae950fbe..9e7a5a37 100644 --- a/functions/src/scripts/update-last-comment-time.ts +++ b/functions/src/scripts/update-last-comment-time.ts @@ -1,5 +1,4 @@ import * as admin from 'firebase-admin' -import * as _ from 'lodash' import { initAdmin } from './script-init' initAdmin() diff --git a/functions/src/sell-shares.ts b/functions/src/sell-shares.ts index e4dbcbc9..08e7d7c5 100644 --- a/functions/src/sell-shares.ts +++ b/functions/src/sell-shares.ts @@ -1,4 +1,4 @@ -import * as _ from 'lodash' +import { partition, sumBy } from 'lodash' import * as admin from 'firebase-admin' import * as functions from 'firebase-functions' @@ -51,15 +51,15 @@ export const sellShares = functions.runWith({ minInstances: 1 }).https.onCall( contractDoc.collection('bets').where('userId', '==', userId) ) - const prevLoanAmount = _.sumBy(userBets, (bet) => bet.loanAmount ?? 0) + const prevLoanAmount = sumBy(userBets, (bet) => bet.loanAmount ?? 0) - const [yesBets, noBets] = _.partition( + const [yesBets, noBets] = partition( userBets ?? [], (bet) => bet.outcome === 'YES' ) const [yesShares, noShares] = [ - _.sumBy(yesBets, (bet) => bet.shares), - _.sumBy(noBets, (bet) => bet.shares), + sumBy(yesBets, (bet) => bet.shares), + sumBy(noBets, (bet) => bet.shares), ] const maxShares = outcome === 'YES' ? yesShares : noShares diff --git a/functions/src/unsubscribe.ts b/functions/src/unsubscribe.ts index 317cf61e..baea8022 100644 --- a/functions/src/unsubscribe.ts +++ b/functions/src/unsubscribe.ts @@ -1,6 +1,5 @@ import * as functions from 'firebase-functions' import * as admin from 'firebase-admin' -import * as _ from 'lodash' import { getUser } from './utils' import { PrivateUser } from '../../common/user' diff --git a/functions/src/update-contract-metrics.ts b/functions/src/update-contract-metrics.ts index c3801df6..ad66d21f 100644 --- a/functions/src/update-contract-metrics.ts +++ b/functions/src/update-contract-metrics.ts @@ -1,6 +1,6 @@ import * as functions from 'firebase-functions' import * as admin from 'firebase-admin' -import * as _ from 'lodash' +import { sumBy } from 'lodash' import { getValues } from './utils' import { Contract } from '../../common/contract' @@ -39,5 +39,5 @@ const computeVolumeFrom = async (contract: Contract, timeAgoMs: number) => { .where('createdTime', '>', Date.now() - timeAgoMs) ) - return _.sumBy(bets, (bet) => (bet.isRedemption ? 0 : Math.abs(bet.amount))) + return sumBy(bets, (bet) => (bet.isRedemption ? 0 : Math.abs(bet.amount))) } diff --git a/functions/src/update-feed.ts b/functions/src/update-feed.ts index be58bf01..6c58970a 100644 --- a/functions/src/update-feed.ts +++ b/functions/src/update-feed.ts @@ -1,6 +1,6 @@ -import * as _ from 'lodash' import * as functions from 'firebase-functions' import * as admin from 'firebase-admin' +import { shuffle, sortBy } from 'lodash' import { getValue, getValues } from './utils' import { Contract } from '../../common/contract' @@ -30,7 +30,7 @@ const BATCH_SIZE = 30 const MAX_BATCHES = 50 const getUserBatches = async () => { - const users = _.shuffle(await getValues(firestore.collection('users'))) + const users = shuffle(await getValues(firestore.collection('users'))) let userBatches: User[][] = [] for (let i = 0; i < users.length; i += BATCH_SIZE) { userBatches.push(users.slice(i, i + BATCH_SIZE)) @@ -128,7 +128,7 @@ export const computeFeed = async (user: User, contracts: Contract[]) => { return [contract, score] as [Contract, number] }) - const sortedContracts = _.sortBy( + const sortedContracts = sortBy( scoredContracts, ([_, score]) => score ).reverse() diff --git a/functions/src/update-recommendations.ts b/functions/src/update-recommendations.ts index 4e656dda..bc82291c 100644 --- a/functions/src/update-recommendations.ts +++ b/functions/src/update-recommendations.ts @@ -1,6 +1,5 @@ import * as functions from 'firebase-functions' import * as admin from 'firebase-admin' -import * as _ from 'lodash' import { getValue, getValues } from './utils' import { Contract } from '../../common/contract' diff --git a/functions/src/update-user-metrics.ts b/functions/src/update-user-metrics.ts index 70fd1bc5..08dc63e6 100644 --- a/functions/src/update-user-metrics.ts +++ b/functions/src/update-user-metrics.ts @@ -1,6 +1,6 @@ import * as functions from 'firebase-functions' import * as admin from 'firebase-admin' -import * as _ from 'lodash' +import { sum, sumBy } from 'lodash' import { getValues } from './utils' import { Contract } from '../../common/contract' @@ -19,7 +19,7 @@ export const updateUserMetrics = functions.pubsub getValues(firestore.collection('contracts')), ]) - const contractsDict = _.fromPairs( + const contractsDict = Object.fromEntries( contracts.map((contract) => [contract.id, contract]) ) @@ -43,12 +43,12 @@ export const updateUserMetrics = functions.pubsub const computeInvestmentValue = async ( user: User, - contractsDict: _.Dictionary + contractsDict: { [k: string]: Contract } ) => { const query = firestore.collectionGroup('bets').where('userId', '==', user.id) const bets = await getValues(query) - return _.sumBy(bets, (bet) => { + return sumBy(bets, (bet) => { const contract = contractsDict[bet.contractId] if (!contract || contract.isResolved) return 0 if (bet.sale || bet.isSold) return 0 @@ -60,20 +60,20 @@ const computeInvestmentValue = async ( const computeTotalPool = async ( user: User, - contractsDict: _.Dictionary + contractsDict: { [k: string]: Contract } ) => { const creatorContracts = Object.values(contractsDict).filter( (contract) => contract.creatorId === user.id ) const pools = creatorContracts.map((contract) => - _.sum(Object.values(contract.pool)) + sum(Object.values(contract.pool)) ) - return _.sum(pools) + return sum(pools) } const computeVolume = async (contract: Contract) => { const bets = await getValues( firestore.collection(`contracts/${contract.id}/bets`) ) - return _.sumBy(bets, (bet) => Math.abs(bet.amount)) + return sumBy(bets, (bet) => Math.abs(bet.amount)) } diff --git a/package.json b/package.json index d1dbf070..9af3a947 100644 --- a/package.json +++ b/package.json @@ -9,10 +9,11 @@ "scripts": {}, "dependencies": {}, "devDependencies": { - "typescript": "4.6.4", "@typescript-eslint/eslint-plugin": "5.25.0", "@typescript-eslint/parser": "5.25.0", "eslint": "8.15.0", - "prettier": "2.5.0" + "eslint-plugin-lodash": "^7.4.0", + "prettier": "2.5.0", + "typescript": "4.6.4" } } diff --git a/web/.eslintrc.js b/web/.eslintrc.js index eafcc74d..f864ffa2 100644 --- a/web/.eslintrc.js +++ b/web/.eslintrc.js @@ -1,9 +1,10 @@ module.exports = { parser: '@typescript-eslint/parser', + plugins: ['lodash'], extends: ['plugin:react-hooks/recommended', 'plugin:@next/next/recommended'], rules: { - // Add or disable rules here. '@next/next/no-img-element': 'off', '@next/next/no-typos': 'off', + 'lodash/import-scope': [2, 'member'], }, } diff --git a/web/components/analytics/charts.tsx b/web/components/analytics/charts.tsx index 6a3b0b52..9e156a5b 100644 --- a/web/components/analytics/charts.tsx +++ b/web/components/analytics/charts.tsx @@ -1,6 +1,6 @@ import { Point, ResponsiveLine } from '@nivo/line' import dayjs from 'dayjs' -import _ from 'lodash' +import { zip } from 'lodash' import { useWindowSize } from 'web/hooks/use-window-size' import { Col } from '../layout/col' @@ -16,7 +16,7 @@ export function DailyCountChart(props: { dayjs(startDate).add(i, 'day').toDate() ) - const points = _.zip(dates, dailyCounts).map(([date, betCount]) => ({ + const points = zip(dates, dailyCounts).map(([date, betCount]) => ({ x: date, y: betCount, })) @@ -68,7 +68,7 @@ export function DailyPercentChart(props: { dayjs(startDate).add(i, 'day').toDate() ) - const points = _.zip(dates, dailyPercent).map(([date, betCount]) => ({ + const points = zip(dates, dailyPercent).map(([date, betCount]) => ({ x: date, y: betCount, })) diff --git a/web/components/answers/answer-resolve-panel.tsx b/web/components/answers/answer-resolve-panel.tsx index 38cee874..7de19b5d 100644 --- a/web/components/answers/answer-resolve-panel.tsx +++ b/web/components/answers/answer-resolve-panel.tsx @@ -1,5 +1,5 @@ import clsx from 'clsx' -import _ from 'lodash' +import { sum, mapValues } from 'lodash' import { useState } from 'react' import { DPM, FreeResponse, FullContract } from 'common/contract' @@ -30,8 +30,8 @@ export function AnswerResolvePanel(props: { setIsSubmitting(true) - const totalProb = _.sum(Object.values(chosenAnswers)) - const normalizedProbs = _.mapValues( + const totalProb = sum(Object.values(chosenAnswers)) + const normalizedProbs = mapValues( chosenAnswers, (prob) => (100 * prob) / totalProb ) diff --git a/web/components/answers/answers-graph.tsx b/web/components/answers/answers-graph.tsx index 5853f4a9..4d983353 100644 --- a/web/components/answers/answers-graph.tsx +++ b/web/components/answers/answers-graph.tsx @@ -1,7 +1,7 @@ import { DatumValue } from '@nivo/core' import { ResponsiveLine } from '@nivo/line' import dayjs from 'dayjs' -import _ from 'lodash' +import { groupBy, sortBy, sumBy } from 'lodash' import { memo } from 'react' import { Bet } from 'common/bet' @@ -48,7 +48,7 @@ export const AnswersGraph = memo(function AnswersGraph(props: { // to the right. latestTime.add(1, 'month').valueOf() - const times = _.sortBy([ + const times = sortBy([ createdTime, ...bets.map((bet) => bet.createdTime), endTime, @@ -167,7 +167,7 @@ const computeProbsByOutcome = ( ) => { const { totalBets } = contract - const betsByOutcome = _.groupBy(bets, (bet) => bet.outcome) + const betsByOutcome = groupBy(bets, (bet) => bet.outcome) const outcomes = Object.keys(betsByOutcome).filter((outcome) => { const maxProb = Math.max( ...betsByOutcome[outcome].map((bet) => bet.probAfter) @@ -175,15 +175,15 @@ const computeProbsByOutcome = ( return outcome !== '0' && maxProb > 0.02 && totalBets[outcome] > 0.000000001 }) - const trackedOutcomes = _.sortBy( + const trackedOutcomes = sortBy( outcomes, (outcome) => -1 * getOutcomeProbability(contract, outcome) ).slice(0, NUM_LINES) - const probsByOutcome = _.fromPairs( + const probsByOutcome = Object.fromEntries( trackedOutcomes.map((outcome) => [outcome, [] as number[]]) ) - const sharesByOutcome = _.fromPairs( + const sharesByOutcome = Object.fromEntries( Object.keys(betsByOutcome).map((outcome) => [outcome, 0]) ) @@ -191,7 +191,7 @@ const computeProbsByOutcome = ( const { outcome, shares } = bet sharesByOutcome[outcome] += shares - const sharesSquared = _.sumBy( + const sharesSquared = sumBy( Object.values(sharesByOutcome).map((shares) => shares ** 2) ) diff --git a/web/components/answers/answers-panel.tsx b/web/components/answers/answers-panel.tsx index b132b3cb..7b3bc8cd 100644 --- a/web/components/answers/answers-panel.tsx +++ b/web/components/answers/answers-panel.tsx @@ -1,5 +1,5 @@ -import _ from 'lodash' -import React, { useLayoutEffect, useState } from 'react' +import { sortBy, partition, sum, uniq } from 'lodash' +import { useLayoutEffect, useState } from 'react' import { DPM, FreeResponse, FullContract } from 'common/contract' import { Col } from '../layout/col' @@ -32,7 +32,7 @@ export function AnswersPanel(props: { const { creatorId, resolution, resolutions, totalBets } = contract const answers = useAnswers(contract.id) ?? contract.answers - const [winningAnswers, losingAnswers] = _.partition( + const [winningAnswers, losingAnswers] = partition( answers.filter( (answer) => answer.id !== '0' && totalBets[answer.id] > 0.000000001 ), @@ -40,10 +40,10 @@ export function AnswersPanel(props: { answer.id === resolution || (resolutions && resolutions[answer.id]) ) const sortedAnswers = [ - ..._.sortBy(winningAnswers, (answer) => + ...sortBy(winningAnswers, (answer) => resolutions ? -1 * resolutions[answer.id] : 0 ), - ..._.sortBy( + ...sortBy( resolution ? [] : losingAnswers, (answer) => -1 * getDpmOutcomeProbability(contract.totalShares, answer.id) ), @@ -58,7 +58,7 @@ export function AnswersPanel(props: { [answerId: string]: number }>({}) - const chosenTotal = _.sum(Object.values(chosenAnswers)) + const chosenTotal = sum(Object.values(chosenAnswers)) const answerItems = getAnswerItems( contract, @@ -158,10 +158,10 @@ function getAnswerItems( answers: Answer[], user: User | undefined | null ) { - let outcomes = _.uniq( - answers.map((answer) => answer.number.toString()) - ).filter((outcome) => getOutcomeProbability(contract, outcome) > 0.0001) - outcomes = _.sortBy(outcomes, (outcome) => + let outcomes = uniq(answers.map((answer) => answer.number.toString())).filter( + (outcome) => getOutcomeProbability(contract, outcome) > 0.0001 + ) + outcomes = sortBy(outcomes, (outcome) => getOutcomeProbability(contract, outcome) ).reverse() diff --git a/web/components/bet-panel.tsx b/web/components/bet-panel.tsx index 6fdf8cea..bb3cacb8 100644 --- a/web/components/bet-panel.tsx +++ b/web/components/bet-panel.tsx @@ -1,6 +1,6 @@ import clsx from 'clsx' -import _ from 'lodash' import React, { useEffect, useState } from 'react' +import { partition, sumBy } from 'lodash' import { useUser } from 'web/hooks/use-user' import { Binary, CPMM, DPM, FullContract } from 'common/contract' @@ -441,13 +441,13 @@ export function SellPanel(props: { const resultProb = getCpmmProbability(newPool, contract.p) const openUserBets = userBets.filter((bet) => !bet.isSold && !bet.sale) - const [yesBets, noBets] = _.partition( + const [yesBets, noBets] = partition( openUserBets, (bet) => bet.outcome === 'YES' ) const [yesShares, noShares] = [ - _.sumBy(yesBets, (bet) => bet.shares), - _.sumBy(noBets, (bet) => bet.shares), + sumBy(yesBets, (bet) => bet.shares), + sumBy(noBets, (bet) => bet.shares), ] const sellOutcome = yesShares ? 'YES' : noShares ? 'NO' : undefined diff --git a/web/components/bets-list.tsx b/web/components/bets-list.tsx index 51d96efc..400a5f7f 100644 --- a/web/components/bets-list.tsx +++ b/web/components/bets-list.tsx @@ -1,5 +1,13 @@ import Link from 'next/link' -import _ from 'lodash' +import { + uniq, + groupBy, + mapValues, + sortBy, + partition, + sumBy, + throttle, +} from 'lodash' import dayjs from 'dayjs' import { useEffect, useState } from 'react' import clsx from 'clsx' @@ -54,7 +62,7 @@ export function BetsList(props: { user: User }) { useEffect(() => { if (bets) { - const contractIds = _.uniq(bets.map((bet) => bet.contractId)) + const contractIds = uniq(bets.map((bet) => bet.contractId)) let disposed = false Promise.all(contractIds.map((id) => getContractFromId(id))).then( @@ -84,10 +92,10 @@ export function BetsList(props: { user: User }) { if (bets.length === 0) return // Decending creation time. bets.sort((bet1, bet2) => bet2.createdTime - bet1.createdTime) - const contractBets = _.groupBy(bets, 'contractId') - const contractsById = _.fromPairs(contracts.map((c) => [c.id, c])) + const contractBets = groupBy(bets, 'contractId') + const contractsById = Object.fromEntries(contracts.map((c) => [c.id, c])) - const contractsMetrics = _.mapValues(contractBets, (bets, contractId) => { + const contractsMetrics = mapValues(contractBets, (bets, contractId) => { const contract = contractsById[contractId] if (!contract) return getContractBetNullMetrics() return getContractBetMetrics(contract, bets) @@ -110,7 +118,7 @@ export function BetsList(props: { user: User }) { (filter === 'open' ? -1 : 1) * (c.resolutionTime ?? c.closeTime ?? Infinity), } - const displayedContracts = _.sortBy(contracts, SORTS[sort]) + const displayedContracts = sortBy(contracts, SORTS[sort]) .reverse() .filter(FILTERS[filter]) .filter((c) => { @@ -121,20 +129,20 @@ export function BetsList(props: { user: User }) { return metrics.payout > 0 }) - const [settled, unsettled] = _.partition( + const [settled, unsettled] = partition( contracts, (c) => c.isResolved || contractsMetrics[c.id].invested === 0 ) - const currentInvested = _.sumBy( + const currentInvested = sumBy( unsettled, (c) => contractsMetrics[c.id].invested ) - const currentBetsValue = _.sumBy( + const currentBetsValue = sumBy( unsettled, (c) => contractsMetrics[c.id].payout ) - const currentNetInvestment = _.sumBy( + const currentNetInvestment = sumBy( unsettled, (c) => contractsMetrics[c.id].netPayout ) @@ -340,10 +348,10 @@ export function MyBetsSummary(props: { const excludeSalesAndAntes = bets.filter( (b) => !b.isAnte && !b.isSold && !b.sale ) - const yesWinnings = _.sumBy(excludeSalesAndAntes, (bet) => + const yesWinnings = sumBy(excludeSalesAndAntes, (bet) => calculatePayout(contract, bet, 'YES') ) - const noWinnings = _.sumBy(excludeSalesAndAntes, (bet) => + const noWinnings = sumBy(excludeSalesAndAntes, (bet) => calculatePayout(contract, bet, 'NO') ) const { invested, profitPercent, payout, profit } = getContractBetMetrics( @@ -421,21 +429,19 @@ export function ContractBetsTable(props: { const bets = props.bets.filter((b) => !b.isAnte) - const [sales, buys] = _.partition(bets, (bet) => bet.sale) + const [sales, buys] = partition(bets, (bet) => bet.sale) - const salesDict = _.fromPairs( + const salesDict = Object.fromEntries( sales.map((sale) => [sale.sale?.betId ?? '', sale]) ) - const [redemptions, normalBets] = _.partition( + const [redemptions, normalBets] = partition( contract.mechanism === 'cpmm-1' ? bets : buys, (b) => b.isRedemption ) - const amountRedeemed = Math.floor( - -0.5 * _.sumBy(redemptions, (b) => b.shares) - ) + const amountRedeemed = Math.floor(-0.5 * sumBy(redemptions, (b) => b.shares)) - const amountLoaned = _.sumBy( + const amountLoaned = sumBy( bets.filter((bet) => !bet.isSold && !bet.sale), (bet) => bet.loanAmount ?? 0 ) @@ -570,10 +576,7 @@ function BetRow(props: { bet: Bet; contract: Contract; saleBet?: Bet }) { ) } -const warmUpSellBet = _.throttle( - () => sellBet({}).catch(() => {}), - 5000 /* ms */ -) +const warmUpSellBet = throttle(() => sellBet({}).catch(() => {}), 5000 /* ms */) function SellButton(props: { contract: Contract; bet: Bet }) { useEffect(() => { diff --git a/web/components/bucket-input.tsx b/web/components/bucket-input.tsx index a64ef035..86456bff 100644 --- a/web/components/bucket-input.tsx +++ b/web/components/bucket-input.tsx @@ -1,4 +1,3 @@ -import _ from 'lodash' import { useState } from 'react' import { NumericContract } from 'common/contract' diff --git a/web/components/charity/charity-card.tsx b/web/components/charity/charity-card.tsx index cf87aff1..05f42ddf 100644 --- a/web/components/charity/charity-card.tsx +++ b/web/components/charity/charity-card.tsx @@ -1,5 +1,5 @@ import { StarIcon } from '@heroicons/react/solid' -import _ from 'lodash' +import { sumBy } from 'lodash' import Link from 'next/link' import Image from 'next/image' import { Charity } from 'common/charity' @@ -11,7 +11,7 @@ export function CharityCard(props: { charity: Charity }) { const { name, slug, photo, preview, id, tags } = props.charity const txns = useCharityTxns(id) - const raised = _.sumBy(txns, (txn) => txn.amount) + const raised = sumBy(txns, (txn) => txn.amount) return ( diff --git a/web/components/contract/contract-info-dialog.tsx b/web/components/contract/contract-info-dialog.tsx index 73858c6f..f15eed2e 100644 --- a/web/components/contract/contract-info-dialog.tsx +++ b/web/components/contract/contract-info-dialog.tsx @@ -1,7 +1,7 @@ import { DotsHorizontalIcon } from '@heroicons/react/outline' import clsx from 'clsx' import dayjs from 'dayjs' -import _ from 'lodash' +import { uniqBy, sum } from 'lodash' import { useState } from 'react' import { Bet } from 'common/bet' @@ -26,7 +26,7 @@ export function ContractInfoDialog(props: { contract: Contract; bets: Bet[] }) { const formatTime = (dt: number) => dayjs(dt).format('MMM DD, YYYY hh:mm a z') const { createdTime, closeTime, resolutionTime } = contract - const tradersCount = _.uniqBy(bets, 'userId').length + const tradersCount = uniqBy(bets, 'userId').length return ( <> @@ -108,7 +108,7 @@ export function ContractInfoDialog(props: { contract: Contract; bets: Bet[] }) { {contract.mechanism === 'dpm-2' && ( Pool - {formatMoney(_.sum(Object.values(contract.pool)))} + {formatMoney(sum(Object.values(contract.pool)))} )} diff --git a/web/components/contract/numeric-graph.tsx b/web/components/contract/numeric-graph.tsx index 5a5b2aff..4343e4c8 100644 --- a/web/components/contract/numeric-graph.tsx +++ b/web/components/contract/numeric-graph.tsx @@ -1,8 +1,8 @@ import { DatumValue } from '@nivo/core' import { ResponsiveLine } from '@nivo/line' import { NUMERIC_GRAPH_COLOR } from 'common/numeric-constants' -import _ from 'lodash' import { memo } from 'react' +import { range } from 'lodash' import { getDpmOutcomeProbabilities } from '../../../common/calculate-dpm' import { NumericContract } from '../../../common/contract' import { useWindowSize } from '../../hooks/use-window-size' @@ -16,10 +16,10 @@ export const NumericGraph = memo(function NumericGraph(props: { const bucketProbs = getDpmOutcomeProbabilities(totalShares) - const xs = _.range(bucketCount).map( + const xs = range(bucketCount).map( (i) => min + ((max - min) * i) / bucketCount ) - const probs = _.range(bucketCount).map((i) => bucketProbs[`${i}`] * 100) + const probs = range(bucketCount).map((i) => bucketProbs[`${i}`] * 100) const points = probs.map((prob, i) => ({ x: xs[i], y: prob })) const maxProb = Math.max(...probs) const data = [{ id: 'Probability', data: points, color: NUMERIC_GRAPH_COLOR }] diff --git a/web/components/feed-create.tsx b/web/components/feed-create.tsx index c7b2ec1c..98b56d69 100644 --- a/web/components/feed-create.tsx +++ b/web/components/feed-create.tsx @@ -1,4 +1,4 @@ -import _ from 'lodash' +import { sample } from 'lodash' import { SparklesIcon, XIcon } from '@heroicons/react/solid' import { Avatar } from './avatar' import { useEffect, useRef, useState } from 'react' @@ -86,9 +86,7 @@ export default function FeedCreate(props: { // Take care not to produce a different placeholder on the server and client const [defaultPlaceholder, setDefaultPlaceholder] = useState('') useEffect(() => { - setDefaultPlaceholder( - `e.g. ${_.sample(ENV_CONFIG.newQuestionPlaceholders)}` - ) + setDefaultPlaceholder(`e.g. ${sample(ENV_CONFIG.newQuestionPlaceholders)}`) }, []) const placeholder = props.placeholder ?? defaultPlaceholder diff --git a/web/components/feed/activity-items.ts b/web/components/feed/activity-items.ts index 1809739d..c958a892 100644 --- a/web/components/feed/activity-items.ts +++ b/web/components/feed/activity-items.ts @@ -1,4 +1,4 @@ -import _ from 'lodash' +import { last, findLastIndex, uniq, sortBy } from 'lodash' import { Answer } from 'common/answer' import { Bet } from 'common/bet' @@ -200,17 +200,17 @@ function getAnswerGroups( ) { const { sortByProb, abbreviated, reversed } = options - let outcomes = _.uniq(bets.map((bet) => bet.outcome)).filter( + let outcomes = uniq(bets.map((bet) => bet.outcome)).filter( (outcome) => getOutcomeProbability(contract, outcome) > 0.0001 ) if (abbreviated) { - const lastComment = _.last(comments) + const lastComment = last(comments) const lastCommentOutcome = bets.find( (bet) => bet.id === lastComment?.betId )?.outcome - const lastBetOutcome = _.last(bets)?.outcome + const lastBetOutcome = last(bets)?.outcome if (lastCommentOutcome && lastBetOutcome) { - outcomes = _.uniq([ + outcomes = uniq([ ...outcomes.filter( (outcome) => outcome !== lastCommentOutcome && outcome !== lastBetOutcome @@ -222,13 +222,13 @@ function getAnswerGroups( outcomes = outcomes.slice(-2) } if (sortByProb) { - outcomes = _.sortBy(outcomes, (outcome) => + outcomes = sortBy(outcomes, (outcome) => getOutcomeProbability(contract, outcome) ) } else { // Sort by recent bet. - outcomes = _.sortBy(outcomes, (outcome) => - _.findLastIndex(bets, (bet) => bet.outcome === outcome) + outcomes = sortBy(outcomes, (outcome) => + findLastIndex(bets, (bet) => bet.outcome === outcome) ) } @@ -274,10 +274,10 @@ function getAnswerAndCommentInputGroups( comments: Comment[], user: User | undefined | null ) { - let outcomes = _.uniq(bets.map((bet) => bet.outcome)).filter( + let outcomes = uniq(bets.map((bet) => bet.outcome)).filter( (outcome) => getOutcomeProbability(contract, outcome) > 0.0001 ) - outcomes = _.sortBy(outcomes, (outcome) => + outcomes = sortBy(outcomes, (outcome) => getOutcomeProbability(contract, outcome) ) const betsByCurrentUser = bets.filter((bet) => bet.userId === user?.id) @@ -343,7 +343,7 @@ function groupBetsAndComments( // iterate through the bets and comment activity items and add them to the items in order of comment creation time: const unorderedBetsAndComments = [...commentsWithoutBets, ...groupedBets] - let sortedBetsAndComments = _.sortBy(unorderedBetsAndComments, (item) => { + let sortedBetsAndComments = sortBy(unorderedBetsAndComments, (item) => { if (item.type === 'comment') { return item.comment.createdTime } else if (item.type === 'bet') { diff --git a/web/components/feed/feed-bets.tsx b/web/components/feed/feed-bets.tsx index 6e883677..d2fe7025 100644 --- a/web/components/feed/feed-bets.tsx +++ b/web/components/feed/feed-bets.tsx @@ -10,7 +10,7 @@ import { formatMoney } from 'common/util/format' import { OutcomeLabel } from 'web/components/outcome-label' import { RelativeTimestamp } from 'web/components/relative-timestamp' import React, { Fragment } from 'react' -import * as _ from 'lodash' +import { uniqBy, partition, sumBy, groupBy } from 'lodash' import { JoinSpans } from 'web/components/join-spans' export function FeedBet(props: { @@ -104,11 +104,11 @@ function BetGroupSpan(props: { }) { const { contract, bets, outcome } = props - const numberTraders = _.uniqBy(bets, (b) => b.userId).length + const numberTraders = uniqBy(bets, (b) => b.userId).length - const [buys, sells] = _.partition(bets, (bet) => bet.amount >= 0) - const buyTotal = _.sumBy(buys, (b) => b.amount) - const sellTotal = _.sumBy(sells, (b) => -b.amount) + const [buys, sells] = partition(bets, (bet) => bet.amount >= 0) + const buyTotal = sumBy(buys, (b) => b.amount) + const sellTotal = sumBy(sells, (b) => -b.amount) return ( @@ -139,7 +139,7 @@ export function FeedBetGroup(props: { }) { const { contract, bets, hideOutcome } = props - const betGroups = _.groupBy(bets, (bet) => bet.outcome) + const betGroups = groupBy(bets, (bet) => bet.outcome) const outcomes = Object.keys(betGroups) // Use the time of the last bet for the entire group diff --git a/web/components/feed/feed-comments.tsx b/web/components/feed/feed-comments.tsx index 1d443ccf..dbaccde5 100644 --- a/web/components/feed/feed-comments.tsx +++ b/web/components/feed/feed-comments.tsx @@ -3,6 +3,7 @@ import { Comment } from 'common/comment' import { User } from 'common/user' import { Contract } from 'common/contract' import React, { useEffect, useState } from 'react' +import { minBy, maxBy, groupBy, partition, sumBy } from 'lodash' import { useUser } from 'web/hooks/use-user' import { formatMoney } from 'common/util/format' import { useRouter } from 'next/router' @@ -16,7 +17,6 @@ import { contractPath } from 'web/lib/firebase/contracts' import { firebaseLogin } from 'web/lib/firebase/users' import { createComment, MAX_COMMENT_LENGTH } from 'web/lib/firebase/comments' import Textarea from 'react-expanding-textarea' -import * as _ from 'lodash' import { Linkify } from 'web/components/linkify' import { SiteLink } from 'web/components/site-link' import { BetStatusText } from 'web/components/feed/feed-bets' @@ -36,7 +36,7 @@ export function FeedCommentThread(props: { props const [showReply, setShowReply] = useState(false) const [replyToUsername, setReplyToUsername] = useState('') - const betsByUserId = _.groupBy(bets, (bet) => bet.userId) + const betsByUserId = groupBy(bets, (bet) => bet.userId) const user = useUser() const commentsList = comments.filter( (comment) => @@ -67,7 +67,7 @@ export function FeedCommentThread(props: { onReplyClick={scrollAndOpenReplyInput} probAtCreatedTime={ contract.outcomeType === 'BINARY' - ? _.minBy(bets, (bet) => { + ? minBy(bets, (bet) => { return bet.createdTime < comment.createdTime ? comment.createdTime - bet.createdTime : comment.createdTime @@ -494,8 +494,7 @@ function getBettorsLargestPositionBeforeTime( } } const majorityAnswer = - _.maxBy(Object.keys(answerCounts), (outcome) => answerCounts[outcome]) ?? - '' + maxBy(Object.keys(answerCounts), (outcome) => answerCounts[outcome]) ?? '' return { userPosition: answerCounts[majorityAnswer] || 0, outcome: majorityAnswer, @@ -505,12 +504,12 @@ function getBettorsLargestPositionBeforeTime( return emptyReturn } - const [yesBets, noBets] = _.partition( + const [yesBets, noBets] = partition( previousBets ?? [], (bet) => bet.outcome === 'YES' ) - yesShares = _.sumBy(yesBets, (bet) => bet.shares) - noShares = _.sumBy(noBets, (bet) => bet.shares) + yesShares = sumBy(yesBets, (bet) => bet.shares) + noShares = sumBy(noBets, (bet) => bet.shares) yesFloorShares = Math.floor(yesShares) noFloorShares = Math.floor(noShares) diff --git a/web/components/feed/feed-items.tsx b/web/components/feed/feed-items.tsx index 88fa8c89..94811cd5 100644 --- a/web/components/feed/feed-items.tsx +++ b/web/components/feed/feed-items.tsx @@ -1,6 +1,5 @@ // From https://tailwindui.com/components/application-ui/lists/feeds import React, { useState } from 'react' -import * as _ from 'lodash' import { BanIcon, CheckIcon, diff --git a/web/components/feed/find-active-contracts.ts b/web/components/feed/find-active-contracts.ts index 51965792..afffc5d0 100644 --- a/web/components/feed/find-active-contracts.ts +++ b/web/components/feed/find-active-contracts.ts @@ -1,4 +1,4 @@ -import _ from 'lodash' +import { groupBy, mapValues, maxBy, sortBy } from 'lodash' import { Contract } from 'web/lib/firebase/contracts' import { Comment } from 'web/lib/firebase/comments' import { Bet } from 'common/bet' @@ -44,10 +44,10 @@ export function findActiveContracts( } // Add contracts by last bet time. - const contractBets = _.groupBy(recentBets, (bet) => bet.contractId) - const contractMostRecentBet = _.mapValues( + const contractBets = groupBy(recentBets, (bet) => bet.contractId) + const contractMostRecentBet = mapValues( contractBets, - (bets) => _.maxBy(bets, (bet) => bet.createdTime) as Bet + (bets) => maxBy(bets, (bet) => bet.createdTime) as Bet ) for (const bet of Object.values(contractMostRecentBet)) { const contract = contractsById.get(bet.contractId) @@ -60,21 +60,21 @@ export function findActiveContracts( !contract.isResolved && (contract.closeTime ?? Infinity) > Date.now() ) - activeContracts = _.sortBy( + activeContracts = sortBy( activeContracts, (c) => -(idToActivityTime.get(c.id) ?? 0) ) - const contractComments = _.groupBy( + const contractComments = groupBy( recentComments, (comment) => comment.contractId ) - const contractMostRecentComment = _.mapValues( + const contractMostRecentComment = mapValues( contractComments, - (comments) => _.maxBy(comments, (c) => c.createdTime) as Comment + (comments) => maxBy(comments, (c) => c.createdTime) as Comment ) - const prioritizedContracts = _.sortBy(activeContracts, (c) => { + const prioritizedContracts = sortBy(activeContracts, (c) => { const seenTime = seenContracts[c.id] if (!seenTime) { return 0 diff --git a/web/components/folds/edit-fold-button.tsx b/web/components/folds/edit-fold-button.tsx index d6c296b2..4257c59a 100644 --- a/web/components/folds/edit-fold-button.tsx +++ b/web/components/folds/edit-fold-button.tsx @@ -1,5 +1,5 @@ import { useState } from 'react' -import _ from 'lodash' +import { isEqual } from 'lodash' import clsx from 'clsx' import { PencilIcon } from '@heroicons/react/outline' @@ -29,7 +29,7 @@ export function EditFoldButton(props: { fold: Fold; className?: string }) { const saveDisabled = name === fold.name && - _.isEqual(tags, fold.tags) && + isEqual(tags, fold.tags) && about === (fold.about ?? '') const onSubmit = async () => { diff --git a/web/components/nav/sidebar.tsx b/web/components/nav/sidebar.tsx index fa39b8cf..eab32b86 100644 --- a/web/components/nav/sidebar.tsx +++ b/web/components/nav/sidebar.tsx @@ -11,7 +11,7 @@ import { SparklesIcon, } from '@heroicons/react/outline' import clsx from 'clsx' -import _ from 'lodash' +import { sortBy } from 'lodash' import Link from 'next/link' import { useRouter } from 'next/router' import { useFollowedFolds } from 'web/hooks/use-fold' @@ -124,7 +124,7 @@ export default function Sidebar(props: { className?: string }) { const user = useUser() let folds = useFollowedFolds(user) || [] - folds = _.sortBy(folds, 'followCount').reverse() + folds = sortBy(folds, 'followCount').reverse() const deservesDailyFreeMarket = !useHasCreatedContractToday(user) const navigationOptions = diff --git a/web/components/number-input.tsx b/web/components/number-input.tsx index 0dbc712e..a5adb3f8 100644 --- a/web/components/number-input.tsx +++ b/web/components/number-input.tsx @@ -1,5 +1,4 @@ import clsx from 'clsx' -import _ from 'lodash' import { Col } from './layout/col' import { Spacer } from './layout/spacer' diff --git a/web/components/use-save-shares.ts b/web/components/use-save-shares.ts index 6a8b251e..977c8a97 100644 --- a/web/components/use-save-shares.ts +++ b/web/components/use-save-shares.ts @@ -1,7 +1,7 @@ import { Binary, CPMM, DPM, FullContract } from 'common/contract' import { Bet } from 'common/bet' import { useEffect, useState } from 'react' -import _ from 'lodash' +import { partition, sumBy } from 'lodash' export const useSaveShares = ( contract: FullContract, @@ -17,13 +17,13 @@ export const useSaveShares = ( | undefined >() - const [yesBets, noBets] = _.partition( + const [yesBets, noBets] = partition( userBets ?? [], (bet) => bet.outcome === 'YES' ) const [yesShares, noShares] = [ - _.sumBy(yesBets, (bet) => bet.shares), - _.sumBy(noBets, (bet) => bet.shares), + sumBy(yesBets, (bet) => bet.shares), + sumBy(noBets, (bet) => bet.shares), ] const yesFloorShares = Math.round(yesShares) === 0 ? 0 : Math.floor(yesShares) diff --git a/web/components/user-page.tsx b/web/components/user-page.tsx index 04774c29..e1398fea 100644 --- a/web/components/user-page.tsx +++ b/web/components/user-page.tsx @@ -19,10 +19,10 @@ import { Comment, getUsersComments } from 'web/lib/firebase/comments' import { Contract } from 'common/contract' import { getContractFromId, listContracts } from 'web/lib/firebase/contracts' import { LoadingIndicator } from './loading-indicator' -import _ from 'lodash' import { BetsList } from './bets-list' import { Bet } from 'common/bet' import { getUserBets } from 'web/lib/firebase/bets' +import { uniq } from 'lodash' export function UserLink(props: { name: string @@ -70,7 +70,7 @@ export function UserPage(props: { }, [user]) useEffect(() => { - const uniqueContractIds = _.uniq( + const uniqueContractIds = uniq( usersComments.map((comment) => comment.contractId) ) Promise.all( diff --git a/web/hooks/use-algo-feed.ts b/web/hooks/use-algo-feed.ts index 5f712fae..fde50e80 100644 --- a/web/hooks/use-algo-feed.ts +++ b/web/hooks/use-algo-feed.ts @@ -1,4 +1,3 @@ -import _, { Dictionary } from 'lodash' import { useState, useEffect } from 'react' import type { feed } from 'common/feed' import { useTimeSinceFirstRender } from './use-time-since-first-render' @@ -15,7 +14,7 @@ export const useAlgoFeed = ( category: string ) => { const [allFeed, setAllFeed] = useState() - const [categoryFeeds, setCategoryFeeds] = useState>() + const [categoryFeeds, setCategoryFeeds] = useState<{ [x: string]: feed }>() const getTime = useTimeSinceFirstRender() diff --git a/web/hooks/use-contracts.ts b/web/hooks/use-contracts.ts index ef0fd65f..9cb6ef03 100644 --- a/web/hooks/use-contracts.ts +++ b/web/hooks/use-contracts.ts @@ -1,4 +1,4 @@ -import _ from 'lodash' +import { isEqual } from 'lodash' import { useEffect, useRef, useState } from 'react' import { Contract, @@ -80,14 +80,14 @@ export const useUpdatedContracts = (contracts: Contract[] | undefined) => { useEffect(() => { if (contracts === undefined) return - contractDict.current = _.fromPairs(contracts.map((c) => [c.id, c])) + contractDict.current = Object.fromEntries(contracts.map((c) => [c.id, c])) const disposes = contracts.map((contract) => { const { id } = contract return listenForContract(id, (contract) => { const curr = contractDict.current[id] - if (!_.isEqual(curr, contract)) { + if (!isEqual(curr, contract)) { contractDict.current[id] = contract as Contract triggerUpdate((n) => n + 1) } diff --git a/web/hooks/use-fold.ts b/web/hooks/use-fold.ts index 4333c218..f6335ebf 100644 --- a/web/hooks/use-fold.ts +++ b/web/hooks/use-fold.ts @@ -1,4 +1,4 @@ -import _ from 'lodash' +import { isEqual, sortBy } from 'lodash' import { useEffect, useState } from 'react' import { Fold } from 'common/fold' import { User } from 'common/user' @@ -95,9 +95,9 @@ export const useFollowedFolds = (user: User | null | undefined) => { setFollowedFolds(JSON.parse(followedFoldJson)) // Exit early if ids and followedFoldIds have all the same elements. if ( - _.isEqual( - _.sortBy(ids), - _.sortBy(JSON.parse(followedFoldJson).map((f: Fold) => f.id)) + isEqual( + sortBy(ids), + sortBy(JSON.parse(followedFoldJson).map((f: Fold) => f.id)) ) ) { return diff --git a/web/hooks/use-measure-size.ts b/web/hooks/use-measure-size.ts index 5e50400b..6959e241 100644 --- a/web/hooks/use-measure-size.ts +++ b/web/hooks/use-measure-size.ts @@ -1,4 +1,4 @@ -import _ from 'lodash' +import { debounce } from 'lodash' import { RefObject, useMemo, useLayoutEffect, useRef, useState } from 'react' type elem_size = @@ -21,7 +21,7 @@ export function useListenElemSize( } return debounceMs - ? _.debounce(updateSize, debounceMs, { leading: false, trailing: true }) + ? debounce(updateSize, debounceMs, { leading: false, trailing: true }) : updateSize }, [callback, elemRef, debounceMs]) diff --git a/web/hooks/use-propz.ts b/web/hooks/use-propz.ts index de161e83..ee2b9669 100644 --- a/web/hooks/use-propz.ts +++ b/web/hooks/use-propz.ts @@ -1,4 +1,4 @@ -import _ from 'lodash' +import { isEmpty } from 'lodash' import { useRouter } from 'next/router' import { useState, useEffect } from 'react' import { IS_PRIVATE_MANIFOLD } from 'common/envs/constants' @@ -16,7 +16,7 @@ export function usePropz( getStaticPropz: (props: PropzProps) => Promise ) { // If props were successfully server-side generated, just use those - if (!_.isEmpty(initialProps)) { + if (!isEmpty(initialProps)) { return initialProps } diff --git a/web/hooks/use-seen-contracts.ts b/web/hooks/use-seen-contracts.ts index efe65743..501e7b0c 100644 --- a/web/hooks/use-seen-contracts.ts +++ b/web/hooks/use-seen-contracts.ts @@ -1,4 +1,4 @@ -import _ from 'lodash' +import { mapValues } from 'lodash' import { useEffect, useState } from 'react' import { Contract } from 'common/contract' import { trackView } from 'web/lib/firebase/tracking' @@ -38,7 +38,7 @@ export const useSaveSeenContract = ( const key = 'feed-seen-contracts' const getSeenContracts = () => { - return _.mapValues( + return mapValues( JSON.parse(localStorage.getItem(key) ?? '{}'), (time) => +time ) diff --git a/web/hooks/use-sort-and-query-params.tsx b/web/hooks/use-sort-and-query-params.tsx index 9b06a413..4430795e 100644 --- a/web/hooks/use-sort-and-query-params.tsx +++ b/web/hooks/use-sort-and-query-params.tsx @@ -1,4 +1,4 @@ -import _ from 'lodash' +import { defaults, debounce } from 'lodash' import { useRouter } from 'next/router' import { useEffect, useMemo, useState } from 'react' import { useSearchBox } from 'react-instantsearch-hooks-web' @@ -18,7 +18,7 @@ export function useInitialQueryAndSort(options?: { defaultSort: Sort shouldLoadFromStorage?: boolean }) { - const { defaultSort, shouldLoadFromStorage } = _.defaults(options, { + const { defaultSort, shouldLoadFromStorage } = defaults(options, { defaultSort: '24-hour-vol', shouldLoadFromStorage: true, }) @@ -79,7 +79,7 @@ export function useUpdateQueryAndSort(props: { // Debounce router query update. const pushQuery = useMemo( () => - _.debounce((query: string | undefined) => { + debounce((query: string | undefined) => { if (query) { router.query.q = query } else { diff --git a/web/hooks/use-state-check-equality.ts b/web/hooks/use-state-check-equality.ts index b7dd1370..d8dc88dd 100644 --- a/web/hooks/use-state-check-equality.ts +++ b/web/hooks/use-state-check-equality.ts @@ -1,4 +1,4 @@ -import _ from 'lodash' +import { isEqual } from 'lodash' import { useMemo, useRef, useState } from 'react' export const useStateCheckEquality = (initialState: T) => { @@ -10,7 +10,7 @@ export const useStateCheckEquality = (initialState: T) => { const checkSetState = useMemo( () => (newState: T) => { const state = stateRef.current - if (!_.isEqual(state, newState)) { + if (!isEqual(state, newState)) { setState(newState) } }, diff --git a/web/hooks/use-user-bets.ts b/web/hooks/use-user-bets.ts index a04a1545..b260a406 100644 --- a/web/hooks/use-user-bets.ts +++ b/web/hooks/use-user-bets.ts @@ -1,4 +1,4 @@ -import _ from 'lodash' +import { uniq } from 'lodash' import { useEffect, useState } from 'react' import { Bet, @@ -51,7 +51,7 @@ export const useUserBetContracts = ( return listenForUserBets( userId, (bets) => { - const contractIds = _.uniq(bets.map((bet) => bet.contractId)) + const contractIds = uniq(bets.map((bet) => bet.contractId)) setContractIds(contractIds) localStorage.setItem(key, JSON.stringify(contractIds)) }, diff --git a/web/hooks/use-user.ts b/web/hooks/use-user.ts index 0b96edc1..bd4caf6e 100644 --- a/web/hooks/use-user.ts +++ b/web/hooks/use-user.ts @@ -1,4 +1,3 @@ -import _ from 'lodash' import { useEffect, useState } from 'react' import { PrivateUser } from 'common/user' import { diff --git a/web/lib/firebase/bets.ts b/web/lib/firebase/bets.ts index f948ed4f..74f30957 100644 --- a/web/lib/firebase/bets.ts +++ b/web/lib/firebase/bets.ts @@ -5,7 +5,7 @@ import { where, orderBy, } from 'firebase/firestore' -import _ from 'lodash' +import { range } from 'lodash' import { db } from './init' import { Bet } from 'common/bet' @@ -138,7 +138,7 @@ export async function getDailyBets(startTime: number, numberOfDays: number) { const query = getBetsQuery(startTime, startTime + DAY_IN_MS * numberOfDays) const bets = await getValues(query) - const betsByDay = _.range(0, numberOfDays).map(() => [] as Bet[]) + const betsByDay = range(0, numberOfDays).map(() => [] as Bet[]) for (const bet of bets) { const dayIndex = Math.floor((bet.createdTime - startTime) / DAY_IN_MS) betsByDay[dayIndex].push(bet) diff --git a/web/lib/firebase/comments.ts b/web/lib/firebase/comments.ts index 4c967f37..e96c080b 100644 --- a/web/lib/firebase/comments.ts +++ b/web/lib/firebase/comments.ts @@ -7,7 +7,7 @@ import { setDoc, where, } from 'firebase/firestore' -import _ from 'lodash' +import { range } from 'lodash' import { getValues, listenForValues } from './utils' import { db } from './init' @@ -117,7 +117,7 @@ export async function getDailyComments( ) const comments = await getValues(query) - const commentsByDay = _.range(0, numberOfDays).map(() => [] as Comment[]) + const commentsByDay = range(0, numberOfDays).map(() => [] as Comment[]) for (const comment of comments) { const dayIndex = Math.floor((comment.createdTime - startTime) / DAY_IN_MS) commentsByDay[dayIndex].push(comment) diff --git a/web/lib/firebase/contracts.ts b/web/lib/firebase/contracts.ts index bd8bfc9b..4fcfb6da 100644 --- a/web/lib/firebase/contracts.ts +++ b/web/lib/firebase/contracts.ts @@ -13,7 +13,7 @@ import { updateDoc, limit, } from 'firebase/firestore' -import _ from 'lodash' +import { range, sortBy } from 'lodash' import { app } from './init' import { getValues, listenForValue, listenForValues } from './utils' @@ -225,7 +225,7 @@ export function listenForHotContracts( setHotContracts: (contracts: Contract[]) => void ) { return listenForValues(hotContractsQuery, (contracts) => { - const hotContracts = _.sortBy( + const hotContracts = sortBy( chooseRandomSubset(contracts, 4), (contract) => contract.volume24Hours ) @@ -235,7 +235,7 @@ export function listenForHotContracts( export async function getHotContracts() { const contracts = await getValues(hotContractsQuery) - return _.sortBy( + return sortBy( chooseRandomSubset(contracts, 10), (contract) => -1 * contract.volume24Hours ) @@ -245,7 +245,7 @@ export async function getContractsBySlugs(slugs: string[]) { const q = query(contractCollection, where('slug', 'in', slugs)) const snapshot = await getDocs(q) const contracts = snapshot.docs.map((doc) => doc.data() as Contract) - return _.sortBy(contracts, (contract) => -1 * contract.volume24Hours) + return sortBy(contracts, (contract) => -1 * contract.volume24Hours) } const topWeeklyQuery = query( @@ -269,7 +269,7 @@ const closingSoonQuery = query( export async function getClosingSoonContracts() { const contracts = await getValues(closingSoonQuery) - return _.sortBy( + return sortBy( chooseRandomSubset(contracts, 2), (contract) => contract.closeTime ) @@ -295,7 +295,7 @@ export async function getDailyContracts( ) const contracts = await getValues(query) - const contractsByDay = _.range(0, numberOfDays).map(() => [] as Contract[]) + const contractsByDay = range(0, numberOfDays).map(() => [] as Contract[]) for (const contract of contracts) { const dayIndex = Math.floor((contract.createdTime - startTime) / DAY_IN_MS) contractsByDay[dayIndex].push(contract) diff --git a/web/lib/firebase/folds.ts b/web/lib/firebase/folds.ts index 90268e98..249ce908 100644 --- a/web/lib/firebase/folds.ts +++ b/web/lib/firebase/folds.ts @@ -10,7 +10,7 @@ import { updateDoc, where, } from 'firebase/firestore' -import _ from 'lodash' +import { sortBy } from 'lodash' import { Fold } from 'common/fold' import { Contract, contractCollection } from './contracts' import { db } from './init' @@ -171,7 +171,7 @@ export async function getFoldsByTags(tags: string[]) { ) ) - return _.sortBy(folds, (fold) => -1 * fold.followCount) + return sortBy(folds, (fold) => -1 * fold.followCount) } export function listenForFoldsWithTags( @@ -187,7 +187,7 @@ export function listenForFoldsWithTags( ) return listenForValues(q, (folds) => { - const sorted = _.sortBy(folds, (fold) => -1 * fold.followCount) + const sorted = sortBy(folds, (fold) => -1 * fold.followCount) setFolds(sorted) }) } diff --git a/web/lib/firebase/users.ts b/web/lib/firebase/users.ts index e66dc177..4edcddb8 100644 --- a/web/lib/firebase/users.ts +++ b/web/lib/firebase/users.ts @@ -18,7 +18,7 @@ import { GoogleAuthProvider, signInWithPopup, } from 'firebase/auth' -import _ from 'lodash' +import { range, throttle, zip } from 'lodash' import { app } from './init' import { PrivateUser, User } from 'common/user' @@ -83,7 +83,7 @@ const CACHED_USER_KEY = 'CACHED_USER_KEY' // used to avoid weird race condition let createUserPromise: Promise | undefined = undefined -const warmUpCreateUser = _.throttle(createUser, 5000 /* ms */) +const warmUpCreateUser = throttle(createUser, 5000 /* ms */) export function listenForLogin(onUser: (user: User | null) => void) { const cachedUser = localStorage.getItem(CACHED_USER_KEY) @@ -210,7 +210,7 @@ export async function getDailyNewUsers( const query = getUsersQuery(startTime, startTime + DAY_MS * numberOfDays) const users = await getValues(query) - const usersByDay = _.range(0, numberOfDays).map(() => [] as User[]) + const usersByDay = range(0, numberOfDays).map(() => [] as User[]) for (const user of users) { const dayIndex = Math.floor((user.createdTime - startTime) / DAY_MS) usersByDay[dayIndex].push(user) @@ -235,5 +235,5 @@ export async function getCategoryFeeds(userId: string) { ) ) const feeds = feedData.map((data) => data?.feed ?? []) - return _.fromPairs(_.zip(CATEGORY_LIST, feeds) as [string, feed][]) + return Object.fromEntries(zip(CATEGORY_LIST, feeds) as [string, feed][]) } diff --git a/web/next.config.js b/web/next.config.js index a03f4b93..5a28778c 100644 --- a/web/next.config.js +++ b/web/next.config.js @@ -6,6 +6,11 @@ module.exports = { experimental: { externalDir: true, optimizeCss: true, + modularizeImports: { + lodash: { + transform: 'lodash/{{member}}', + }, + }, }, images: { domains: ['lh3.googleusercontent.com', 'i.imgur.com'], diff --git a/web/pages/[username]/[contractSlug].tsx b/web/pages/[username]/[contractSlug].tsx index 31295b62..a1738e39 100644 --- a/web/pages/[username]/[contractSlug].tsx +++ b/web/pages/[username]/[contractSlug].tsx @@ -1,6 +1,6 @@ import React, { useEffect, useMemo, useState } from 'react' import { ArrowLeftIcon } from '@heroicons/react/outline' -import _ from 'lodash' +import { keyBy, sortBy, groupBy, sumBy, mapValues } from 'lodash' import { useContractWithPreload } from 'web/hooks/use-contract' import { ContractOverview } from 'web/components/contract/contract-overview' @@ -250,13 +250,13 @@ function ContractLeaderboard(props: { contract: Contract; bets: Bet[] }) { const { userProfits, top5Ids } = useMemo(() => { // Create a map of userIds to total profits (including sales) - const betsByUser = _.groupBy(bets, 'userId') - const userProfits = _.mapValues(betsByUser, (bets) => - _.sumBy(bets, (bet) => resolvedPayout(contract, bet) - bet.amount) + const betsByUser = groupBy(bets, 'userId') + const userProfits = mapValues(betsByUser, (bets) => + sumBy(bets, (bet) => resolvedPayout(contract, bet) - bet.amount) ) // Find the 5 users with the most profits - const top5Ids = _.entries(userProfits) - .sort(([i1, p1], [i2, p2]) => p2 - p1) + const top5Ids = Object.entries(userProfits) + .sort(([_i1, p1], [_i2, p2]) => p2 - p1) .filter(([, p]) => p > 0) .slice(0, 5) .map(([id]) => id) @@ -267,7 +267,7 @@ function ContractLeaderboard(props: { contract: Contract; bets: Bet[] }) { console.log('foo') if (top5Ids.length > 0) { listUsers(top5Ids).then((users) => { - const sortedUsers = _.sortBy(users, (user) => -userProfits[user.id]) + const sortedUsers = sortBy(users, (user) => -userProfits[user.id]) setUsers(sortedUsers) }) } @@ -294,8 +294,8 @@ function ContractTopTrades(props: { comments: Comment[] }) { const { contract, bets, comments } = props - const commentsById = _.keyBy(comments, 'id') - const betsById = _.keyBy(bets, 'id') + const commentsById = keyBy(comments, 'id') + const betsById = keyBy(bets, 'id') // If 'id2' is the sale of 'id1', both are logged with (id2 - id1) of profit // Otherwise, we record the profit at resolution time @@ -312,11 +312,11 @@ function ContractTopTrades(props: { } // 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) // And also the commentId of the comment with the highest profit - const topCommentId = _.sortBy( + const topCommentId = sortBy( comments, (c) => c.betId && -profitById[c.betId] )[0]?.id diff --git a/web/pages/admin.tsx b/web/pages/admin.tsx index cea82e8b..db230e74 100644 --- a/web/pages/admin.tsx +++ b/web/pages/admin.tsx @@ -6,7 +6,7 @@ import dayjs from 'dayjs' import { usePrivateUsers, useUsers } from 'web/hooks/use-users' import Custom404 from './404' import { useContracts } from 'web/hooks/use-contracts' -import _ from 'lodash' +import { mapKeys } from 'lodash' import { useAdmin } from 'web/hooks/use-admin' import { contractPath } from 'web/lib/firebase/contracts' @@ -23,7 +23,7 @@ function UsersTable() { let privateUsers = usePrivateUsers() // Map private users by user id - const privateUsersById = _.mapKeys(privateUsers, 'id') + const privateUsersById = mapKeys(privateUsers, 'id') console.log('private users by id', privateUsersById) // For each user, set their email from the PrivateUser @@ -64,7 +64,7 @@ function UsersTable() { id: 'username', name: 'Username', formatter: (cell) => - html(`@${cell}`), }, @@ -134,7 +134,7 @@ function ContractsTable() { id: 'creatorUsername', name: 'Username', formatter: (cell) => - html(`@${cell}`), diff --git a/web/pages/analytics.tsx b/web/pages/analytics.tsx index dc5cca73..2e14b50a 100644 --- a/web/pages/analytics.tsx +++ b/web/pages/analytics.tsx @@ -1,5 +1,5 @@ import dayjs from 'dayjs' -import _ from 'lodash' +import { zip, uniq, sumBy } from 'lodash' import { IS_PRIVATE_MANIFOLD } from 'common/envs/constants' import { DailyCountChart, @@ -40,12 +40,12 @@ export async function getStaticPropz() { ) const dailyCommentCounts = dailyComments.map((comments) => comments.length) - const dailyUserIds = _.zip(dailyContracts, dailyBets, dailyComments).map( + const dailyUserIds = zip(dailyContracts, dailyBets, dailyComments).map( ([contracts, bets, comments]) => { const creatorIds = (contracts ?? []).map((c) => c.creatorId) const betUserIds = (bets ?? []).map((bet) => bet.userId) const commentUserIds = (comments ?? []).map((comment) => comment.userId) - return _.uniq([...creatorIds, ...betUserIds, ...commentUserIds]) + return uniq([...creatorIds, ...betUserIds, ...commentUserIds]) } ) @@ -87,7 +87,7 @@ export async function getStaticPropz() { for (let j = lastWeek.start; j <= lastWeek.end; j++) { dailyUserIds[j].forEach((userId) => activeLastWeek.add(userId)) } - const retainedCount = _.sumBy(Array.from(activeTwoWeeksAgo), (userId) => + const retainedCount = sumBy(Array.from(activeTwoWeeksAgo), (userId) => activeLastWeek.has(userId) ? 1 : 0 ) const retainedFrac = retainedCount / activeTwoWeeksAgo.size @@ -112,7 +112,7 @@ export async function getStaticPropz() { for (let j = lastMonth.start; j <= lastMonth.end; j++) { dailyUserIds[j].forEach((userId) => activeLastMonth.add(userId)) } - const retainedCount = _.sumBy(Array.from(activeTwoMonthsAgo), (userId) => + const retainedCount = sumBy(Array.from(activeTwoMonthsAgo), (userId) => activeLastMonth.has(userId) ? 1 : 0 ) const retainedFrac = retainedCount / activeTwoMonthsAgo.size diff --git a/web/pages/charity/[charitySlug].tsx b/web/pages/charity/[charitySlug].tsx index 2f9f88db..45933b15 100644 --- a/web/pages/charity/[charitySlug].tsx +++ b/web/pages/charity/[charitySlug].tsx @@ -1,4 +1,4 @@ -import _ from 'lodash' +import { sortBy, sumBy, uniqBy } from 'lodash' import clsx from 'clsx' import { useEffect, useRef, useState } from 'react' import { Col } from 'web/components/layout/col' @@ -41,13 +41,13 @@ function CharityPage(props: { charity: Charity }) { const user = useUser() const txns = useCharityTxns(charity.id) - const newToOld = _.sortBy(txns, (txn) => -txn.createdTime) - const totalRaised = _.sumBy(txns, (txn) => txn.amount) - const fromYou = _.sumBy( + const newToOld = sortBy(txns, (txn) => -txn.createdTime) + const totalRaised = sumBy(txns, (txn) => txn.amount) + const fromYou = sumBy( txns.filter((txn) => txn.fromId === user?.id), (txn) => txn.amount ) - const numSupporters = _.uniqBy(txns, (txn) => txn.fromId).length + const numSupporters = uniqBy(txns, (txn) => txn.fromId).length const { width, height } = useWindowSize() const [showConfetti, setShowConfetti] = useState(false) diff --git a/web/pages/charity/index.tsx b/web/pages/charity/index.tsx index 0e4b5991..f33ceb33 100644 --- a/web/pages/charity/index.tsx +++ b/web/pages/charity/index.tsx @@ -1,4 +1,4 @@ -import _ from 'lodash' +import { mapValues, groupBy, sumBy, sum, sortBy, debounce } from 'lodash' import { useState, useMemo } from 'react' import { charities, Charity as CharityType } from 'common/charity' import { CharityCard } from 'web/components/charity/charity-card' @@ -12,11 +12,11 @@ import { formatMoney } from 'common/util/format' export async function getStaticProps() { const txns = await getAllCharityTxns() - const totals = _.mapValues(_.groupBy(txns, 'toId'), (txns) => - _.sumBy(txns, (txn) => txn.amount) + const totals = mapValues(groupBy(txns, 'toId'), (txns) => + sumBy(txns, (txn) => txn.amount) ) - const totalRaised = _.sum(Object.values(totals)) - const sortedCharities = _.sortBy(charities, [ + const totalRaised = sum(Object.values(totals)) + const sortedCharities = sortBy(charities, [ (charity) => (charity.tags?.includes('Featured') ? 0 : 1), (charity) => -totals[charity.id], ]) @@ -37,7 +37,7 @@ export default function Charity(props: { const { totalRaised, charities } = props const [query, setQuery] = useState('') - const debouncedQuery = _.debounce(setQuery, 50) + const debouncedQuery = debounce(setQuery, 50) const filterCharities = useMemo( () => diff --git a/web/pages/fold/[...slugs]/index.tsx b/web/pages/fold/[...slugs]/index.tsx index cb462fb8..ad286d60 100644 --- a/web/pages/fold/[...slugs]/index.tsx +++ b/web/pages/fold/[...slugs]/index.tsx @@ -1,4 +1,4 @@ -import _ from 'lodash' +import { flatten, take, partition, sortBy } from 'lodash' import { Fold } from 'common/fold' import { Comment } from 'common/comment' @@ -47,8 +47,8 @@ export async function getStaticPropz(props: { params: { slugs: string[] } }) { contracts.map((contract) => listAllBets(contract.id)) ) - let activeContracts = findActiveContracts(contracts, [], _.flatten(bets), {}) - const [resolved, unresolved] = _.partition( + let activeContracts = findActiveContracts(contracts, [], flatten(bets), {}) + const [resolved, unresolved] = partition( activeContracts, ({ isResolved }) => isResolved ) @@ -80,8 +80,8 @@ export async function getStaticPropz(props: { params: { slugs: string[] } }) { } async function toTopUsers(userScores: { [userId: string]: number }) { - const topUserPairs = _.take( - _.sortBy(Object.entries(userScores), ([_, score]) => -1 * score), + const topUserPairs = take( + sortBy(Object.entries(userScores), ([_, score]) => -1 * score), 10 ).filter(([_, score]) => score >= 0.5) @@ -134,7 +134,7 @@ export default function FoldPage(props: { const isCurator = user && fold && user.id === fold.curatorId const taggedContracts = useTaggedContracts(fold?.tags) ?? props.contracts - const contractsMap = _.fromPairs( + const contractsMap = Object.fromEntries( taggedContracts.map((contract) => [contract.id, contract]) ) diff --git a/web/pages/folds.tsx b/web/pages/folds.tsx index 4ffbd32b..749da213 100644 --- a/web/pages/folds.tsx +++ b/web/pages/folds.tsx @@ -1,4 +1,4 @@ -import _ from 'lodash' +import { sortBy, debounce } from 'lodash' import Link from 'next/link' import { useEffect, useState } from 'react' import { Fold } from 'common/fold' @@ -21,7 +21,7 @@ export async function getStaticProps() { const curators = await Promise.all( folds.map((fold) => getUser(fold.curatorId)) ) - const curatorsDict = _.fromPairs( + const curatorsDict = Object.fromEntries( curators.map((curator) => [curator.id, curator]) ) @@ -37,7 +37,7 @@ export async function getStaticProps() { export default function Folds(props: { folds: Fold[] - curatorsDict: _.Dictionary + curatorsDict: { [k: string]: User } }) { const [curatorsDict, setCuratorsDict] = useState(props.curatorsDict) @@ -51,7 +51,7 @@ export default function Folds(props: { if (newFolds.length > 0) { Promise.all(newFolds.map(({ curatorId }) => getUser(curatorId))).then( (newUsers) => { - const newUsersDict = _.fromPairs( + const newUsersDict = Object.fromEntries( newUsers.map((user) => [user.id, user]) ) setCuratorsDict({ ...curatorsDict, ...newUsersDict }) @@ -68,7 +68,7 @@ export default function Folds(props: { } // List followed folds first, then folds with the highest follower count - const matches = _.sortBy(folds, [ + const matches = sortBy(folds, [ (fold) => !followedFoldIds.includes(fold.id), (fold) => -1 * fold.followCount, ]).filter( @@ -79,7 +79,7 @@ export default function Folds(props: { check(f.lowercaseTags.map((tag) => `#${tag}`).join(' ')) ) // Not strictly necessary, but makes the "hold delete" experience less laggy - const debouncedQuery = _.debounce(setQuery, 50) + const debouncedQuery = debounce(setQuery, 50) return ( diff --git a/web/pages/server-sitemap.xml.tsx b/web/pages/server-sitemap.xml.tsx index d837b26e..246bb9ee 100644 --- a/web/pages/server-sitemap.xml.tsx +++ b/web/pages/server-sitemap.xml.tsx @@ -1,4 +1,4 @@ -import _ from 'lodash' +import { sortBy } from 'lodash' import { GetServerSideProps } from 'next' import { getServerSideSitemap, ISitemapField } from 'next-sitemap' @@ -10,7 +10,7 @@ export const getServerSideProps: GetServerSideProps = async (ctx) => { const response = await fetch(`https://${DOMAIN}/api/v0/markets`) const liteMarkets = (await response.json()) as LiteMarket[] - const sortedMarkets = _.sortBy(liteMarkets, (m) => -m.volume24Hours) + const sortedMarkets = sortBy(liteMarkets, (m) => -m.volume24Hours) const fields = sortedMarkets.map((market) => ({ // See https://www.sitemaps.org/protocol.html diff --git a/yarn.lock b/yarn.lock index a798f1ed..2da70cae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2394,6 +2394,13 @@ eslint-plugin-jsx-a11y@^6.5.1: language-tags "^1.0.5" minimatch "^3.0.4" +eslint-plugin-lodash@^7.4.0: + version "7.4.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-lodash/-/eslint-plugin-lodash-7.4.0.tgz#14a761547f126c92ff56789662a20a44f8bb6290" + integrity sha512-Tl83UwVXqe1OVeBRKUeWcfg6/pCW1GTRObbdnbEJgYwjxp5Q92MEWQaH9+dmzbRt6kvYU1Mp893E79nJiCSM8A== + dependencies: + lodash "^4.17.21" + eslint-plugin-react-hooks@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.5.0.tgz#5f762dfedf8b2cf431c689f533c9d3fa5dcf25ad"