@@ -3,11 +3,12 @@ import { isAction } from './util';
3
3
import type { DataModelingThunkAction } from './reducer' ;
4
4
import { analyzeDocuments , type MongoDBJSONSchema } from 'mongodb-schema' ;
5
5
import { getCurrentDiagramFromState } from './diagram' ;
6
- import type { Document } from 'bson' ;
7
- import type { AggregationCursor } from 'mongodb' ;
6
+ import { UUID } from 'bson' ;
8
7
import type { Relationship } from '../services/data-model-storage' ;
9
8
import { applyLayout } from '@mongodb-js/diagramming' ;
10
9
import { collectionToBaseNodeForLayout } from '../utils/nodes-and-edges' ;
10
+ import { inferForeignToLocalRelationshipsForCollection } from './relationships' ;
11
+ import { mongoLogId } from '@mongodb-js/compass-logging/provider' ;
11
12
12
13
export type AnalysisProcessState = {
13
14
currentAnalysisOptions :
@@ -18,9 +19,10 @@ export type AnalysisProcessState = {
18
19
collections : string [ ] ;
19
20
} & AnalysisOptions )
20
21
| null ;
22
+ analysisProcessStatus : 'idle' | 'in-progress' ;
21
23
samplesFetched : number ;
22
24
schemasAnalyzed : number ;
23
- relationsInferred : boolean ;
25
+ relationsInferred : number ;
24
26
} ;
25
27
26
28
export enum AnalysisProcessActionTypes {
@@ -58,6 +60,8 @@ export type NamespaceSchemaAnalyzedAction = {
58
60
59
61
export type NamespacesRelationsInferredAction = {
60
62
type : AnalysisProcessActionTypes . NAMESPACES_RELATIONS_INFERRED ;
63
+ namespace : string ;
64
+ count : number ;
61
65
} ;
62
66
63
67
export type AnalysisFinishedAction = {
@@ -92,9 +96,10 @@ export type AnalysisProgressActions =
92
96
93
97
const INITIAL_STATE = {
94
98
currentAnalysisOptions : null ,
99
+ analysisProcessStatus : 'idle' as const ,
95
100
samplesFetched : 0 ,
96
101
schemasAnalyzed : 0 ,
97
- relationsInferred : false ,
102
+ relationsInferred : 0 ,
98
103
} ;
99
104
100
105
export const analysisProcessReducer : Reducer < AnalysisProcessState > = (
@@ -106,6 +111,7 @@ export const analysisProcessReducer: Reducer<AnalysisProcessState> = (
106
111
) {
107
112
return {
108
113
...INITIAL_STATE ,
114
+ analysisProcessStatus : 'in-progress' ,
109
115
currentAnalysisOptions : {
110
116
name : action . name ,
111
117
connectionId : action . connectionId ,
@@ -127,6 +133,16 @@ export const analysisProcessReducer: Reducer<AnalysisProcessState> = (
127
133
schemasAnalyzed : state . schemasAnalyzed + 1 ,
128
134
} ;
129
135
}
136
+ if (
137
+ isAction ( action , AnalysisProcessActionTypes . ANALYSIS_CANCELED ) ||
138
+ isAction ( action , AnalysisProcessActionTypes . ANALYSIS_FAILED ) ||
139
+ isAction ( action , AnalysisProcessActionTypes . ANALYSIS_FINISHED )
140
+ ) {
141
+ return {
142
+ ...state ,
143
+ analysisProcessStatus : 'idle' ,
144
+ } ;
145
+ }
130
146
return state ;
131
147
} ;
132
148
@@ -146,11 +162,26 @@ export function startAnalysis(
146
162
| AnalysisCanceledAction
147
163
| AnalysisFailedAction
148
164
> {
149
- return async ( dispatch , getState , services ) => {
165
+ return async (
166
+ dispatch ,
167
+ getState ,
168
+ {
169
+ connections,
170
+ cancelAnalysisControllerRef,
171
+ logger,
172
+ track,
173
+ dataModelStorage,
174
+ preferences,
175
+ }
176
+ ) => {
177
+ // Analysis is in progress, don't start a new one unless user canceled it
178
+ if ( cancelAnalysisControllerRef . current ) {
179
+ return ;
180
+ }
150
181
const namespaces = collections . map ( ( collName ) => {
151
182
return `${ database } .${ collName } ` ;
152
183
} ) ;
153
- const cancelController = ( services . cancelAnalysisControllerRef . current =
184
+ const cancelController = ( cancelAnalysisControllerRef . current =
154
185
new AbortController ( ) ) ;
155
186
dispatch ( {
156
187
type : AnalysisProcessActionTypes . ANALYZING_COLLECTIONS_START ,
@@ -161,18 +192,17 @@ export function startAnalysis(
161
192
options,
162
193
} ) ;
163
194
try {
164
- const dataService =
165
- services . connections . getDataServiceForConnection ( connectionId ) ;
195
+ let relations : Relationship [ ] = [ ] ;
196
+ const dataService = connections . getDataServiceForConnection ( connectionId ) ;
197
+
166
198
const collections = await Promise . all (
167
199
namespaces . map ( async ( ns ) => {
168
- const sample : AggregationCursor < Document > = dataService . sampleCursor (
200
+ const sample = await dataService . sample (
169
201
ns ,
170
202
{ size : 100 } ,
203
+ { promoteValues : false } ,
171
204
{
172
- signal : cancelController . signal ,
173
- promoteValues : false ,
174
- } ,
175
- {
205
+ abortSignal : cancelController . signal ,
176
206
fallbackReadPreference : 'secondaryPreferred' ,
177
207
}
178
208
) ;
@@ -194,26 +224,71 @@ export function startAnalysis(
194
224
type : AnalysisProcessActionTypes . NAMESPACE_SCHEMA_ANALYZED ,
195
225
namespace : ns ,
196
226
} ) ;
197
- return { ns, schema } ;
227
+ return { ns, schema, sample } ;
198
228
} )
199
229
) ;
200
230
201
- if ( options . automaticallyInferRelations ) {
202
- // TODO
231
+ if (
232
+ preferences . getPreferences ( ) . enableAutomaticRelationshipInference &&
233
+ options . automaticallyInferRelations
234
+ ) {
235
+ relations = (
236
+ await Promise . all (
237
+ collections . map (
238
+ async ( {
239
+ ns,
240
+ schema,
241
+ sample,
242
+ } ) : Promise < Relationship [ 'relationship' ] [ ] > => {
243
+ const relationships =
244
+ await inferForeignToLocalRelationshipsForCollection (
245
+ ns ,
246
+ schema ,
247
+ sample ,
248
+ collections ,
249
+ dataService ,
250
+ cancelController . signal ,
251
+ ( err ) => {
252
+ logger . log . warn (
253
+ mongoLogId ( 1_001_000_371 ) ,
254
+ 'DataModeling' ,
255
+ 'Failed to identify relationship for collection' ,
256
+ { ns, error : err . message }
257
+ ) ;
258
+ }
259
+ ) ;
260
+ dispatch ( {
261
+ type : AnalysisProcessActionTypes . NAMESPACES_RELATIONS_INFERRED ,
262
+ namespace : ns ,
263
+ count : relationships . length ,
264
+ } ) ;
265
+ return relationships ;
266
+ }
267
+ )
268
+ )
269
+ ) . flatMap ( ( relationships ) => {
270
+ return relationships . map ( ( relationship ) => {
271
+ return {
272
+ id : new UUID ( ) . toHexString ( ) ,
273
+ relationship,
274
+ isInferred : true ,
275
+ } ;
276
+ } ) ;
277
+ } ) ;
203
278
}
204
279
205
280
if ( cancelController . signal . aborted ) {
206
281
throw cancelController . signal . reason ;
207
282
}
208
283
209
284
const positioned = await applyLayout (
210
- collections . map ( ( coll ) =>
211
- collectionToBaseNodeForLayout ( {
285
+ collections . map ( ( coll ) => {
286
+ return collectionToBaseNodeForLayout ( {
212
287
ns : coll . ns ,
213
288
jsonSchema : coll . schema ,
214
289
displayPosition : [ 0 , 0 ] ,
215
- } )
216
- ) ,
290
+ } ) ;
291
+ } ) ,
217
292
[ ] ,
218
293
'LEFT_RIGHT'
219
294
) ;
@@ -229,22 +304,20 @@ export function startAnalysis(
229
304
const position = node ? node . position : { x : 0 , y : 0 } ;
230
305
return { ...coll , position } ;
231
306
} ) ,
232
- relations : [ ] ,
307
+ relations,
233
308
} ) ;
234
309
235
- services . track ( 'Data Modeling Diagram Created' , {
310
+ track ( 'Data Modeling Diagram Created' , {
236
311
num_collections : collections . length ,
237
312
} ) ;
238
313
239
- void services . dataModelStorage . save (
240
- getCurrentDiagramFromState ( getState ( ) )
241
- ) ;
314
+ void dataModelStorage . save ( getCurrentDiagramFromState ( getState ( ) ) ) ;
242
315
} catch ( err ) {
243
316
if ( cancelController . signal . aborted ) {
244
317
dispatch ( { type : AnalysisProcessActionTypes . ANALYSIS_CANCELED } ) ;
245
318
} else {
246
- services . logger . log . error (
247
- services . logger . mongoLogId ( 1_001_000_350 ) ,
319
+ logger . log . error (
320
+ mongoLogId ( 1_001_000_350 ) ,
248
321
'DataModeling' ,
249
322
'Failed to analyze schema' ,
250
323
{ err }
@@ -255,7 +328,7 @@ export function startAnalysis(
255
328
} ) ;
256
329
}
257
330
} finally {
258
- services . cancelAnalysisControllerRef . current = null ;
331
+ cancelAnalysisControllerRef . current = null ;
259
332
}
260
333
} ;
261
334
}
0 commit comments