Skip to content

Commit ead1aaf

Browse files
committed
improvement(org): keep the member inventory out of the lighter pages' server graph
1 parent 5216949 commit ead1aaf

10 files changed

Lines changed: 90 additions & 83 deletions

File tree

‎apps/sim/app/o/[organizationId]/home/page.test.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ const mocks = vi.hoisted(() => ({ context: vi.fn(), prefetchHome: vi.fn() }))
1313
vi.mock('next/navigation', () => nextNavigationMock)
1414
vi.mock('@/lib/organizations/surface', () => ({ getOrganizationSurfaceContext: mocks.context }))
1515
vi.mock('@/lib/mothership/chat/lifecycle', () => mothershipChatLifecycleMock)
16-
vi.mock('@/platform/lib/data/prefetch', () => ({
16+
vi.mock('@/platform/lib/data/prefetch', () => ({}))
17+
vi.mock('@/platform/lib/data/prefetch-member-inventory', () => ({
1718
prefetchHome: mocks.prefetchHome,
18-
prefetchSearch: mocks.prefetchHome,
1919
}))
2020
vi.mock('@/platform/app/(shell)/home/_components/home-dashboard/home-dashboard-skeleton', () => ({
2121
HomeDashboardSkeleton: ({ requestMode }: { requestMode: string }) => (

‎apps/sim/app/o/[organizationId]/home/page.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { Hydrated } from '@/app/o/[organizationId]/_redesign/hydrated'
77
import { getOrganizationHomeRedirect } from '@/app/o/[organizationId]/home/home-redirect'
88
import { HomeConversation } from '@/platform/app/(shell)/home/_components/home-conversation/home-conversation'
99
import { HomeDashboardSkeleton } from '@/platform/app/(shell)/home/_components/home-dashboard/home-dashboard-skeleton'
10-
import { prefetchHome } from '@/platform/lib/data/prefetch'
10+
import { prefetchHome } from '@/platform/lib/data/prefetch-member-inventory'
1111

1212
export const metadata: Metadata = {
1313
title: 'Home',

‎apps/sim/app/o/[organizationId]/integrations/hydrated-integrations.tsx‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,13 @@ import { Hydrated } from '@/app/o/[organizationId]/_redesign/hydrated'
55
import { OrganizationIntegrations } from '@/app/o/[organizationId]/integrations/integrations'
66
import { IntegrationsHost } from '@/app/o/[organizationId]/integrations/integrations-host'
77
import { IntegrationsRouteFallback } from '@/platform/app/(shell)/integrations/_components/integrations-page/integrations-route'
8-
import { prefetchIntegrations } from '@/platform/lib/data/prefetch'
8+
import { prefetchIntegrations } from '@/platform/lib/data/prefetch-member-inventory'
99

1010
interface HydratedIntegrationsProps extends ComponentProps<typeof OrganizationIntegrations> {
1111
surface: OrganizationSurfaceContext
1212
principal: SessionPrincipal
1313
}
1414

15-
/** The route, painted from the server's reads. */
1615
export function HydratedIntegrations({ surface, principal, ...route }: HydratedIntegrationsProps) {
1716
return (
1817
<Suspense

‎apps/sim/lib/organizations/surface.ts‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ export interface OrganizationSurfaceContext extends OrganizationChatAccess {
5858
deployment: DeploymentShape
5959
}
6060

61-
/** Whether the viewer's permission group lets them connect accounts for Search. */
6261
export function canConnectSearchIntegrations(config: PermissionGroupConfig | null): boolean {
6362
return (
6463
!capabilityDeniedBy('integrations.manage', config) &&

‎apps/sim/platform/app/(shell)/search/_components/search-surface/search-history.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { InlineNotice } from '@/platform/design-system/components/status/status'
1212

1313
const HISTORY_RECENT_LIMIT = 5
1414

15-
/** The viewer's private recent activity, and which list is showing. A refused read shows nothing. */
15+
/** The viewer's private recent activity, and which list is showing. A failed read hides stale rows. */
1616
function useSearchLandingHistory(organizationId: string, userId: string) {
1717
const history = useSearchHistory(organizationId, userId)
1818
const clearHistory = useClearSearchHistory(organizationId, userId)

‎apps/sim/platform/lib/data/chat-list.ts‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import {
1313
useSetMothershipChatPinned,
1414
} from '@/hooks/queries/mothership-chats'
1515

16-
/** Assistant chats live under Search, agent chats at the chat route. */
1716
export function organizationChatHref(
1817
chat: { id: string; mode?: string },
1918
routes: ReturnType<typeof organizationRoutes>

‎apps/sim/platform/lib/data/integrations.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export function useMemberIntegrations(organizationId: string) {
5151
}
5252
}
5353

54-
/** Connecting an account and letting it go, wherever accounts are offered. A failure reports itself. */
54+
/** A failure reports itself. */
5555
export function useIntegrationActions(organizationId: string) {
5656
const connect = useConnectOrganizationAccount()
5757
const reconnect = useReconnectPersonalOrganizationAccount()
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import 'server-only'
2+
import type { SessionPrincipal } from '@sim/auth/principal'
3+
import type { DehydratedState } from '@tanstack/react-query'
4+
import { listSearchIntegrationsContract } from '@/lib/api/contracts/knowledge/search-integrations'
5+
import { getOrganizationAccountsContract } from '@/lib/api/contracts/organization-accounts'
6+
import { getOrganizationAccountsSettings } from '@/lib/credential-groups/application/organization-accounts'
7+
import { canReadMemberIntegrations } from '@/lib/credential-groups/member-integrations'
8+
import { listSearchIntegrations } from '@/lib/knowledge/application/search-integrations'
9+
import {
10+
ORGANIZATION_ACCOUNTS_STALE_TIME,
11+
organizationAccountsKeys,
12+
} from '@/hooks/queries/utils/organization-accounts-keys'
13+
import {
14+
SEARCH_INTEGRATIONS_STALE_TIME,
15+
searchIntegrationKeys,
16+
} from '@/hooks/queries/utils/search-integration-keys'
17+
import { createSeeds } from '@/platform/lib/data/prefetch'
18+
19+
type PrefetchSurface = Parameters<typeof canReadMemberIntegrations>[0] & {
20+
organization: { id: string }
21+
}
22+
23+
function seedMemberInventory(
24+
seeds: ReturnType<typeof createSeeds>,
25+
surface: PrefetchSurface,
26+
principal: SessionPrincipal
27+
) {
28+
if (!canReadMemberIntegrations(surface)) return []
29+
const organizationId = surface.organization.id
30+
const input = { organizationId }
31+
return [
32+
seeds.seed(
33+
organizationAccountsKeys.detail(organizationId),
34+
ORGANIZATION_ACCOUNTS_STALE_TIME,
35+
async () =>
36+
getOrganizationAccountsContract.response.schema.parse(
37+
await getOrganizationAccountsSettings.execute({ principal, input })
38+
)
39+
),
40+
seeds.seed(
41+
searchIntegrationKeys.list(organizationId),
42+
SEARCH_INTEGRATIONS_STALE_TIME,
43+
async () =>
44+
listSearchIntegrationsContract.response.schema.parse({
45+
success: true,
46+
data: await listSearchIntegrations.execute({ principal, input }),
47+
}).data
48+
),
49+
]
50+
}
51+
52+
/**
53+
* What decides each Home widget's state, so a viewer with no connection paints their prompt at once.
54+
* Provider reads stay on the client: they can take seconds.
55+
*/
56+
export function prefetchHome(
57+
surface: PrefetchSurface,
58+
principal: SessionPrincipal
59+
): Promise<DehydratedState> {
60+
const seeds = createSeeds(surface.organization.id, principal)
61+
return seeds.settle([
62+
...seedMemberInventory(seeds, surface, principal),
63+
...(surface.searchAccess.memberScoped ? [seeds.searchHistory()] : []),
64+
])
65+
}
66+
67+
export function prefetchIntegrations(
68+
surface: PrefetchSurface,
69+
principal: SessionPrincipal
70+
): Promise<DehydratedState> {
71+
const seeds = createSeeds(surface.organization.id, principal)
72+
return seeds.settle([...seedMemberInventory(seeds, surface, principal), seeds.secretSource()])
73+
}

‎apps/sim/platform/lib/data/prefetch.test.ts‎

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,8 @@ vi.mock('@/lib/sim-links/application/use-cases', () => ({
3939
listSimLinks: { execute: mocks.links },
4040
}))
4141

42-
import {
43-
prefetchHome,
44-
prefetchIntegrations,
45-
prefetchSearch,
46-
prefetchSimLinks,
47-
} from '@/platform/lib/data/prefetch'
42+
import { prefetchSearch, prefetchSimLinks } from '@/platform/lib/data/prefetch'
43+
import { prefetchHome, prefetchIntegrations } from '@/platform/lib/data/prefetch-member-inventory'
4844

4945
const principal = createSessionPrincipal({ userId: 'viewer' })
5046
const surface = (memberScoped: boolean, canConnect = true) => ({
@@ -124,6 +120,10 @@ describe('seeding under the keys the hooks read', () => {
124120
expect(seeded(await prefetchSimLinks('org-1', principal, view))).toEqual([
125121
[...simLinksQueryOptions('org-1', term, view.sort, view.dir).queryKey],
126122
])
123+
expect(mocks.links).toHaveBeenCalledWith({
124+
principal,
125+
input: { organizationId: 'org-1', search: term || undefined, sort: view.sort, dir: view.dir },
126+
})
127127
})
128128
})
129129

@@ -134,10 +134,6 @@ describe('Links first paint', () => {
134134
sort: 'name',
135135
dir: 'asc',
136136
})
137-
expect(mocks.links).toHaveBeenCalledWith({
138-
principal,
139-
input: { organizationId: 'org-1', search: undefined, sort: 'name', dir: 'asc' },
140-
})
141137
const { normalizedName, ...wire } = LINK_RECORD
142138
expect(state.queries[0].state.data).toEqual({
143139
pages: [

‎apps/sim/platform/lib/data/prefetch.ts‎

Lines changed: 5 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,16 @@ import 'server-only'
22
import type { SessionPrincipal } from '@sim/auth/principal'
33
import { type DehydratedState, dehydrate } from '@tanstack/react-query'
44
import { listSearchHistoryContract } from '@/lib/api/contracts/knowledge/search-history'
5-
import { listSearchIntegrationsContract } from '@/lib/api/contracts/knowledge/search-integrations'
6-
import { getOrganizationAccountsContract } from '@/lib/api/contracts/organization-accounts'
75
import { getSecretSourceContract } from '@/lib/api/contracts/organization-secrets'
86
import { listSimLinksContract } from '@/lib/api/contracts/sim-links'
97
import { presentSimLinkPage } from '@/lib/api/server/sim-link-presenters'
108
import { withinDeadline } from '@/lib/core/utils/deadline'
11-
import { getOrganizationAccountsSettings } from '@/lib/credential-groups/application/organization-accounts'
12-
import { canReadMemberIntegrations } from '@/lib/credential-groups/member-integrations'
139
import { listSearchHistory } from '@/lib/knowledge/application/search-history'
14-
import { listSearchIntegrations } from '@/lib/knowledge/application/search-integrations'
1510
import { readOrganizationSecretSource } from '@/lib/organization-secrets/application/use-cases'
1611
import { listSimLinks } from '@/lib/sim-links/application/use-cases'
1712
import type { SimLinkSort } from '@/lib/sim-links/validation'
1813
import type { SortDirection } from '@/lib/url-state'
1914
import { makeQueryClient } from '@/app/_shell/providers/get-query-client'
20-
import {
21-
ORGANIZATION_ACCOUNTS_STALE_TIME,
22-
organizationAccountsKeys,
23-
} from '@/hooks/queries/utils/organization-accounts-keys'
2415
import {
2516
ORGANIZATION_SECRET_STALE_TIME,
2617
organizationSecretKeys,
@@ -29,10 +20,6 @@ import {
2920
SEARCH_HISTORY_STALE_TIME,
3021
searchHistoryKeys,
3122
} from '@/hooks/queries/utils/search-history-keys'
32-
import {
33-
SEARCH_INTEGRATIONS_STALE_TIME,
34-
searchIntegrationKeys,
35-
} from '@/hooks/queries/utils/search-integration-keys'
3623
import { SIM_LINKS_STALE_TIME, simLinkKeys } from '@/hooks/queries/utils/sim-link-keys'
3724

3825
/** A slow read gives up its first-paint seat rather than hold the document; the client fetches it. */
@@ -42,10 +29,6 @@ function withinBudget<T>(read: () => Promise<T>) {
4229
return () => withinDeadline(read, Date.now() + PREFETCH_TIMEOUT_MS)
4330
}
4431

45-
type PrefetchSurface = Parameters<typeof canReadMemberIntegrations>[0] & {
46-
organization: { id: string }
47-
}
48-
4932
interface SimLinksListView {
5033
search: string
5134
sort: SimLinkSort
@@ -56,35 +39,17 @@ interface SimLinksListView {
5639
* The cheap reads behind first paint. Each runs its route's use case and contract schema under its
5740
* hook's key, together rather than in turn. A failed or slow read stays out of hydration for the
5841
* client to retry, and a read the route would refuse this viewer fails the same way here.
42+
*
43+
* Keep these light, since each lands in its route's server graph: the member inventory, which reaches
44+
* the block and tool registries, is apart in `prefetch-member-inventory.ts`.
5945
*/
60-
function createSeeds(organizationId: string, principal: SessionPrincipal) {
46+
export function createSeeds(organizationId: string, principal: SessionPrincipal) {
6147
const queryClient = makeQueryClient()
6248
const input = { organizationId }
6349
const seed = <T>(queryKey: readonly unknown[], staleTime: number, read: () => Promise<T>) =>
6450
queryClient.prefetchQuery({ queryKey, staleTime, queryFn: withinBudget(read) })
6551
return {
66-
memberInventory: (surface: PrefetchSurface) =>
67-
canReadMemberIntegrations(surface)
68-
? [
69-
seed(
70-
organizationAccountsKeys.detail(organizationId),
71-
ORGANIZATION_ACCOUNTS_STALE_TIME,
72-
async () =>
73-
getOrganizationAccountsContract.response.schema.parse(
74-
await getOrganizationAccountsSettings.execute({ principal, input })
75-
)
76-
),
77-
seed(
78-
searchIntegrationKeys.list(organizationId),
79-
SEARCH_INTEGRATIONS_STALE_TIME,
80-
async () =>
81-
listSearchIntegrationsContract.response.schema.parse({
82-
success: true,
83-
data: await listSearchIntegrations.execute({ principal, input }),
84-
}).data
85-
),
86-
]
87-
: [],
52+
seed,
8853
searchHistory: () =>
8954
seed(
9055
searchHistoryKeys.list(organizationId, principal.userId),
@@ -128,21 +93,6 @@ function createSeeds(organizationId: string, principal: SessionPrincipal) {
12893
}
12994
}
13095

131-
/**
132-
* What decides each Home widget's state, so a viewer with no connection paints their prompt at once.
133-
* Provider reads stay on the client: they can take seconds.
134-
*/
135-
export function prefetchHome(
136-
surface: PrefetchSurface,
137-
principal: SessionPrincipal
138-
): Promise<DehydratedState> {
139-
const seeds = createSeeds(surface.organization.id, principal)
140-
return seeds.settle([
141-
...seeds.memberInventory(surface),
142-
...(surface.searchAccess.memberScoped ? [seeds.searchHistory()] : []),
143-
])
144-
}
145-
14696
/** The history under Search's field. Results stay on the client: they are the slow live read. */
14797
export function prefetchSearch(
14898
organizationId: string,
@@ -152,15 +102,6 @@ export function prefetchSearch(
152102
return seeds.settle([seeds.searchHistory()])
153103
}
154104

155-
/** The member inventory and secret source the Integrations pages list. */
156-
export function prefetchIntegrations(
157-
surface: PrefetchSurface,
158-
principal: SessionPrincipal
159-
): Promise<DehydratedState> {
160-
const seeds = createSeeds(surface.organization.id, principal)
161-
return seeds.settle([...seeds.memberInventory(surface), seeds.secretSource()])
162-
}
163-
164105
/** The first page of the list the URL asks for. Later pages load as the table scrolls. */
165106
export function prefetchSimLinks(
166107
organizationId: string,

0 commit comments

Comments
 (0)