Skip to content

Commit

Permalink
added DAO search to chain home page
Browse files Browse the repository at this point in the history
  • Loading branch information
NoahSaso committed Nov 23, 2024
1 parent cfbff89 commit f9c519d
Show file tree
Hide file tree
Showing 9 changed files with 375 additions and 133 deletions.
5 changes: 5 additions & 0 deletions packages/i18n/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -1005,6 +1005,7 @@
"appsProposalDescription": "Add a title and description, and then review the actions generated by the app. Once it all looks good, publish it to the DAO for voting.",
"areTheyMissingQuestion": "Are they missing?",
"areYouSureConfirmButton": "Are you sure you want to do that? Press again to confirm.",
"atLeastNumDaosFound": "at least {{count, number}} DAOs found",
"authzAuthorizationDescription": "Grant / revoke authorizations that allow other accounts to perform actions on behalf of the DAO.",
"authzExecDescription": "Perform an action on behalf of another account.",
"authzWarning": "USE WITH CAUTION! Granting an Authorization allows another account to perform actions on behalf of your account.",
Expand Down Expand Up @@ -1279,6 +1280,8 @@
"nothingHereYet": "Nothing here yet.",
"notificationsInInbox_one": "There is {{count}} notification in your inbox.",
"notificationsInInbox_other": "There are {{count}} notification in your inbox.",
"numDaosFound_one": "{{count, number}} DAO found",
"numDaosFound_other": "{{count, number}} DAOs found",
"numNftsSelected_one": "{{count}} NFT selected",
"numNftsSelected_other": "{{count}} NFTs selected",
"numNftsSelected_zero": "No NFT selected",
Expand Down Expand Up @@ -1414,6 +1417,7 @@
"searchDraftPlaceholder": "Find a draft...",
"searchForAccount": "Search for an account...",
"searchForChain": "Search for a chain...",
"searchForDao": "Search for a DAO...",
"searchForToken": "Search for a token...",
"searchForWidget": "Search for a widget...",
"searchMessages": "Search messages",
Expand Down Expand Up @@ -1704,6 +1708,7 @@
"addToProfile": "Add to Profile",
"advancedConfiguration": "Advanced configuration",
"all": "All",
"allDaos": "All DAOs",
"allNfts": "All NFTs",
"approval": "Approval",
"approved": "Approved",
Expand Down
53 changes: 45 additions & 8 deletions packages/state/indexer/search.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import MeiliSearch from 'meilisearch'
import MeiliSearch, { SearchResponse } from 'meilisearch'

import { IndexerDumpState, WithChainId } from '@dao-dao/types'
import { ProposalResponse as MultipleChoiceProposalResponse } from '@dao-dao/types/contracts/DaoProposalMultiple'
Expand Down Expand Up @@ -37,17 +37,49 @@ export type DaoSearchResult = {
}

export type SearchDaosOptions = WithChainId<{
/**
* Search query that compares against all fields.
*/
query: string
/**
* Offset to start search results from.
*/
offset?: number
/**
* Limit number of search results.
*/
limit?: number
/**
* Number of hits per page.
*/
hitsPerPage?: number
/**
* Page number.
*/
page?: number
/**
* Exclude DAOs by their core address.
*/
exclude?: string[]
/**
* Sort search results by the given fields.
*
* Defaults to most recently created at the top, and deprioritize those with
* no proposals: `['value.createdAtEpoch:desc', 'value.proposalCount:desc']`.
*/
sort?: string[]
}>

export const searchDaos = async ({
chainId,
query,
offset,
limit,
hitsPerPage,
page,
exclude = [],
}: SearchDaosOptions): Promise<DaoSearchResult[]> => {
sort = ['value.createdAtEpoch:desc', 'value.proposalCount:desc'],
}: SearchDaosOptions): Promise<SearchResponse<DaoSearchResult>> => {
const client = await loadMeilisearchClient()

if (!chainIsIndexed(chainId)) {
Expand All @@ -59,7 +91,10 @@ export const searchDaos = async ({
exclude.push(...DAOS_HIDDEN_FROM_SEARCH)

const results = await index.search<Omit<DaoSearchResult, 'chainId'>>(query, {
offset,
limit,
hitsPerPage,
page,
filter: [
// Exclude inactive DAOs.
`NOT value.config.name IN ["${INACTIVE_DAO_NAMES.join('", "')}"]`,
Expand All @@ -70,14 +105,16 @@ export const searchDaos = async ({
]
.map((filter) => `(${filter})`)
.join(' AND '),
// Most recent at the top.
sort: ['block.height:desc', 'value.proposalCount:desc'],
sort,
})

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

export type DaoProposalSearchResult = {
Expand Down
10 changes: 10 additions & 0 deletions packages/state/query/queries/indexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import { IndexerFormulaType } from '@dao-dao/types'
import {
QueryIndexerOptions,
QuerySnapperOptions,
SearchDaosOptions,
queryIndexer,
queryIndexerUpStatus,
querySnapper,
searchDaos,
} from '../../indexer'

/**
Expand Down Expand Up @@ -146,4 +148,12 @@ export const indexerQueries = {
// props can't serialize undefined.
queryFn: async () => (await querySnapper<T>(options)) ?? null,
}),
/**
* Search DAOs.
*/
searchDaos: (options: SearchDaosOptions) =>
queryOptions({
queryKey: ['indexer', 'searchDaos', options],
queryFn: () => searchDaos(options),
}),
}
11 changes: 0 additions & 11 deletions packages/state/recoil/selectors/indexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,14 @@ import {

import {
DaoProposalSearchResult,
DaoSearchResult,
QueryIndexerOptions,
QuerySnapperOptions,
SearchDaoProposalsOptions,
SearchDaosOptions,
loadMeilisearchClient,
queryIndexer,
queryIndexerUpStatus,
querySnapper,
searchDaoProposals,
searchDaos,
} from '../../indexer'
import {
refreshIndexerUpStatusAtom,
Expand Down Expand Up @@ -211,14 +208,6 @@ export const querySnapperSelector = selectorFamily<any, QuerySnapperParams>({
get: (options) => async () => await querySnapper(options),
})

export const searchDaosSelector = selectorFamily<
DaoSearchResult[],
SearchDaosOptions
>({
key: 'searchDaos',
get: (options) => async () => await searchDaos(options),
})

/**
* Get recent DAO proposals for a chain.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { useQueries } from '@tanstack/react-query'
import { useTranslation } from 'react-i18next'
import { useRecoilValue, waitForAllSettled } from 'recoil'
import { useRecoilValue } from 'recoil'

import { navigatingToHrefAtom, searchDaosSelector } from '@dao-dao/state/recoil'
import { useCachedLoadable, useDaoNavHelpers } from '@dao-dao/stateless'
import { indexerQueries } from '@dao-dao/state/query'
import { navigatingToHrefAtom } from '@dao-dao/state/recoil'
import { useDaoNavHelpers } from '@dao-dao/stateless'
import {
CommandModalContextSection,
CommandModalContextSectionItem,
Expand All @@ -16,6 +18,7 @@ import {
getFallbackImage,
getImageUrlForChainId,
getSupportedChains,
makeCombineQueryResultsIntoLoadingData,
mustGetConfiguredChainConfig,
parseContractVersion,
} from '@dao-dao/utils'
Expand Down Expand Up @@ -45,36 +48,32 @@ export const useFollowingAndFilteredDaosSections = ({
const followingDaosLoading = useLoadingFollowingDaos()
const { getDaoPath } = useDaoNavHelpers()

const queryResults = useCachedLoadable(
options.filter
? waitForAllSettled(
chains.map(({ chain }) =>
searchDaosSelector({
chainId: chain.chainId,
query: options.filter,
limit,
// Exclude following DAOs from search since they show in a
// separate section.
exclude: followingDaosLoading.loading
? undefined
: followingDaosLoading.data
.filter(({ chainId }) => chainId === chain.chainId)
.map(({ coreAddress }) => coreAddress),
})
)
)
: undefined
)
const searchQueries = useQueries({
queries: chains.map(({ chain }) =>
indexerQueries.searchDaos({
chainId: chain.chainId,
query: options.filter,
limit,
// Exclude following DAOs from search since they show in a
// separate section.
exclude: followingDaosLoading.loading
? undefined
: followingDaosLoading.data
.filter(({ chainId }) => chainId === chain.chainId)
.map(({ coreAddress }) => coreAddress),
})
),
combine: makeCombineQueryResultsIntoLoadingData({
transform: (results) => results.flatMap((r) => r.hits),
}),
})

const navigatingToHref = useRecoilValue(navigatingToHrefAtom)

// Use query results if filter is present.
const daos = [
...(options.filter
? (queryResults.state !== 'hasValue'
? []
: queryResults.contents.flatMap((l) => l.valueMaybe() || [])
)
? (searchQueries.loading ? [] : searchQueries.data)
.filter(({ value }) => !!value?.config)
.map(
({
Expand Down Expand Up @@ -142,8 +141,7 @@ export const useFollowingAndFilteredDaosSections = ({

// When filter present, use search results. Otherwise use featured DAOs.
const daosLoading = options.filter
? queryResults.state === 'loading' ||
(queryResults.state === 'hasValue' && queryResults.updating)
? searchQueries.loading || searchQueries.updating
: featuredDaosLoading.loading || !!featuredDaosLoading.updating

const followingSection: CommandModalContextSection<CommandModalDaoInfo> = {
Expand Down
47 changes: 19 additions & 28 deletions packages/stateful/components/AddressInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,11 @@ import Fuse from 'fuse.js'
import { useMemo } from 'react'
import { FieldValues, Path, useFormContext } from 'react-hook-form'
import { useTranslation } from 'react-i18next'
import { waitForNone } from 'recoil'
import { useDeepCompareMemoize } from 'use-deep-compare-effect'

import { profileQueries } from '@dao-dao/state/query'
import { searchDaosSelector } from '@dao-dao/state/recoil'
import { indexerQueries, profileQueries } from '@dao-dao/state/query'
import {
AddressInput as StatelessAddressInput,
useCachedLoadable,
useChain,
} from '@dao-dao/stateless'
import { AddressInputProps, Entity, EntityType } from '@dao-dao/types'
Expand Down Expand Up @@ -58,26 +55,29 @@ export const AddressInput = <

// Search DAOs on current chains and all polytone-connected chains so we can
// find polytone accounts.
const searchDaosLoadable = useCachedLoadable(
hasFormValue && props.type !== 'wallet'
? waitForNone(
[
const searchedDaos = useQueries({
queries:
hasFormValue && props.type !== 'wallet'
? [
// Current chain.
currentChain.chainId,
// Chains that have polytone connections with the current chain.
...POLYTONE_CONFIG_PER_CHAIN.filter(([, destChains]) =>
Object.keys(destChains).includes(currentChain.chainId)
).map(([chainId]) => chainId),
].map((chainId) =>
searchDaosSelector({
indexerQueries.searchDaos({
chainId,
query: formValue,
limit: 5,
})
)
)
: undefined
)
: [],
combine: makeCombineQueryResultsIntoLoadingData({
firstLoad: 'none',
transform: (results) => results.flatMap((r) => r.hits),
}),
})

const queryClient = useQueryClient()
const loadingEntities = useQueries({
Expand All @@ -90,16 +90,12 @@ export const AddressInput = <
})
)
: []),
...(searchDaosLoadable.state === 'hasValue'
? searchDaosLoadable.contents.flatMap((loadable) =>
loadable.state === 'hasValue'
? loadable.contents.map(({ chainId, id: address }) =>
entityQueries.info(queryClient, {
chainId,
address,
})
)
: []
...(!searchedDaos.loading
? searchedDaos.data.flatMap(({ chainId, id: address }) =>
entityQueries.info(queryClient, {
chainId,
address,
})
)
: []),
],
Expand Down Expand Up @@ -151,12 +147,7 @@ export const AddressInput = <
(!searchProfilesLoading.errored &&
searchProfilesLoading.updating))) ||
(props.type !== 'wallet' &&
(searchDaosLoadable.state === 'loading' ||
(searchDaosLoadable.state === 'hasValue' &&
(searchDaosLoadable.updating ||
searchDaosLoadable.contents.some(
(loadable) => loadable.state === 'loading'
))))) ||
(searchedDaos.loading || searchedDaos.updating)) ||
loadingEntities.loading ||
!!loadingEntities.updating,
}
Expand Down
Loading

0 comments on commit f9c519d

Please sign in to comment.