Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add stats to homepage <3 #1780

Merged
merged 10 commits into from
Jun 1, 2024
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ on:
- development

env:
NEXT_PUBLIC_INDEXER_URL: https://indexer.daodao.zone
NEXT_PUBLIC_SITE_URL: NEXT_PUBLIC_SITE_URL
NEXT_PUBLIC_SEARCH_URL: NEXT_PUBLIC_SEARCH_URL
NEXT_PUBLIC_SEARCH_API_KEY: NEXT_PUBLIC_SEARCH_API_KEY
Expand Down
133 changes: 125 additions & 8 deletions apps/dapp/pages/[[...tab]].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,134 @@
import { GetStaticPaths, GetStaticProps } from 'next'

import { serverSideTranslations } from '@dao-dao/i18n/serverSideTranslations'
import { Home } from '@dao-dao/stateful'
import { AccountTabId } from '@dao-dao/types'
import { getSupportedChains } from '@dao-dao/utils'
import { querySnapper } from '@dao-dao/state'
import { Home, StatefulHomeProps } from '@dao-dao/stateful'
import { AccountTabId, ChainId, DaoDaoIndexerChainStats } from '@dao-dao/types'
import {
MAINNET,
getDaoInfoForChainId,
getSupportedChains,
processError,
} from '@dao-dao/utils'

export default Home

export const getStaticProps: GetStaticProps = async ({ locale }) => ({
props: {
...(await serverSideTranslations(locale, ['translation'])),
},
})
export const getStaticProps: GetStaticProps<StatefulHomeProps> = async ({
locale,
params,
}) => {
const tabPath =
params?.tab && Array.isArray(params?.tab) ? params.tab[0] : undefined

// If defined, try to find matching chain. If found, show chain-only page.
const selectedChain = tabPath
? getSupportedChains().find(({ name }) => name === tabPath)
: undefined
const chainId = selectedChain?.chainId

const chainGovDaos = chainId
? selectedChain.noGov
? undefined
: [getDaoInfoForChainId(chainId, [])]
: // Get chain x/gov DAOs if not on a chain-specific home.
[
// Start with Cosmos Hub.
MAINNET ? ChainId.CosmosHubMainnet : ChainId.CosmosHubTestnet,
// Add DAO DAO-supported chains.
...getSupportedChains().flatMap(({ chainId, noGov }) =>
noGov ? [] : chainId
),
// Add some other common chains.
...(MAINNET
? [
'akashnet-2',
'secret-4',
'regen-1',
'injective-1',
'celestia',
'dydx-mainnet-1',
'archway-1',
'coreum-mainnet-1',
]
: []),
].map((chainId) => getDaoInfoForChainId(chainId, []))

// Get all or chain-specific stats and TVL.
const [tvl, allStats, monthStats, weekStats] = await Promise.all([
querySnapper<number>({
query: chainId ? 'daodao-chain-tvl' : 'daodao-all-tvl',
parameters: chainId ? { chainId } : undefined,
}),
querySnapper<DaoDaoIndexerChainStats>({
query: chainId ? 'daodao-chain-stats' : 'daodao-all-stats',
parameters: chainId ? { chainId } : undefined,
}),
querySnapper<DaoDaoIndexerChainStats>({
query: chainId ? 'daodao-chain-stats' : 'daodao-all-stats',
parameters: {
...(chainId ? { chainId } : undefined),
daysAgo: 30,
},
}),
querySnapper<DaoDaoIndexerChainStats>({
query: chainId ? 'daodao-chain-stats' : 'daodao-all-stats',
parameters: {
...(chainId ? { chainId } : undefined),
daysAgo: 7,
},
}),
])

const validTvl = typeof tvl === 'number'
const validAllStats = !!allStats
const validMonthStats = !!monthStats
const validWeekStats = !!weekStats
if (!validTvl || !validAllStats || !validMonthStats || !validWeekStats) {
processError('Failed to fetch TVL/stats for home page', {
forceCapture: true,
tags: {
chainId,
},
extra: {
tvl,
allStats,
monthStats,
weekStats,
},
})
throw new Error(
`Failed to fetch stats due to invalid: ${[
!validTvl && 'TVL',
!validAllStats && 'all stats',
!validMonthStats && 'month stats',
!validWeekStats && 'week stats',
]
.filter(Boolean)
.join(', ')}.`
)
}

return {
props: {
...(await serverSideTranslations(locale, ['translation'])),
// Chain-specific home page.
...(chainId && { chainId }),
// All or chain-specific stats.
stats: {
all: allStats,
month: monthStats,
week: weekStats,
// If chain is 1, it will not be shown.
chains: chainId ? 1 : getSupportedChains().length,
tvl,
},
// Chain x/gov DAOs.
...(chainGovDaos && { chainGovDaos }),
},
// Revalidate every day.
revalidate: 24 * 60 * 60,
}
}

export const getStaticPaths: GetStaticPaths = () => ({
paths: [
Expand Down
2 changes: 1 addition & 1 deletion apps/dapp/pages/dao/[address]/[[...slug]].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const DaoHomePage: NextPage<DaoPageWrapperProps> = ({
...props
}) => (
<DaoPageWrapper {...props}>
{props.serializedInfo?.coreVersion === ContractVersion.Gov ? (
{props.info?.coreVersion === ContractVersion.Gov ? (
<ChainGovernanceDappHome />
) : (
<DaoDappHome />
Expand Down
2 changes: 1 addition & 1 deletion apps/dapp/pages/dao/[address]/proposals/[proposalId].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const ProposalPage: NextPage<DaoProposalProps> = ({
...props
}) => (
<DaoPageWrapper {...props}>
{props.serializedInfo?.coreVersion === ContractVersion.Gov ? (
{props.info?.coreVersion === ContractVersion.Gov ? (
<GovProposal {...props} />
) : (
<DaoProposal {...props} />
Expand Down
4 changes: 2 additions & 2 deletions apps/dapp/pages/dao/[address]/proposals/create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ const ProposalCreatePage: NextPage<DaoPageWrapperProps> = ({
...props
}) => (
<DaoPageWrapper {...props}>
{!props.serializedInfo ? (
{!props.info ? (
<PageLoader />
) : props.serializedInfo.coreVersion === ContractVersion.Gov ? (
) : props.info.coreVersion === ContractVersion.Gov ? (
<CreateGovProposal />
) : (
<CreateDaoProposal />
Expand Down
12 changes: 10 additions & 2 deletions packages/i18n/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@
"earlyExecute": "Early execute",
"enable": "Enable",
"execute": "Execute",
"findAnotherChain": "Find another chain",
"findAnotherDao": "Find another DAO",
"follow": "Follow",
"forceExecute": "Force execute",
"forcePublish": "Force publish",
Expand Down Expand Up @@ -910,6 +912,7 @@
"cannotRemoveNoneOption": "You cannot remove this option. It will be added to the end of the list.",
"catchingUp": "Catching up...",
"chainTokensNotShowingUpPrompt": "Are your tokens not showing up? Add more chains.",
"chainsDeployedTooltip": "The number of chains with native DAO DAO deployments.",
"chooseProfilePictureSubtitle": "Use an NFT you own to represent your identity, or upload an image.",
"chooseTokenToPayWith": "Choose token to pay with",
"claimToReceiveUnstaked": "Claim them to receive your unstaked tokens.",
Expand Down Expand Up @@ -997,7 +1000,7 @@
"errored": "Errored",
"establishedTooltip": "When the DAO was created.",
"estimatedStargazeUsdValueTooltip": "USD value is estimated using price data from Stargaze. This is not fully reflective of realizable spending power due to liquidity limitations.",
"estimatedTreasuryUsdValueTooltip": "The USD value of treasuries is estimated by summing the value of all tokens held in the treasury that are listed on CoinGecko, Osmosis, Stargaze, White Whale, and Astroport. This is not fully reflective of realizable spending power due to liquidity limitations.",
"estimatedTreasuryUsdValueTooltip": "The USD value of DAO treasuries is estimated by summing the value of all tokens held in the treasury that are listed on CoinGecko, Osmosis, Astroport, Stargaze, and White Whale. This is not fully reflective of realizable spending power due to liquidity limitations.",
"estimatedUsdValueTooltip": "USD value is estimated using price data from CoinGecko, Osmosis, White Whale, and Astroport. This is not fully reflective of realizable spending power due to liquidity limitations.",
"executeSmartContractActionDescription": "Execute a message on a smart contract.",
"failing": "Failing",
Expand Down Expand Up @@ -1231,6 +1234,7 @@
"remainingBalanceVesting": "Remaining balance vesting",
"removeCw20FromTreasuryActionDescription": "Stop displaying the DAO's balance of a CW20 token in the treasury view.",
"removeCw721FromTreasuryActionDescription": "Stop displaying the NFTs owned by the DAO from a CW721 NFT collection in the treasury view.",
"required": "Required",
"retroactiveCompensationDescription": "After each contribution cycle, contributors are assigned points based on their contributions. Payment is split based on the points they receive.",
"reviewActionImportData": "Review the following actions to make sure they look right. If they do, click the Import button at the bottom to add them to the proposal.",
"reviewYourProposal": "Review your proposal...",
Expand Down Expand Up @@ -1528,7 +1532,7 @@
"canceled": "Canceled",
"casting": "Casting",
"chain": "Chain",
"chainGovernance": "Chain Governance",
"chainGovernance": "Chain governance",
"chainModule": {
"gov": "Governance Module"
},
Expand Down Expand Up @@ -1582,6 +1586,7 @@
"dao": "DAO",
"daoAdminExec": "DAO Admin Execute",
"daoCreationProcess": "DAO Creation Process",
"daoDaoCommunityStatistics": "DAO DAO Community Statistics",
"daoNotFound": "DAO Not Found",
"daoRatings": "DAO Ratings",
"daos": "DAOs",
Expand Down Expand Up @@ -1758,6 +1763,7 @@
"quorum": "Quorum",
"ratioOfVotes": "Ratio of votes",
"rawData": "Raw data",
"recentProposals": "Recent proposals",
"recipient": "Recipient",
"recognizeSubDao": "Recognize {{name}} as a SubDAO",
"redelegate": "Redelegate",
Expand Down Expand Up @@ -1838,10 +1844,12 @@
"treasuryHistory": "Treasury History",
"treasuryValue": "Treasury Value",
"turnout": "Turnout",
"tvl": "TVL",
"twitter": "Twitter",
"type": "Type",
"undelegate": "Undelegate",
"underDevelopment": "Under development",
"uniqueVoters": "Unique voters",
"unjailValidator": "Unjail validator",
"unregisteredSlashAmount": "Unregistered slash amount",
"unstake": "Unstake",
Expand Down
56 changes: 54 additions & 2 deletions packages/state/indexer/search.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import MeiliSearch from 'meilisearch'

import { IndexerDumpState, WithChainId } from '@dao-dao/types'
import { ProposalResponse as MultipleChoiceProposalResponse } from '@dao-dao/types/contracts/DaoProposalMultiple'
import { ProposalResponse as SingleChoiceProposalResponse } from '@dao-dao/types/contracts/DaoProposalSingle.v2'
import { ProposalStatus } from '@dao-dao/types/protobuf/codegen/cosmos/gov/v1/gov'
import {
CommonError,
Expand All @@ -27,8 +29,8 @@ export type DaoSearchResult = {
chainId: string
id: string
block: {
height: string
timeUnixMs: string
height: number
timeUnixMs: number
}
value: IndexerDumpState
}
Expand Down Expand Up @@ -77,6 +79,56 @@ export const searchDaos = async ({
}))
}

export type DaoProposalSearchResult = {
chainId: string
id: string
block: {
height: number
timeUnixMs: number
}
value: SingleChoiceProposalResponse | MultipleChoiceProposalResponse
}

export type SearchDaoProposalsOptions = WithChainId<{
limit: number
}>

export const getRecentDaoProposals = async ({
chainId,
limit,
}: SearchDaoProposalsOptions): Promise<DaoProposalSearchResult[]> => {
const client = await loadMeilisearchClient()

if (!chainIsIndexed(chainId)) {
throw new Error(CommonError.NoIndexerForChain)
}

const index = client.index(chainId + '_proposals')

const results = await index.search<Omit<DaoProposalSearchResult, 'chainId'>>(
null,
{
limit,
filter: [
// Exclude hidden DAOs.
'value.hideFromSearch NOT EXISTS OR value.hideFromSearch != true',
// Ensure DAO and proposal ID exist.
'value.dao EXISTS',
'value.daoProposalId EXISTS',
]
.map((filter) => `(${filter})`)
.join(' AND '),
// Most recently created first.
sort: ['value.proposal.start_height:desc'],
}
)

return results.hits.map((hit) => ({
chainId,
...hit,
}))
}

export type GovProposalSearchResult = {
chainId: string
id: string
Expand Down
Loading
Loading