Skip to content

Commit c13f44b

Browse files
committed
define and handle unsupported state
1 parent cf25bab commit c13f44b

File tree

7 files changed

+103
-6
lines changed

7 files changed

+103
-6
lines changed

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ describe('CollectionHeaderActions [Component]', function () {
6161
hasSchemaAnalysisData={true}
6262
analyzedSchemaDepth={2}
6363
schemaAnalysisStatus="complete"
64+
schemaAnalysisError={null}
6465
{...props}
6566
/>
6667
</PreferencesProvider>
@@ -294,5 +295,37 @@ describe('CollectionHeaderActions [Component]', function () {
294295
expect(button).to.exist;
295296
expect(button).to.have.attribute('aria-disabled', 'true');
296297
});
298+
299+
it('should show an error banner when the schema is in an unsupported state', async function () {
300+
mockUseAssignment.returns({
301+
assignment: {
302+
assignmentData: {
303+
variant: 'mockDataGeneratorVariant',
304+
},
305+
},
306+
});
307+
308+
await renderCollectionHeaderActions(
309+
{
310+
namespace: 'test.collection',
311+
isReadonly: false,
312+
hasSchemaAnalysisData: false,
313+
schemaAnalysisStatus: 'error',
314+
schemaAnalysisError: {
315+
errorType: 'unsupportedState',
316+
errorMessage: 'Unsupported state',
317+
},
318+
onOpenMockDataModal: sinon.stub(),
319+
},
320+
{},
321+
atlasConnectionInfo
322+
);
323+
324+
const button = screen.getByTestId(
325+
'collection-header-generate-mock-data-button'
326+
);
327+
expect(button).to.exist;
328+
expect(button).to.have.attribute('aria-disabled', 'true');
329+
});
297330
});
298331
});

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
import {
2222
SCHEMA_ANALYSIS_STATE_ANALYZING,
2323
type SchemaAnalysisStatus,
24+
type SchemaAnalysisError,
2425
} from '../../schema-analysis-types';
2526

2627
/**
@@ -57,6 +58,7 @@ type CollectionHeaderActionsProps = {
5758
sourcePipeline?: unknown[];
5859
onOpenMockDataModal: () => void;
5960
hasSchemaAnalysisData: boolean;
61+
schemaAnalysisError: SchemaAnalysisError | null;
6062
analyzedSchemaDepth: number;
6163
schemaAnalysisStatus: SchemaAnalysisStatus | null;
6264
};
@@ -73,6 +75,7 @@ const CollectionHeaderActions: React.FunctionComponent<
7375
hasSchemaAnalysisData,
7476
analyzedSchemaDepth,
7577
schemaAnalysisStatus,
78+
schemaAnalysisError,
7679
}: CollectionHeaderActionsProps) => {
7780
const connectionInfo = useConnectionInfo();
7881
const { id: connectionId, atlasMetadata } = connectionInfo;
@@ -151,6 +154,9 @@ const CollectionHeaderActions: React.FunctionComponent<
151154
'At this time we are unable to generate mock data for collections that have deeply nested documents'}
152155
{isCollectionEmpty &&
153156
'Please add data to your collection to generate similar mock documents'}
157+
{schemaAnalysisError &&
158+
schemaAnalysisError.errorType === 'unsupportedState' &&
159+
'This collection has a field with a name that contains a ".", which mock data generation does not support at this time.'}
154160
</Tooltip>
155161
)}
156162
{atlasMetadata && (

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ import { openMockDataGeneratorModal } from '../../modules/collection-tab';
2424
import type { CollectionState } from '../../modules/collection-tab';
2525
import {
2626
SCHEMA_ANALYSIS_STATE_COMPLETE,
27+
SCHEMA_ANALYSIS_STATE_ERROR,
2728
type SchemaAnalysisStatus,
29+
type SchemaAnalysisError,
2830
} from '../../schema-analysis-types';
2931

3032
const collectionHeaderStyles = css({
@@ -70,6 +72,7 @@ type CollectionHeaderProps = {
7072
hasSchemaAnalysisData: boolean;
7173
analyzedSchemaDepth: number;
7274
schemaAnalysisStatus: SchemaAnalysisStatus | null;
75+
schemaAnalysisError: SchemaAnalysisError | null;
7376
};
7477

7578
const getInsightsForPipeline = (pipeline: any[], isAtlas: boolean) => {
@@ -108,6 +111,7 @@ const CollectionHeader: React.FunctionComponent<CollectionHeaderProps> = ({
108111
hasSchemaAnalysisData,
109112
analyzedSchemaDepth,
110113
schemaAnalysisStatus,
114+
schemaAnalysisError,
111115
}) => {
112116
const darkMode = useDarkMode();
113117
const showInsights = usePreference('showInsights');
@@ -188,6 +192,7 @@ const CollectionHeader: React.FunctionComponent<CollectionHeaderProps> = ({
188192
hasSchemaAnalysisData={hasSchemaAnalysisData}
189193
analyzedSchemaDepth={analyzedSchemaDepth}
190194
schemaAnalysisStatus={schemaAnalysisStatus}
195+
schemaAnalysisError={schemaAnalysisError}
191196
/>
192197
</div>
193198
<MockDataGeneratorModal />
@@ -199,6 +204,10 @@ const mapStateToProps = (state: CollectionState) => {
199204
const { schemaAnalysis } = state;
200205

201206
return {
207+
schemaAnalysisError:
208+
schemaAnalysis && schemaAnalysis.status === SCHEMA_ANALYSIS_STATE_ERROR
209+
? schemaAnalysis.error
210+
: null,
202211
hasSchemaAnalysisData:
203212
schemaAnalysis &&
204213
schemaAnalysis.status === SCHEMA_ANALYSIS_STATE_COMPLETE &&

packages/compass-collection/src/components/mock-data-generator-modal/to-simplified-field-info.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { FIELD_NAME_SEPARATOR } from '../../transform-schema-to-field-info';
12
import type { processSchema } from '../../transform-schema-to-field-info';
23
import type { FieldInfo } from '../../schema-analysis-types';
34

@@ -22,7 +23,7 @@ export default function toSimplifiedFieldInfo(
2223

2324
const result: SimplifiedFieldInfoTree = {};
2425
for (const path of sortedFieldPaths) {
25-
const fieldParts = path.split('.');
26+
const fieldParts = path.split(FIELD_NAME_SEPARATOR);
2627

2728
let node = result;
2829
for (let i = 0; i < fieldParts.length; i++) {

packages/compass-collection/src/modules/collection-tab.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ import {
3232
type FieldInfo,
3333
} from '../schema-analysis-types';
3434
import { calculateSchemaDepth } from '../calculate-schema-depth';
35-
import { processSchema } from '../transform-schema-to-field-info';
35+
import {
36+
processSchema,
37+
ProcessSchemaUnsupportedStateError,
38+
} from '../transform-schema-to-field-info';
3639
import type { Document, MongoError } from 'mongodb';
3740
import { MockDataGeneratorStep } from '../components/mock-data-generator-modal/types';
3841
import type { MockDataGeneratorState } from '../components/mock-data-generator-modal/types';
@@ -51,6 +54,13 @@ function isAction<A extends AnyAction>(
5154
const ERROR_CODE_MAX_TIME_MS_EXPIRED = 50;
5255

5356
function getErrorDetails(error: Error): SchemaAnalysisError {
57+
if (error instanceof ProcessSchemaUnsupportedStateError) {
58+
return {
59+
errorType: 'unsupportedState',
60+
errorMessage: error.message,
61+
};
62+
}
63+
5464
const errorCode = (error as MongoError).code;
5565
const errorMessage = error.message || 'Unknown error';
5666
let errorType: SchemaAnalysisError['errorType'] = 'general';
@@ -648,6 +658,23 @@ export const analyzeCollectionSchema = (): CollectionThunkAction<
648658
return;
649659
}
650660

661+
if (err instanceof ProcessSchemaUnsupportedStateError) {
662+
logger.log.error(
663+
mongoLogId(1_001_000_365),
664+
'Collection',
665+
'Schema analysis failed due to unsupported state',
666+
{
667+
namespace,
668+
error: err.message,
669+
}
670+
);
671+
dispatch({
672+
type: CollectionActions.SchemaAnalysisFailed,
673+
error: err,
674+
});
675+
return;
676+
}
677+
651678
logger.log.error(
652679
mongoLogId(1_001_000_363),
653680
'Collection',

packages/compass-collection/src/schema-analysis-types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export type SchemaAnalysisStartedState = {
2222

2323
export type SchemaAnalysisError = {
2424
errorMessage: string;
25-
errorType: 'timeout' | 'highComplexity' | 'general';
25+
errorType: 'timeout' | 'highComplexity' | 'general' | 'unsupportedState';
2626
};
2727

2828
export type SchemaAnalysisErrorState = {

packages/compass-collection/src/transform-schema-to-field-info.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,21 @@ import {
4343
* Maximum number of sample values to include for each field
4444
*/
4545
const MAX_SAMPLE_VALUES = 10;
46+
export const FIELD_NAME_SEPARATOR = '.';
47+
48+
export class ProcessSchemaUnsupportedStateError extends Error {
49+
constructor(message: string) {
50+
super(message);
51+
this.name = 'ProcessSchemaUnsupportedStateError';
52+
}
53+
}
54+
55+
export class ProcessSchemaValidationError extends Error {
56+
constructor(message: string) {
57+
super(message);
58+
this.name = 'ProcessSchemaValidationError';
59+
}
60+
}
4661

4762
/**
4863
* Converts a BSON value to its primitive JavaScript equivalent
@@ -163,6 +178,12 @@ function processNamedField(
163178
return;
164179
}
165180

181+
if (field.name.includes(FIELD_NAME_SEPARATOR)) {
182+
throw new ProcessSchemaUnsupportedStateError(
183+
`no support for field names that contain a '.' ; field name: '${field.name}'`
184+
);
185+
}
186+
166187
const currentPath = pathPrefix ? `${pathPrefix}.${field.name}` : field.name;
167188

168189
// Process based on the type
@@ -238,13 +259,13 @@ function validateFieldPath(fieldPath: string) {
238259

239260
for (const part of parts) {
240261
if (part === '') {
241-
throw new Error(
262+
throw new ProcessSchemaValidationError(
242263
`invalid fieldPath '${fieldPath}': field parts cannot be empty`
243264
);
244265
}
245266

246267
if (part.replaceAll('[]', '') === '') {
247-
throw new Error(
268+
throw new ProcessSchemaValidationError(
248269
`invalid fieldPath '${fieldPath}': field parts must have characters other than '[]'`
249270
);
250271
}
@@ -257,7 +278,7 @@ function validateFieldPath(fieldPath: string) {
257278
// $ - at the end of the string
258279
const remaining = part.replace(/(\[\])+$/, '');
259280
if (remaining.includes('[') || remaining.includes(']')) {
260-
throw new Error(
281+
throw new ProcessSchemaValidationError(
261282
`invalid fieldPath '${fieldPath}': '[]' can only appear at the end of field parts`
262283
);
263284
}

0 commit comments

Comments
 (0)