Skip to content

Commit d8a0d16

Browse files
authored
feat(compass-collection): Connect schema redux state to modal and filter collections based on nesting depth - CLOUDP-342665 (#7283)
* WIP * WIP * Handle undefined * Comments * Move tests * Address comment * Add getAssignment to experimentation interface * Dependency updates * Hide button tooltip while analysis in progress * Address comment to add tooltip * Add thunk action abort signal cancellation
1 parent 22377c1 commit d8a0d16

File tree

11 files changed

+708
-132
lines changed

11 files changed

+708
-132
lines changed

package-lock.json

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/compass-collection/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
},
7474
"devDependencies": {
7575
"@mongodb-js/eslint-config-compass": "^1.4.8",
76+
"@mongodb-js/mdb-experiment-js": "1.9.0",
7677
"@mongodb-js/mocha-config-compass": "^1.7.1",
7778
"@mongodb-js/prettier-config-compass": "^1.2.8",
7879
"@mongodb-js/testing-library-compass": "^1.3.11",

packages/compass-collection/src/components/collection-header-actions/collection-header-actions.spec.tsx

Lines changed: 24 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ describe('CollectionHeaderActions [Component]', function () {
4848
<CompassExperimentationProvider
4949
useAssignment={mockUseAssignment}
5050
assignExperiment={sinon.stub()}
51+
getAssignment={sinon.stub().resolves(null)}
5152
>
5253
<WorkspacesServiceProvider
5354
value={workspaceService as WorkspacesService}
@@ -57,6 +58,9 @@ describe('CollectionHeaderActions [Component]', function () {
5758
namespace="test.test"
5859
isReadonly={false}
5960
onOpenMockDataModal={sinon.stub()}
61+
hasSchemaAnalysisData={true}
62+
analyzedSchemaDepth={2}
63+
schemaAnalysisStatus="complete"
6064
{...props}
6165
/>
6266
</PreferencesProvider>
@@ -221,97 +225,21 @@ describe('CollectionHeaderActions [Component]', function () {
221225
},
222226
};
223227

224-
it('should not show Mock Data Generator button when user is in control group', async function () {
225-
mockUseAssignment.returns({
226-
assignment: {
227-
assignmentData: {
228-
variant: 'mockDataGeneratorControl',
229-
},
230-
},
231-
});
232-
233-
await renderCollectionHeaderActions(
234-
{
235-
namespace: 'test.collection',
236-
isReadonly: false,
237-
},
238-
{},
239-
atlasConnectionInfo
240-
);
241-
242-
expect(
243-
screen.queryByTestId('collection-header-generate-mock-data-button')
244-
).to.not.exist;
245-
});
246-
247-
it('should not show Mock Data Generator button when not in Atlas', async function () {
248-
mockUseAssignment.returns({
249-
assignment: {
250-
assignmentData: {
251-
variant: 'treatment',
252-
},
253-
},
254-
});
255-
228+
it('should call useAssignment with correct parameters', async function () {
256229
await renderCollectionHeaderActions({
257230
namespace: 'test.collection',
258231
isReadonly: false,
259-
// Don't pass atlasConnectionInfo, to simulate not being in Atlas
260232
});
261233

262-
expect(
263-
screen.queryByTestId('collection-header-generate-mock-data-button')
264-
).to.not.exist;
265-
});
266-
267-
it('should not show Mock Data Generator button for readonly collections', async function () {
268-
mockUseAssignment.returns({
269-
assignment: {
270-
assignmentData: {
271-
variant: 'treatment',
272-
},
273-
},
274-
});
275-
276-
await renderCollectionHeaderActions(
277-
{
278-
namespace: 'test.collection',
279-
isReadonly: true,
280-
},
281-
{},
282-
atlasConnectionInfo
234+
expect(mockUseAssignment).to.have.been.calledWith(
235+
ExperimentTestName.mockDataGenerator,
236+
true // trackIsInSample - Experiment viewed analytics event
283237
);
284-
285-
expect(
286-
screen.queryByTestId('collection-header-generate-mock-data-button')
287-
).to.not.exist;
288238
});
289239

290-
it('should not show Mock Data Generator button for views (sourceName present)', async function () {
291-
mockUseAssignment.returns({
292-
assignment: {
293-
assignmentData: {
294-
variant: 'treatment',
295-
},
296-
},
297-
});
298-
299-
await renderCollectionHeaderActions(
300-
{
301-
namespace: 'test.collection',
302-
isReadonly: false,
303-
sourceName: 'source-collection',
304-
},
305-
{},
306-
atlasConnectionInfo
307-
);
308-
309-
expect(
310-
screen.queryByTestId('collection-header-generate-mock-data-button')
311-
).to.not.exist;
312-
});
240+
it('should call onOpenMockDataModal when CTA button is clicked', async function () {
241+
const onOpenMockDataModal = sinon.stub();
313242

314-
it('should show Mock Data Generator button when user is in treatment group and in Atlas', async function () {
315243
mockUseAssignment.returns({
316244
assignment: {
317245
assignmentData: {
@@ -324,35 +252,25 @@ describe('CollectionHeaderActions [Component]', function () {
324252
{
325253
namespace: 'test.collection',
326254
isReadonly: false,
255+
onOpenMockDataModal,
327256
},
328257
{},
329258
atlasConnectionInfo
330259
);
331260

332-
expect(
333-
screen.getByTestId('collection-header-generate-mock-data-button')
334-
).to.exist;
335-
});
336-
337-
it('should call useAssignment with correct parameters', async function () {
338-
await renderCollectionHeaderActions({
339-
namespace: 'test.collection',
340-
isReadonly: false,
341-
});
342-
343-
expect(mockUseAssignment).to.have.been.calledWith(
344-
ExperimentTestName.mockDataGenerator,
345-
true // trackIsInSample - Experiment viewed analytics event
261+
const button = screen.getByTestId(
262+
'collection-header-generate-mock-data-button'
346263
);
347-
});
264+
button.click();
348265

349-
it('should call onOpenMockDataModal when CTA button is clicked', async function () {
350-
const onOpenMockDataModal = sinon.stub();
266+
expect(onOpenMockDataModal).to.have.been.calledOnce;
267+
});
351268

269+
it('should disable button for deeply nested collections', async function () {
352270
mockUseAssignment.returns({
353271
assignment: {
354272
assignmentData: {
355-
variant: 'mockDataGeneratorVariant',
273+
variant: 'mockDataGeneratorVariant', // Treatment variant
356274
},
357275
},
358276
});
@@ -361,7 +279,10 @@ describe('CollectionHeaderActions [Component]', function () {
361279
{
362280
namespace: 'test.collection',
363281
isReadonly: false,
364-
onOpenMockDataModal,
282+
hasSchemaAnalysisData: true,
283+
analyzedSchemaDepth: 5, // Exceeds MAX_COLLECTION_NESTING_DEPTH (3)
284+
schemaAnalysisStatus: 'complete',
285+
onOpenMockDataModal: sinon.stub(),
365286
},
366287
{},
367288
atlasConnectionInfo
@@ -370,9 +291,8 @@ describe('CollectionHeaderActions [Component]', function () {
370291
const button = screen.getByTestId(
371292
'collection-header-generate-mock-data-button'
372293
);
373-
button.click();
374-
375-
expect(onOpenMockDataModal).to.have.been.calledOnce;
294+
expect(button).to.exist;
295+
expect(button).to.have.attribute('aria-disabled', 'true');
376296
});
377297
});
378298
});

packages/compass-collection/src/components/collection-header-actions/collection-header-actions.tsx

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,15 @@ import {
1818
ExperimentTestName,
1919
ExperimentTestGroup,
2020
} from '@mongodb-js/compass-telemetry/provider';
21+
import {
22+
SCHEMA_ANALYSIS_STATE_ANALYZING,
23+
type SchemaAnalysisStatus,
24+
} from '../../schema-analysis-types';
25+
26+
/**
27+
* Maximum allowed nesting depth for collections to show Mock Data Generator
28+
*/
29+
const MAX_COLLECTION_NESTING_DEPTH = 3;
2130

2231
const collectionHeaderActionsStyles = css({
2332
display: 'flex',
@@ -47,6 +56,9 @@ type CollectionHeaderActionsProps = {
4756
sourceName?: string;
4857
sourcePipeline?: unknown[];
4958
onOpenMockDataModal: () => void;
59+
hasSchemaAnalysisData: boolean;
60+
analyzedSchemaDepth: number;
61+
schemaAnalysisStatus: SchemaAnalysisStatus | null;
5062
};
5163

5264
const CollectionHeaderActions: React.FunctionComponent<
@@ -58,6 +70,9 @@ const CollectionHeaderActions: React.FunctionComponent<
5870
sourceName,
5971
sourcePipeline,
6072
onOpenMockDataModal,
73+
hasSchemaAnalysisData,
74+
analyzedSchemaDepth,
75+
schemaAnalysisStatus,
6176
}: CollectionHeaderActionsProps) => {
6277
const connectionInfo = useConnectionInfo();
6378
const { id: connectionId, atlasMetadata } = connectionInfo;
@@ -85,9 +100,13 @@ const CollectionHeaderActions: React.FunctionComponent<
85100
atlasMetadata && // Only show in Atlas
86101
!isReadonly && // Don't show for readonly collections (views)
87102
!sourceName; // sourceName indicates it's a view
88-
// TODO: CLOUDP-337090: also filter out overly nested collections
89103

90-
const hasData = true; // TODO: CLOUDP-337090
104+
const exceedsMaxNestingDepth =
105+
analyzedSchemaDepth > MAX_COLLECTION_NESTING_DEPTH;
106+
107+
const isCollectionEmpty =
108+
!hasSchemaAnalysisData &&
109+
schemaAnalysisStatus !== SCHEMA_ANALYSIS_STATE_ANALYZING;
91110

92111
return (
93112
<div
@@ -111,13 +130,13 @@ const CollectionHeaderActions: React.FunctionComponent<
111130
)}
112131
{shouldShowMockDataButton && (
113132
<Tooltip
114-
enabled={!hasData}
133+
enabled={exceedsMaxNestingDepth || isCollectionEmpty}
115134
trigger={
116135
<div>
117136
<Button
118137
data-testid="collection-header-generate-mock-data-button"
119138
size={ButtonSize.Small}
120-
disabled={!hasData}
139+
disabled={!hasSchemaAnalysisData || exceedsMaxNestingDepth}
121140
onClick={onOpenMockDataModal}
122141
leftGlyph={<Icon glyph="Sparkle" />}
123142
>
@@ -126,7 +145,10 @@ const CollectionHeaderActions: React.FunctionComponent<
126145
</div>
127146
}
128147
>
129-
Please add data to your collection to generate similar mock documents
148+
{exceedsMaxNestingDepth &&
149+
'At this time we are unable to generate mock data for collections that have deeply nested documents'}
150+
{isCollectionEmpty &&
151+
'Please add data to your collection to generate similar mock documents'}
130152
</Tooltip>
131153
)}
132154
{atlasMetadata && (

0 commit comments

Comments
 (0)