Skip to content

Commit e666896

Browse files
committed
feat(schema): add schema export modal, feature flag gated
1 parent e41df6b commit e666896

File tree

11 files changed

+735
-95
lines changed

11 files changed

+735
-95
lines changed

packages/compass-preferences-model/src/feature-flags.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export type FeatureFlags = {
1717
enableOidc: boolean; // Not capitalized "OIDC" for spawn arg casing.
1818
newExplainPlan: boolean;
1919
showInsights: boolean;
20+
enableExportSchema: boolean;
2021
enableRenameCollectionModal: boolean;
2122
enableQueryHistoryAutocomplete: boolean;
2223
enableProxySupport: boolean;
@@ -100,4 +101,14 @@ export const featureFlags: Required<{
100101
short: 'Enable Global Writes tab in Atlas Cloud',
101102
},
102103
},
104+
105+
/**
106+
* Feature flag for export schema. Epic: COMPASS-6862.
107+
*/
108+
enableExportSchema: {
109+
stage: 'development',
110+
description: {
111+
short: 'Enable schema export',
112+
},
113+
},
103114
};

packages/compass-schema/src/components/compass-schema.tsx

Lines changed: 44 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ import { getAtlasPerformanceAdvisorLink } from '../utils';
3636
import { useIsLastAppliedQueryOutdated } from '@mongodb-js/compass-query-bar';
3737
import { useTelemetry } from '@mongodb-js/compass-telemetry/provider';
3838
import type { RootState } from '../stores/store';
39-
import { startAnalysis, stopAnalysis } from '../stores/reducer';
39+
import { startAnalysis, stopAnalysis } from '../stores/schema-analysis-reducer';
40+
import { openExportSchema } from '../stores/schema-export-reducer';
41+
import ExportSchemaModal from './export-schema-modal';
4042

4143
const rootStyles = css({
4244
width: '100%',
@@ -373,13 +375,15 @@ const Schema: React.FunctionComponent<{
373375
schema: MongodbSchema | null;
374376
count?: number;
375377
resultId?: string;
378+
onExportSchemaClicked: () => void;
376379
onStartAnalysis: () => Promise<void>;
377380
onStopAnalysis: () => void;
378381
}> = ({
379382
analysisState,
380383
errorMessage,
381384
schema,
382385
resultId,
386+
onExportSchemaClicked,
383387
onStartAnalysis,
384388
onStopAnalysis,
385389
}) => {
@@ -393,47 +397,54 @@ const Schema: React.FunctionComponent<{
393397
'enablePerformanceAdvisorBanner'
394398
);
395399

400+
const enableExportSchema = usePreference('enableExportSchema');
401+
396402
return (
397-
<div className={rootStyles}>
398-
<WorkspaceContainer
399-
toolbar={
400-
<SchemaToolbar
401-
onAnalyzeSchemaClicked={onApplyClicked}
402-
onResetClicked={onApplyClicked}
403-
analysisState={analysisState}
404-
errorMessage={errorMessage || ''}
405-
isOutdated={!!outdated}
406-
sampleSize={schema ? schema.count : 0}
407-
schemaResultId={resultId || ''}
408-
/>
409-
}
410-
>
411-
<div className={contentStyles}>
412-
{enablePerformanceAdvisorBanner && <PerformanceAdvisorBanner />}
413-
{analysisState === ANALYSIS_STATE_INITIAL && (
414-
<InitialScreen onApplyClicked={onApplyClicked} />
415-
)}
416-
{analysisState === ANALYSIS_STATE_ANALYZING && (
417-
<AnalyzingScreen onCancelClicked={onStopAnalysis} />
418-
)}
419-
{analysisState === ANALYSIS_STATE_COMPLETE && (
420-
<FieldList schema={schema} analysisState={analysisState} />
421-
)}
422-
</div>
423-
</WorkspaceContainer>
424-
</div>
403+
<>
404+
<div className={rootStyles}>
405+
<WorkspaceContainer
406+
toolbar={
407+
<SchemaToolbar
408+
onAnalyzeSchemaClicked={onApplyClicked}
409+
onExportSchemaClicked={onExportSchemaClicked}
410+
onResetClicked={onApplyClicked}
411+
analysisState={analysisState}
412+
errorMessage={errorMessage || ''}
413+
isOutdated={!!outdated}
414+
sampleSize={schema ? schema.count : 0}
415+
schemaResultId={resultId || ''}
416+
/>
417+
}
418+
>
419+
<div className={contentStyles}>
420+
{enablePerformanceAdvisorBanner && <PerformanceAdvisorBanner />}
421+
{analysisState === ANALYSIS_STATE_INITIAL && (
422+
<InitialScreen onApplyClicked={onApplyClicked} />
423+
)}
424+
{analysisState === ANALYSIS_STATE_ANALYZING && (
425+
<AnalyzingScreen onCancelClicked={onStopAnalysis} />
426+
)}
427+
{analysisState === ANALYSIS_STATE_COMPLETE && (
428+
<FieldList schema={schema} analysisState={analysisState} />
429+
)}
430+
</div>
431+
</WorkspaceContainer>
432+
</div>
433+
{enableExportSchema && <ExportSchemaModal />}
434+
</>
425435
);
426436
};
427437

428438
export default connect(
429439
(state: RootState) => ({
430-
analysisState: state.analysisState,
431-
errorMessage: state.errorMessage,
432-
schema: state.schema,
433-
resultId: state.resultId,
440+
analysisState: state.schemaAnalysis.analysisState,
441+
errorMessage: state.schemaAnalysis.errorMessage,
442+
schema: state.schemaAnalysis.schema,
443+
resultId: state.schemaAnalysis.resultId,
434444
}),
435445
{
436446
onStartAnalysis: startAnalysis,
437447
onStopAnalysis: stopAnalysis,
448+
onExportSchemaClicked: openExportSchema,
438449
}
439450
)(Schema);

packages/compass-schema/src/components/coordinates-minichart/coordinates-minichart.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
geoLayerAdded,
2323
geoLayersDeleted,
2424
geoLayersEdited,
25-
} from '../../stores/reducer';
25+
} from '../../stores/schema-analysis-reducer';
2626

2727
// TODO: Disable boxZoom handler for circle lasso.
2828
//
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
import React, { type ChangeEvent, useCallback } from 'react';
2+
import { connect } from 'react-redux';
3+
import {
4+
Banner,
5+
Button,
6+
Code,
7+
ModalBody,
8+
ModalHeader,
9+
ModalFooter,
10+
Modal,
11+
RadioBox,
12+
RadioBoxGroup,
13+
css,
14+
spacing,
15+
ErrorSummary,
16+
Label,
17+
CancelLoader,
18+
} from '@mongodb-js/compass-components';
19+
20+
import type { RootState } from '../stores/store';
21+
import {
22+
cancelExportSchema,
23+
changeExportSchemaFormat,
24+
closeExportSchema,
25+
type SchemaFormat,
26+
type ExportStatus,
27+
} from '../stores/schema-export-reducer';
28+
29+
const loaderStyles = css({
30+
marginTop: spacing[400],
31+
});
32+
33+
const legacyWarningStyles = css({
34+
marginTop: spacing[200],
35+
});
36+
37+
const contentContainerStyles = css({
38+
paddingTop: spacing[400],
39+
paddingBottom: spacing[400],
40+
});
41+
42+
const exportSchemaFormatOptions: {
43+
title: string;
44+
id: SchemaFormat;
45+
}[] = [
46+
{
47+
title: 'Standard',
48+
id: 'standardJSON',
49+
},
50+
{
51+
title: 'MongoDB',
52+
id: 'mongoDBJSON',
53+
},
54+
{
55+
title: 'Extended',
56+
id: 'extendedJSON',
57+
},
58+
{
59+
title: 'Extended (Legacy)',
60+
id: 'legacyJSON',
61+
},
62+
];
63+
64+
const formatTypeRadioBoxGroupId = 'export-schema-format-type-box-group';
65+
const formatTypeRadioBoxGroupLabelId = `${formatTypeRadioBoxGroupId}-label`;
66+
67+
const ExportSchemaModal: React.FunctionComponent<{
68+
errorMessage?: string;
69+
exportStatus: ExportStatus;
70+
isOpen: boolean;
71+
resultId?: string;
72+
exportFormat: SchemaFormat;
73+
exportedSchema?: string;
74+
onCancelSchemaExport: () => void;
75+
onChangeSchemaExportFormat: (format: SchemaFormat) => Promise<void>;
76+
onClose: () => void;
77+
}> = ({
78+
errorMessage,
79+
exportStatus,
80+
isOpen,
81+
exportFormat,
82+
exportedSchema,
83+
onCancelSchemaExport,
84+
onChangeSchemaExportFormat,
85+
onClose,
86+
}) => {
87+
const onFormatOptionSelected = useCallback(
88+
(event: ChangeEvent<HTMLInputElement>) => {
89+
event.preventDefault();
90+
91+
void onChangeSchemaExportFormat(event.target.value as SchemaFormat);
92+
},
93+
[onChangeSchemaExportFormat]
94+
);
95+
96+
return (
97+
<Modal open={isOpen} setOpen={onClose}>
98+
<ModalHeader title="Export Schema" />
99+
<ModalBody>
100+
<Label
101+
htmlFor={formatTypeRadioBoxGroupId}
102+
id={formatTypeRadioBoxGroupLabelId}
103+
>
104+
Schema Format
105+
</Label>
106+
<RadioBoxGroup
107+
aria-labelledby={formatTypeRadioBoxGroupLabelId}
108+
id={formatTypeRadioBoxGroupId}
109+
data-testid={formatTypeRadioBoxGroupId}
110+
onChange={onFormatOptionSelected}
111+
value={exportFormat}
112+
size="compact"
113+
>
114+
{exportSchemaFormatOptions.map(({ title, id }) => {
115+
return (
116+
<RadioBox
117+
id={`export-schema-format-${id}-button`}
118+
data-testid={`export-schema-format-${id}-button`}
119+
checked={exportFormat === id}
120+
value={id}
121+
key={id}
122+
>
123+
{title}
124+
</RadioBox>
125+
);
126+
})}
127+
</RadioBoxGroup>
128+
{exportFormat === 'legacyJSON' && (
129+
<Banner
130+
className={legacyWarningStyles}
131+
variant="warning"
132+
data-testid="legacy-export-json-warning"
133+
>
134+
<strong>
135+
The JSON Extended format is deprecated and will be removed in a
136+
future release.
137+
</strong>
138+
We&apos;re transitioning to a better format. Discover the new
139+
experience by selecting a relevant format from above.
140+
</Banner>
141+
)}
142+
<div className={contentContainerStyles}>
143+
{exportStatus === 'inprogress' && (
144+
<CancelLoader
145+
className={loaderStyles}
146+
data-testid="schema-export-loader"
147+
progressText="Formatting Schema"
148+
cancelText="Stop"
149+
onCancel={onCancelSchemaExport}
150+
/>
151+
)}
152+
{exportStatus === 'complete' && (
153+
<Code
154+
id="export-schema-content"
155+
data-testid="export-to-language-input"
156+
language="json"
157+
copyable={true}
158+
>
159+
{exportedSchema ?? 'Empty'}
160+
</Code>
161+
)}
162+
{exportStatus === 'error' && errorMessage && (
163+
<ErrorSummary
164+
data-testid="schema-export-error-message"
165+
errors={[
166+
`An error occurred during schema export: ${errorMessage}`,
167+
]}
168+
/>
169+
)}
170+
</div>
171+
</ModalBody>
172+
<ModalFooter>
173+
<Button onClick={onClose} variant="default">
174+
Cancel
175+
</Button>
176+
</ModalFooter>
177+
</Modal>
178+
);
179+
};
180+
181+
export default connect(
182+
(state: RootState) => ({
183+
exportStatus: state.schemaExport.exportStatus,
184+
errorMessage: state.schemaExport.errorMessage,
185+
exportFormat: state.schemaExport.exportFormat,
186+
isOpen: state.schemaExport.isOpen,
187+
exportedSchema: state.schemaExport.exportedSchema,
188+
}),
189+
{
190+
onCancelSchemaExport: cancelExportSchema,
191+
onChangeSchemaExportFormat: changeExportSchemaFormat,
192+
onClose: closeExportSchema,
193+
}
194+
)(ExportSchemaModal);

0 commit comments

Comments
 (0)