diff --git a/js/app/packages/app/component/UnifiedListView.tsx b/js/app/packages/app/component/UnifiedListView.tsx index f80336134..a43d39fc2 100644 --- a/js/app/packages/app/component/UnifiedListView.tsx +++ b/js/app/packages/app/component/UnifiedListView.tsx @@ -799,6 +799,13 @@ export function UnifiedListView(props: UnifiedListViewProps) { const emailActive = useEmailLinksStatus(); + const validSearchTerms = createMemo(() => { + return debouncedSearchForService().length >= 3; + }); + const isSearchActive = createMemo(() => { + return validSearchTerms(); + }); + const dssQueryParams = createMemo( (): GetItemsSoupParams => ({ limit: props.defaultDisplayOptions?.limit ?? 100, @@ -830,6 +837,7 @@ export function UnifiedListView(props: UnifiedListViewProps) { email_filters: { recipients: emailActive() && + !isSearchActive() && view().viewType !== 'project' && (entityTypeFilter().includes('email') || entityTypeFilter().length === 0) @@ -874,19 +882,6 @@ export function UnifiedListView(props: UnifiedListViewProps) { }) ); - const validSearchTerms = createMemo(() => { - return debouncedSearchForService().length >= 3; - }); - const validSearchFilters = createMemo(() => { - const senders = unifiedSearchFilters()?.email?.senders; - if (senders && senders.length > 0) return true; - return false; - }); - - const isSearchActive = createMemo(() => { - return validSearchTerms() || validSearchFilters(); - }); - const disableSearchService = createMemo(() => { return !isSearchActive(); }); @@ -971,10 +966,13 @@ export function UnifiedListView(props: UnifiedListViewProps) { const channelsQuery = createChannelsQuery({ disabled: disableChannelsQuery, }); - const dssInfiniteQuery = createDssInfiniteQuery(dssQueryParams, { - disabled: disableDssInfiniteQuery, - requestBody: dssQueryRequestBody, - }); + const dssInfiniteQuery = createDssInfiniteQuery( + dssQueryParams, + dssQueryRequestBody, + { + disabled: disableDssInfiniteQuery, + } + ); const searchNameContentInfiniteQuery = createUnifiedSearchInfiniteQuery( searchUnifiedNameContentQueryParams, { disabled: disableSearchService } @@ -988,6 +986,16 @@ export function UnifiedListView(props: UnifiedListViewProps) { }; }; + // We want to be to be able to search over locally cached emails without actually + // fetching more data when we have a invalid search term (i.e. one or two chars). + // If we're using search service for a valid term, we can safely fetch more data + // from dss for fuzzy name search since we won't be searching over emails (too big). + const disableFetchMore = createMemo(() => { + const searchAllEmails = + (dssQueryRequestBody().email_filters?.recipients ?? []).length === 0; + return searchText().length > 0 && searchAllEmails; + }); + const { UnifiedListComponent, entities, isLoading } = createUnifiedInfiniteList< WithNotification | EntityData> @@ -1011,6 +1019,7 @@ export function UnifiedListView(props: UnifiedListViewProps) { entitySort, searchFilter: nameFuzzySearchFilter, isSearchActive, + disableFetchMore, }); createEffect(() => { diff --git a/js/app/packages/core/email-link/index.ts b/js/app/packages/core/email-link/index.ts index 480329272..8be37dff5 100644 --- a/js/app/packages/core/email-link/index.ts +++ b/js/app/packages/core/email-link/index.ts @@ -10,7 +10,7 @@ import { emailClient } from '@service-email/client'; import { updateUserInfo } from '@service-gql/client'; import { useQuery } from '@tanstack/solid-query'; import { err, okAsync, ResultAsync } from 'neverthrow'; -import { createSignal } from 'solid-js'; +import { createMemo, createSignal } from 'solid-js'; import { queryClient } from '../../macro-entity/src/queries/client'; export const [emailRefetchInterval, setEmailRefetchInterval] = createSignal< @@ -38,12 +38,12 @@ export function useEmailLinksQuery() { export function useEmailLinksStatus() { const links = useEmailLinksQuery(); - return () => { + return createMemo(() => { if (!links.data || links.error) { return false; } - return links.data?.length > 0; - }; + return links.data.length > 0; + }); } function invalidateEmailLinks() { diff --git a/js/app/packages/macro-entity/src/components/Provider.tsx b/js/app/packages/macro-entity/src/components/Provider.tsx index 86d76acd2..032d36cac 100644 --- a/js/app/packages/macro-entity/src/components/Provider.tsx +++ b/js/app/packages/macro-entity/src/components/Provider.tsx @@ -49,7 +49,7 @@ export function Provider(props: ParentProps) { staleTime: 1000 * 10, // 10 seconds }); queryClient.setQueryDefaults(queryKeys.all.dss, { - staleTime: 1000 * 5, // 5 seconds + staleTime: 1000 * 60, // 1 minute }); queryClient.setQueryDefaults(queryKeys.all.email, { staleTime: 1000, // 1 second diff --git a/js/app/packages/macro-entity/src/components/UnifiedInfiniteList.tsx b/js/app/packages/macro-entity/src/components/UnifiedInfiniteList.tsx index 3b7683311..f0629d719 100644 --- a/js/app/packages/macro-entity/src/components/UnifiedInfiniteList.tsx +++ b/js/app/packages/macro-entity/src/components/UnifiedInfiniteList.tsx @@ -206,6 +206,7 @@ interface UnifiedInfiniteListContext { entitySort?: Accessor>; searchFilter?: Accessor | undefined>; isSearchActive?: Accessor; + disableFetchMore?: Accessor; } export function createUnifiedInfiniteList({ @@ -217,6 +218,7 @@ export function createUnifiedInfiniteList({ entitySort, searchFilter, isSearchActive, + disableFetchMore, }: UnifiedInfiniteListContext) { const [sortedEntitiesStore, setSortedEntitiesStore] = createStore([]); const allEntities = createMemo(() => { @@ -311,7 +313,7 @@ export function createUnifiedInfiniteList({ const searching = isSearchActive?.(); if (searching) { - // NOTE: the default sort will be channels, then local fuzzy name, then serach service + // NOTE: the default sort will be channels, then local fuzzy name, then search service // avoiding doing an extra sort as a speed optimization return entities.toSorted(sortEntitiesForSearch); } @@ -388,7 +390,7 @@ export function createUnifiedInfiniteList({ let isFetchingMore = false; const fetchMoreData = async () => { - if (isFetchingMore) return; + if (disableFetchMore?.() || isFetchingMore) return; isFetchingMore = true; const results = entityInfiniteQueries.map((query) => { diff --git a/js/app/packages/macro-entity/src/queries/dss.ts b/js/app/packages/macro-entity/src/queries/dss.ts index 073de2db8..f23aa501c 100644 --- a/js/app/packages/macro-entity/src/queries/dss.ts +++ b/js/app/packages/macro-entity/src/queries/dss.ts @@ -74,10 +74,12 @@ const fetchPaginatedDocumentsPost = async ({ apiToken, params, requestBody, + signal, }: { apiToken?: string; requestBody?: PostSoupRequest; params?: PostItemsSoupParams; + signal?: AbortSignal; }) => { if (!apiToken) throw new Error('No API token provided'); const Authorization = `Bearer ${apiToken}`; @@ -93,6 +95,7 @@ const fetchPaginatedDocumentsPost = async ({ headers: { Authorization, 'Content-Type': 'application/json' }, method: 'POST', body: requestBody ? JSON.stringify(requestBody) : undefined, + signal, }); if (!response.ok) throw new Error('Failed to fetch documents', { cause: response }); @@ -102,21 +105,20 @@ const fetchPaginatedDocumentsPost = async ({ }; export function createDssInfiniteQuery( - _params?: Accessor, + initialParams?: Accessor, + getRequestBody?: Accessor, options?: { disabled?: Accessor; - requestBody?: Accessor; } ) { const params = () => { - const argParams = _params?.(); + const argParams = initialParams?.(); let limit = 100; let sort_method; let emailView; - const requestBody = options?.requestBody; - if (requestBody) { - const body = requestBody(); + if (getRequestBody) { + const body = getRequestBody(); if (body?.limit) { limit = body.limit; } @@ -140,7 +142,7 @@ export function createDssInfiniteQuery( const instructionsIdQuery = useInstructionsMdIdQuery(); return useInfiniteQuery(() => { - const requestBody = options?.requestBody?.(); + const requestBody = getRequestBody?.(); // Include all filters in query key so query refetches when any filter changes const documentFilters = requestBody?.document_filters; const projectFilters = requestBody?.project_filters; @@ -166,12 +168,13 @@ export function createDssInfiniteQuery( return { queryKey, - queryHash: hashKey(queryKey), - queryFn: ({ pageParam }) => { + queryKeyHashFn: hashKey, + queryFn: ({ pageParam, signal }) => { return fetchPaginatedDocumentsPost({ apiToken: authQuery.data, - requestBody: requestBody, + requestBody, params: { cursor: pageParam.cursor }, + signal, }); }, initialPageParam: params(),