Skip to content

Commit 55207ce

Browse files
feat(schema): File download COMPASS-8704 (#6740)
Co-authored-by: Sergey Petushkov <[email protected]> --------- Co-authored-by: Sergey Petushkov <[email protected]>
1 parent 026a15c commit 55207ce

File tree

10 files changed

+316
-50
lines changed

10 files changed

+316
-50
lines changed

packages/compass-e2e-tests/helpers/compass.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { inspect } from 'util';
22
import { ObjectId, EJSON } from 'bson';
33
import { promises as fs, rmdirSync } from 'fs';
44
import type Mocha from 'mocha';
5-
import path from 'path';
65
import os from 'os';
76
import { execFile } from 'child_process';
87
import type { ExecFileOptions, ExecFileException } from 'child_process';
@@ -42,6 +41,8 @@ import {
4241
ELECTRON_PATH,
4342
} from './test-runner-paths';
4443
import treeKill from 'tree-kill';
44+
import { downloadPath } from './downloads';
45+
import path from 'path';
4546

4647
const killAsync = async (pid: number, signal?: string) => {
4748
return new Promise<void>((resolve, reject) => {
@@ -677,6 +678,13 @@ async function startCompassElectron(
677678

678679
try {
679680
browser = (await remote(options)) as CompassBrowser;
681+
// https://webdriver.io/docs/best-practices/file-download/#configuring-chromium-browser-downloads
682+
const page = await browser.getPuppeteer();
683+
const cdpSession = await page.target().createCDPSession();
684+
await cdpSession.send('Browser.setDownloadBehavior', {
685+
behavior: 'allow',
686+
downloadPath: downloadPath,
687+
});
680688
} catch (err) {
681689
debug('Failed to start remote webdriver session', {
682690
error: (err as Error).stack,
@@ -755,6 +763,26 @@ export async function startBrowser(
755763
runCounter++;
756764
const { webdriverOptions, wdioOptions } = await processCommonOpts();
757765

766+
const browserCapabilities: Record<string, Record<string, unknown>> = {
767+
chrome: {
768+
'goog:chromeOptions': {
769+
prefs: {
770+
'download.default_directory': downloadPath,
771+
},
772+
},
773+
},
774+
firefox: {
775+
'moz:firefoxOptions': {
776+
prefs: {
777+
'browser.download.dir': downloadPath,
778+
'browser.download.folderList': 2,
779+
'browser.download.manager.showWhenStarting': false,
780+
'browser.helperApps.neverAsk.saveToDisk': '*/*',
781+
},
782+
},
783+
},
784+
};
785+
758786
// webdriverio removed RemoteOptions. It is now
759787
// Capabilities.WebdriverIOConfig, but Capabilities is not exported
760788
const options = {
@@ -763,6 +791,7 @@ export async function startBrowser(
763791
...(context.browserVersion && {
764792
browserVersion: context.browserVersion,
765793
}),
794+
...browserCapabilities[context.browserName],
766795

767796
// from https://github.com/webdriverio-community/wdio-electron-service/blob/32457f60382cb4970c37c7f0a19f2907aaa32443/packages/wdio-electron-service/src/launcher.ts#L102
768797
'wdio:enforceWebDriverClassic': true,
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import path from 'path';
2+
import fs from 'fs';
3+
4+
export const downloadPath = path.join(__dirname, 'downloads');
5+
6+
export const waitForFileDownload = async (
7+
filename: string,
8+
browser: WebdriverIO.Browser
9+
): Promise<{
10+
fileExists: boolean;
11+
filePath: string;
12+
}> => {
13+
const filePath = `${downloadPath}/${filename}`;
14+
await browser.waitUntil(
15+
function () {
16+
return fs.existsSync(filePath);
17+
},
18+
{ timeout: 10000, timeoutMsg: 'file not downloaded yet.' }
19+
);
20+
21+
return { fileExists: fs.existsSync(filePath), filePath };
22+
};
23+
24+
export const cleanUpDownloadedFile = (filename: string) => {
25+
const filePath = `${downloadPath}/${filename}`;
26+
try {
27+
if (fs.existsSync(filePath)) {
28+
fs.unlinkSync(filePath);
29+
}
30+
} catch (err) {
31+
console.error(`Error deleting file: ${(err as Error).message}`);
32+
}
33+
};

packages/compass-e2e-tests/helpers/selectors.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1025,7 +1025,12 @@ export const AnalyzeSchemaButton = '[data-testid="analyze-schema-button"]';
10251025
export const ExportSchemaButton = '[data-testid="open-schema-export-button"]';
10261026
export const ExportSchemaFormatOptions =
10271027
'[data-testid="export-schema-format-type-box-group"]';
1028+
export const exportSchemaFormatOption = (
1029+
option: 'standardJSON' | 'mongoDBJSON' | 'extendedJSON'
1030+
) => `label[for="export-schema-format-${option}-button"]`;
10281031
export const ExportSchemaOutput = '[data-testid="export-schema-content"]';
1032+
export const ExportSchemaDownloadButton =
1033+
'[data-testid="schema-export-download-button"]';
10291034
export const SchemaFieldList = '[data-testid="schema-field-list"]';
10301035
export const AnalysisMessage =
10311036
'[data-testid="schema-content"] [data-testid="schema-analysis-message"]';

packages/compass-e2e-tests/tests/collection-schema-tab.test.ts

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,19 @@ import {
66
screenshotIfFailed,
77
skipForWeb,
88
DEFAULT_CONNECTION_NAME_1,
9+
TEST_COMPASS_WEB,
910
} from '../helpers/compass';
1011
import type { Compass } from '../helpers/compass';
1112
import * as Selectors from '../helpers/selectors';
1213
import {
1314
createGeospatialCollection,
1415
createNumbersCollection,
1516
} from '../helpers/insert-data';
17+
import {
18+
cleanUpDownloadedFile,
19+
waitForFileDownload,
20+
} from '../helpers/downloads';
21+
import { readFileSync } from 'fs';
1622

1723
const { expect } = chai;
1824

@@ -111,12 +117,21 @@ describe('Collection schema tab', function () {
111117

112118
describe('with the enableExportSchema feature flag enabled', function () {
113119
beforeEach(async function () {
114-
// TODO(COMPASS-8819): remove web skip when defaulted true.
115-
skipForWeb(this, "can't toggle features in compass-web");
116-
await browser.setFeature('enableExportSchema', true);
120+
if (!TEST_COMPASS_WEB)
121+
await browser.setFeature('enableExportSchema', true);
122+
});
123+
124+
const filename = 'schema-test-numbers-mongoDBJSON.json';
125+
126+
before(() => {
127+
cleanUpDownloadedFile(filename);
117128
});
118129

119-
it('shows an exported schema to copy', async function () {
130+
after(() => {
131+
cleanUpDownloadedFile(filename);
132+
});
133+
134+
it('shows an exported schema to copy (standard JSON Schema)', async function () {
120135
await browser.navigateToCollectionTab(
121136
DEFAULT_CONNECTION_NAME_1,
122137
'test',
@@ -157,6 +172,57 @@ describe('Collection schema tab', function () {
157172
},
158173
});
159174
});
175+
176+
it('can download schema (MongoDB $jsonSchema)', async function () {
177+
await browser.navigateToCollectionTab(
178+
DEFAULT_CONNECTION_NAME_1,
179+
'test',
180+
'numbers',
181+
'Schema'
182+
);
183+
await browser.clickVisible(Selectors.AnalyzeSchemaButton);
184+
185+
const element = browser.$(Selectors.SchemaFieldList);
186+
await element.waitForDisplayed();
187+
188+
await browser.clickVisible(Selectors.ExportSchemaButton);
189+
190+
const exportModal = browser.$(Selectors.ExportSchemaFormatOptions);
191+
await exportModal.waitForDisplayed();
192+
193+
await browser.clickVisible(
194+
Selectors.exportSchemaFormatOption('mongoDBJSON')
195+
);
196+
197+
const exportSchemaButton = browser.$(
198+
Selectors.ExportSchemaDownloadButton
199+
);
200+
await exportSchemaButton.waitForEnabled();
201+
await exportSchemaButton.click();
202+
203+
const { fileExists, filePath } = await waitForFileDownload(
204+
filename,
205+
browser
206+
);
207+
expect(fileExists).to.be.true;
208+
209+
const content = readFileSync(filePath, 'utf-8');
210+
expect(JSON.parse(content)).to.deep.equal({
211+
bsonType: 'object',
212+
required: ['_id', 'i', 'j'],
213+
properties: {
214+
_id: {
215+
bsonType: 'objectId',
216+
},
217+
i: {
218+
bsonType: 'int',
219+
},
220+
j: {
221+
bsonType: 'int',
222+
},
223+
},
224+
});
225+
});
160226
});
161227

162228
it('analyzes the schema with a query');

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

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
ErrorSummary,
1515
Label,
1616
CancelLoader,
17+
SpinLoader,
1718
} from '@mongodb-js/compass-components';
1819
import { CodemirrorMultilineEditor } from '@mongodb-js/compass-editor';
1920

@@ -25,6 +26,7 @@ import {
2526
trackSchemaExported,
2627
type SchemaFormat,
2728
type ExportStatus,
29+
downloadSchema,
2830
} from '../stores/schema-export-reducer';
2931

3032
const loaderStyles = css({
@@ -80,10 +82,12 @@ const ExportSchemaModal: React.FunctionComponent<{
8082
resultId?: string;
8183
exportFormat: SchemaFormat;
8284
exportedSchema?: string;
85+
filename?: string;
8386
onCancelSchemaExport: () => void;
8487
onChangeSchemaExportFormat: (format: SchemaFormat) => Promise<void>;
8588
onClose: () => void;
8689
onExportedSchemaCopied: () => void;
90+
onSchemaDownload: () => void;
8791
}> = ({
8892
errorMessage,
8993
exportStatus,
@@ -94,6 +98,7 @@ const ExportSchemaModal: React.FunctionComponent<{
9498
onChangeSchemaExportFormat,
9599
onClose,
96100
onExportedSchemaCopied,
101+
onSchemaDownload,
97102
}) => {
98103
const onFormatOptionSelected = useCallback(
99104
(event: ChangeEvent<HTMLInputElement>) => {
@@ -178,10 +183,12 @@ const ExportSchemaModal: React.FunctionComponent<{
178183
Cancel
179184
</Button>
180185
<Button
181-
onClick={() => {
182-
/* TODO(COMPASS-8704): download and track with trackSchemaExported */
183-
}}
184186
variant="primary"
187+
isLoading={exportStatus === 'inprogress'}
188+
loadingIndicator={<SpinLoader />}
189+
disabled={!exportedSchema}
190+
onClick={onSchemaDownload}
191+
data-testid="schema-export-download-button"
185192
>
186193
Export
187194
</Button>
@@ -197,11 +204,14 @@ export default connect(
197204
exportFormat: state.schemaExport.exportFormat,
198205
isOpen: state.schemaExport.isOpen,
199206
exportedSchema: state.schemaExport.exportedSchema,
207+
filename: state.schemaExport.filename,
200208
}),
201209
{
202210
onExportedSchemaCopied: trackSchemaExported,
211+
onExportedSchema: trackSchemaExported,
203212
onCancelSchemaExport: cancelExportSchema,
204213
onChangeSchemaExportFormat: changeExportSchemaFormat,
205214
onClose: closeExportSchema,
215+
onSchemaDownload: downloadSchema,
206216
}
207217
)(ExportSchemaModal);

0 commit comments

Comments
 (0)