@@ -9,8 +9,10 @@ import { createKnowledgeAccessProvider } from '@/lib/knowledge/access/scope'
99import { defineAuthorizedKnowledgeUseCase } from '@/lib/knowledge/application/authorized-knowledge-use-case'
1010import { resolveKnowledgeOwnerContext } from '@/lib/knowledge/application/contexts'
1111import { knowledgeOperations } from '@/lib/knowledge/application/operations'
12+ import { instrumentSourceOverviewUseCase } from '@/lib/knowledge/application/search-diagnostics'
1213import { MAX_SEARCH_SOURCE_PROVIDER_TYPES } from '@/lib/knowledge/constants'
1314import { knowledgeReadAccessBatches } from '@/lib/knowledge/read-access'
15+ import { annotateSearchDiagnostics , measureSearchStage } from '@/lib/knowledge/search/diagnostics'
1416import { searchIntegrationAccessCondition } from '@/lib/knowledge/search/integration-policy'
1517import { CONNECTOR_META_REGISTRY } from '@/connectors/registry'
1618
@@ -54,120 +56,144 @@ export async function listConfiguredSearchProviderTypes(owner: ResourceOwner): P
5456}
5557
5658/** Provider-level existence probes keep setup cards independent of the loaded source pages. */
57- export const readSearchSourceOverview = defineAuthorizedKnowledgeUseCase ( {
58- operation : knowledgeOperations . readSearchSourceOverview ,
59- resolveContext : ( { input } : { input : ResourceOwner } ) => resolveKnowledgeOwnerContext ( input ) ,
60- async execute ( { principal, context } ) : Promise < SearchSourceOverview > {
61- const availability = await resolveKnowledgeAccessAvailability ( context )
62- const access = createKnowledgeAccessProvider ( principal , context )
63- const providerTypes = searchProviderTypes ( )
64- const configured = configuredSearchSourceCondition ( context , providerTypes )
65- const available = or (
66- availability . memberScoped ? eq ( knowledgeConnector . accessMode , 'members' ) : undefined ,
67- availability . sourceMirrored
68- ? and (
69- eq ( knowledgeConnector . accessMode , 'admin' ) ,
70- inArray (
71- knowledgeConnector . connectorType ,
72- providerTypes . filter (
73- ( type ) =>
74- availability . memberScoped || ! CONNECTOR_META_REGISTRY [ type ] . requiresMemberIdentity
59+ export const readSearchSourceOverview = instrumentSourceOverviewUseCase (
60+ defineAuthorizedKnowledgeUseCase ( {
61+ operation : knowledgeOperations . readSearchSourceOverview ,
62+ resolveContext : ( { input } : { input : ResourceOwner } ) => resolveKnowledgeOwnerContext ( input ) ,
63+ async execute ( { principal, context } ) : Promise < SearchSourceOverview > {
64+ const availability = await measureSearchStage ( 'source_overview.availability' , ( ) =>
65+ resolveKnowledgeAccessAvailability ( context )
66+ )
67+ const access = createKnowledgeAccessProvider ( principal , context )
68+ const providerTypes = searchProviderTypes ( )
69+ const configured = configuredSearchSourceCondition ( context , providerTypes )
70+ const available = or (
71+ availability . memberScoped ? eq ( knowledgeConnector . accessMode , 'members' ) : undefined ,
72+ availability . sourceMirrored
73+ ? and (
74+ eq ( knowledgeConnector . accessMode , 'admin' ) ,
75+ inArray (
76+ knowledgeConnector . connectorType ,
77+ providerTypes . filter (
78+ ( type ) =>
79+ availability . memberScoped ||
80+ ! CONNECTOR_META_REGISTRY [ type ] . requiresMemberIdentity
81+ )
7582 )
7683 )
77- )
78- : undefined
79- )
80- const syncingEnabled = and (
81- available ,
82- searchIntegrationAccessCondition ( ) ,
83- notInArray ( knowledgeConnector . status , [ 'paused' , 'disabled' ] ) ,
84- or (
85- eq ( knowledgeConnector . accessMode , 'admin' ) ,
86- notInArray ( knowledgeConnector . memberSyncStatus , [ 'disabled' ] )
84+ : undefined
85+ )
86+ const syncingEnabled = and (
87+ available ,
88+ searchIntegrationAccessCondition ( ) ,
89+ notInArray ( knowledgeConnector . status , [ 'paused' , 'disabled' ] ) ,
90+ or (
91+ eq ( knowledgeConnector . accessMode , 'admin' ) ,
92+ notInArray ( knowledgeConnector . memberSyncStatus , [ 'disabled' ] )
93+ )
8794 )
88- )
89- const documentConditions = and (
90- eq ( document . connectorId , knowledgeConnector . id ) ,
91- eq ( document . knowledgeBaseId , knowledgeConnector . knowledgeBaseId ) ,
92- eq ( document . enabled , true ) ,
93- eq ( document . userExcluded , false ) ,
94- isNull ( document . archivedAt ) ,
95- isNull ( document . deletedAt )
96- )
97- const providers = await configuredProvidersQuery ( )
98- . where ( configured )
99- . limit ( MAX_SEARCH_SOURCE_PROVIDER_TYPES )
100- const indexingTypes = new Set < string > ( )
101- let hasSearchableDocuments = false
102- for await ( const accessCondition of knowledgeReadAccessBatches ( access , [
103- configured ,
104- available ,
105- documentConditions ,
106- ] ) ) {
107- const readableDocument = and ( documentConditions , accessCondition )
108- const [ indexing , searchable ] = await Promise . all ( [
109- availability . memberScoped || availability . sourceMirrored
110- ? configuredProvidersQuery ( )
111- . where (
112- and (
113- configured ,
114- syncingEnabled ,
115- or (
116- inArray ( knowledgeConnector . status , [ 'pending' , 'syncing' ] ) ,
117- and (
118- eq ( knowledgeConnector . accessMode , 'members' ) ,
119- inArray ( knowledgeConnector . memberSyncStatus , [ 'pending' , 'running' ] )
120- ) ,
121- exists (
122- db
123- . select ( { id : document . id } )
124- . from ( document )
125- . where (
95+ const documentConditions = and (
96+ eq ( document . connectorId , knowledgeConnector . id ) ,
97+ eq ( document . knowledgeBaseId , knowledgeConnector . knowledgeBaseId ) ,
98+ eq ( document . enabled , true ) ,
99+ eq ( document . userExcluded , false ) ,
100+ isNull ( document . archivedAt ) ,
101+ isNull ( document . deletedAt )
102+ )
103+ const providers = await measureSearchStage ( 'source_overview.providers' , ( ) =>
104+ configuredProvidersQuery ( ) . where ( configured ) . limit ( MAX_SEARCH_SOURCE_PROVIDER_TYPES )
105+ )
106+ annotateSearchDiagnostics ( { configuredProviderCount : providers . length } )
107+ const indexingTypes = new Set < string > ( )
108+ let searchableProbes = 0
109+ let hasSearchableDocuments = false
110+ for await ( const accessCondition of knowledgeReadAccessBatches ( access , [
111+ configured ,
112+ available ,
113+ documentConditions ,
114+ ] ) ) {
115+ const readableDocument = and ( documentConditions , accessCondition )
116+ const probesSources : boolean = availability . memberScoped || availability . sourceMirrored
117+ /** One searchable document is the whole answer, so later batches skip the probe entirely. */
118+ const probesSearchable : boolean = probesSources && ! hasSearchableDocuments
119+ if ( probesSearchable ) searchableProbes += 1
120+ /** Annotated so the searchable probe's guard does not infer through its own result. */
121+ const [ indexing , searchable ] : [ { connectorType : string } [ ] , { id : string } [ ] ] =
122+ await Promise . all ( [
123+ probesSources
124+ ? measureSearchStage ( 'source_overview.indexing' , ( ) =>
125+ configuredProvidersQuery ( )
126+ . where (
127+ and (
128+ configured ,
129+ syncingEnabled ,
130+ or (
131+ inArray ( knowledgeConnector . status , [ 'pending' , 'syncing' ] ) ,
126132 and (
127- readableDocument ,
128- inArray ( document . processingStatus , [ 'pending' , 'processing' ] )
133+ eq ( knowledgeConnector . accessMode , 'members' ) ,
134+ inArray ( knowledgeConnector . memberSyncStatus , [ 'pending' , 'running' ] )
135+ ) ,
136+ exists (
137+ db
138+ . select ( { id : document . id } )
139+ . from ( document )
140+ . where (
141+ and (
142+ readableDocument ,
143+ inArray ( document . processingStatus , [ 'pending' , 'processing' ] )
144+ )
145+ )
129146 )
130147 )
148+ )
131149 )
132- )
150+ . limit ( MAX_SEARCH_SOURCE_PROVIDER_TYPES )
133151 )
134- )
135- . limit ( MAX_SEARCH_SOURCE_PROVIDER_TYPES )
136- : [ ] ,
137- availability . memberScoped || availability . sourceMirrored
138- ? db
139- . select ( { id : document . id } )
140- . from ( document )
141- . innerJoin ( knowledgeConnector , eq ( knowledgeConnector . id , document . connectorId ) )
142- . innerJoin ( knowledgeBase , eq ( knowledgeBase . id , knowledgeConnector . knowledgeBaseId ) )
143- . where (
144- and (
145- configured ,
146- available ,
147- readableDocument ,
148- eq ( document . processingStatus , 'completed' ) ,
149- exists (
150- db
151- . select ( { id : embedding . id } )
152- . from ( embedding )
153- . where (
154- and ( eq ( embedding . documentId , document . id ) , eq ( embedding . enabled , true ) )
152+ : [ ] ,
153+ probesSearchable
154+ ? measureSearchStage ( 'source_overview.searchable' , ( ) =>
155+ db
156+ . select ( { id : document . id } )
157+ . from ( document )
158+ . innerJoin ( knowledgeConnector , eq ( knowledgeConnector . id , document . connectorId ) )
159+ . innerJoin (
160+ knowledgeBase ,
161+ eq ( knowledgeBase . id , knowledgeConnector . knowledgeBaseId )
162+ )
163+ . where (
164+ and (
165+ configured ,
166+ available ,
167+ readableDocument ,
168+ eq ( document . processingStatus , 'completed' ) ,
169+ exists (
170+ db
171+ . select ( { id : embedding . id } )
172+ . from ( embedding )
173+ . where (
174+ and (
175+ eq ( embedding . documentId , document . id ) ,
176+ eq ( embedding . enabled , true )
177+ )
178+ )
179+ )
155180 )
156- )
181+ )
182+ . limit ( 1 )
157183 )
158- )
159- . limit ( 1 )
160- : [ ] ,
161- ] )
162- for ( const provider of indexing ) indexingTypes . add ( provider . connectorType )
163- hasSearchableDocuments ||= searchable . length > 0
164- }
165- return {
166- providers : providers . map ( ( { connectorType } ) => ( {
167- connectorType,
168- isSyncing : indexingTypes . has ( connectorType ) ,
169- } ) ) ,
170- hasSearchableDocuments ,
171- }
172- } ,
173- } )
184+ : [ ] ,
185+ ] )
186+ for ( const provider of indexing ) indexingTypes . add ( provider . connectorType )
187+ hasSearchableDocuments ||= searchable . length > 0
188+ }
189+ annotateSearchDiagnostics ( { searchableProbeCount : searchableProbes } )
190+ return {
191+ providers : providers . map ( ( { connectorType } ) => ( {
192+ connectorType ,
193+ isSyncing : indexingTypes . has ( connectorType ) ,
194+ } ) ) ,
195+ hasSearchableDocuments ,
196+ }
197+ } ,
198+ } )
199+ )
0 commit comments