Skip to content

Commit 6d0d75a

Browse files
joehantjlav5
andauthored
Add support for emulator import/export in VSCode (#8053)
* Added config * Adding basic persistence support * formats * Starting on clearData * Clear data now working! * Added import/export support * PR fixes + fidelity to approved API' * Trying to fix vsce compile * Progress on vsce import/export/clearData UI * fixed build issue * Adding config options for importPath/exportPath/exportOnExit * More fixes * add changelog * Merge conflict * Tweak the emulator action-buttons * Startign on tests * Adding basic tests for export and clear' * format * ajv upgrade * Fix config issue * Fix typo * Santiy checking terminalView - does this even resolve? * Does it work as a single test? * PR fixes --------- Co-authored-by: TJ Lavelle <[email protected]>
1 parent a16eb2e commit 6d0d75a

File tree

19 files changed

+222
-22
lines changed

19 files changed

+222
-22
lines changed

firebase-vscode/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
## NEXT
22

3-
- [Feature] Added `debug` setting to run commands with `--debug`
3+
- [Added] Added support for emulator import/export.
4+
- [Added] Added `debug` setting to run commands with `--debug`
45

56
## 0.12.0
67

firebase-vscode/common/messaging/protocol.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,12 @@ export interface WebviewToExtensionParamsMap {
5555
/** Calls the `firebase init` CLI */
5656
runFirebaseInit: void;
5757

58-
/** Calls the `firebase init` CLI */
58+
/** Calls the `firebase emulators:start` CLI */
5959
runStartEmulators: void;
6060

61+
/** Calls the `firebase emulators:export` CLI */
62+
runEmulatorsExport: void;
63+
6164
/**
6265
* Show a UI message using the vscode interface
6366
*/
@@ -100,6 +103,12 @@ export interface WebviewToExtensionParamsMap {
100103
/** Opens generated docs */
101104
"fdc.open-docs": void;
102105

106+
/** Opens settings page searching for Data Connect emualtor settings */
107+
"fdc.open-emulator-settings": void;
108+
109+
/** Clears data from a running data connect emulator */
110+
"fdc.clear-emulator-data": void;
111+
103112
// Initialize "result" tab.
104113
getDataConnectResults: void;
105114

firebase-vscode/package.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,20 @@
7878
"markdownDescription": "%ext.config.idx.viewMetricNotice%",
7979
"scope": "application"
8080
},
81+
"firebase.emulators.importPath": {
82+
"type": "string",
83+
"markdownDescription": "%ext.config.emulators.importPath%"
84+
},
85+
"firebase.emulators.exportPath": {
86+
"type": "string",
87+
"default": "./exportedData",
88+
"markdownDescription": "%ext.config.emulators.exportPath%"
89+
},
90+
"firebase.emulators.exportOnExit":{
91+
"type": "boolean",
92+
"default": false,
93+
"markdownDescription": "%ext.config.emulators.exportOnExit%"
94+
},
8195
"firebase.debug": {
8296
"type": "boolean",
8397
"default": false,

firebase-vscode/package.nls.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44
"ext.config.firebasePath": "Path to the `Firebase` module, e.g. `./node_modules/firebase`",
55
"ext.config.hosting.useFrameworks": "Enable web frameworks",
66
"ext.config.npmPath": "Path to NPM executable in local environment",
7+
"ext.config.title": "Firebase Data Connect",
8+
"ext.config.idx.viewMetricNotice": "Show data collection notice on next startup (IDX Only)",
9+
"ext.config.emulators.importPath": "Path to import emulator data from",
10+
"ext.config.emulators.exportPath": "Path to export emulator data to",
11+
"ext.config.emulators.exportOnExit": "If true, data will be exported to exportPath when the emulator shuts down"
712
"ext.config.debug": "When true, add the --debug flag to any commands run by the extension",
8-
"ext.config.title": "Prettier",
9-
"ext.config.idx.viewMetricNotice": "Show data collection notice on next startup (IDX Only)"
1013
}

firebase-vscode/src/analytics.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export enum DATA_CONNECT_EVENT_NAME {
3232
START_EMULATORS = "start_emulators",
3333
AUTO_COMPLETE = "auto_complete",
3434
SESSION_CHAR_COUNT = "session_char_count",
35+
EMULATOR_EXPORT ="emulator_export",
3536
SETUP_FIREBASE_BINARY = "setup_firebase_binary",
3637
}
3738

firebase-vscode/src/core/emulators.ts

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { EmulatorsStatus, RunningEmulatorInfo } from "../messaging/types";
66
import { EmulatorHubClient } from "../../../src/emulator/hubClient";
77
import { GetEmulatorsResponse } from "../../../src/emulator/hub";
88
import { EmulatorInfo } from "../emulator/types";
9+
import { getSettings } from "../utils/settings";
910
export class EmulatorsController implements Disposable {
1011
constructor(private broker: ExtensionBrokerImpl) {
1112
this.emulatorStatusItem.command = "firebase.openFirebaseRc";
@@ -21,6 +22,25 @@ export class EmulatorsController implements Disposable {
2122
this.setEmulatorsStarting();
2223
}),
2324
);
25+
26+
// Subscription to open up settings window
27+
this.subscriptions.push(
28+
broker.on("fdc.open-emulator-settings", () => {
29+
vscode.commands.executeCommand( 'workbench.action.openSettings', 'firebase.emulators' );
30+
})
31+
);
32+
33+
// Subscription to trigger clear emulator data when button is clicked.
34+
this.subscriptions.push(
35+
broker.on("fdc.clear-emulator-data", () => {
36+
vscode.commands.executeCommand("firebase.emulators.clearData");
37+
}),
38+
);
39+
40+
// Subscription to trigger emulator exports when button is clicked.
41+
this.subscriptions.push(broker.on("runEmulatorsExport", () => {
42+
vscode.commands.executeCommand("firebase.emulators.exportData")
43+
}));
2444
}
2545

2646
readonly emulatorStatusItem = vscode.window.createStatusBarItem("emulators");
@@ -39,6 +59,17 @@ export class EmulatorsController implements Disposable {
3959
this.setEmulatorsStopped.bind(this),
4060
);
4161

62+
private readonly clearEmulatorDataCommand = vscode.commands.registerCommand(
63+
"firebase.emulators.clearData",
64+
this.clearDataConnectData.bind(this),
65+
);
66+
67+
68+
private readonly exportEmulatorDataCommand = vscode.commands.registerCommand(
69+
"firebase.emulators.exportData",
70+
this.exportEmulatorData.bind(this),
71+
);
72+
4273
readonly emulators: { status: EmulatorsStatus; infos?: RunningEmulatorInfo } =
4374
{
4475
status: "stopped",
@@ -107,23 +138,47 @@ export class EmulatorsController implements Disposable {
107138
async findRunningCliEmulators(): Promise<
108139
{ status: EmulatorsStatus; infos?: RunningEmulatorInfo } | undefined
109140
> {
110-
const projectId = firebaseRC.value?.tryReadValue?.projects?.default;
111-
// TODO: think about what to without projectID, in potentially a logged out mode
112-
const hubClient = new EmulatorHubClient(projectId!);
113-
114-
if (hubClient.foundHub()) {
141+
const hubClient = this.getHubClient();
142+
if (hubClient) {
115143
const response: GetEmulatorsResponse = await hubClient.getEmulators();
116144

117145
if (Object.values(response)) {
118146
this.setEmulatorsRunningInfo(Object.values(response));
119147
} else {
120148
this.setEmulatorsStopped();
121149
}
150+
}
151+
return this.emulators;
152+
}
153+
154+
async clearDataConnectData(): Promise<void> {
155+
const hubClient = this.getHubClient();
156+
if (hubClient) {
157+
await hubClient.clearDataConnectData();
158+
vscode.window.showInformationMessage(`Data Connect emulator data has been cleared.`);
159+
}
160+
}
161+
162+
async exportEmulatorData(): Promise<void> {
163+
const settings = getSettings();
164+
const exportDir = settings.exportPath;
165+
const hubClient = this.getHubClient();
166+
if (hubClient) {
167+
// TODO: Make exportDir configurable
168+
await hubClient.postExport({path: exportDir, initiatedBy: "Data Connect VSCode extension"});
169+
vscode.window.showInformationMessage(`Emulator Data exported to ${exportDir}`);
170+
}
171+
}
172+
173+
private getHubClient(): EmulatorHubClient | undefined {
174+
const projectId = firebaseRC.value?.tryReadValue?.projects?.default;
175+
// TODO: think about what to without projectID, in potentially a logged out mode
176+
const hubClient = new EmulatorHubClient(projectId!);
177+
if (hubClient.foundHub()) {
178+
return hubClient;
122179
} else {
123180
this.setEmulatorsStopped();
124181
}
125-
126-
return this.emulators;
127182
}
128183

129184
public areEmulatorsRunning() {
@@ -135,5 +190,7 @@ export class EmulatorsController implements Disposable {
135190
this.findRunningEmulatorsCommand.dispose();
136191
this.emulatorStatusItem.dispose();
137192
this.emulatorsStoppped.dispose();
193+
this.clearEmulatorDataCommand.dispose();
194+
this.exportEmulatorDataCommand.dispose();
138195
}
139196
}

firebase-vscode/src/data-connect/terminal.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,19 @@ export function registerTerminalTasks(
9090

9191
const startEmulatorsTaskBroker = broker.on("runStartEmulators", () => {
9292
analyticsLogger.logger.logUsage(DATA_CONNECT_EVENT_NAME.START_EMULATORS);
93-
// TODO: optional debug mode
93+
94+
let cmd = `${settings.firebasePath} emulators:start --project ${currentProjectId.value}`;
95+
96+
if (settings.importPath) {
97+
cmd += ` --import ${settings.importPath}`;
98+
}
99+
if (settings.exportOnExit) {
100+
cmd += ` --export-on-exit ${settings.exportPath}`;
101+
}
94102
runTerminalTask(
95103
"firebase emulators",
96-
`${settings.firebasePath} emulators:start --project ${currentProjectId.value}`,
97-
// emulators:start almost never ask interactive questions.
98-
{ focus: false },
104+
cmd,
105+
{ focus: true },
99106
);
100107
});
101108

firebase-vscode/src/test/integration/fishfood/emulator.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { firebaseSuite, firebaseTest } from "../../utils/test_hooks";
22
import { FirebaseCommands } from "../../utils/page_objects/commands";
33
import { FirebaseSidebar } from "../../utils/page_objects/sidebar";
4+
5+
import { TerminalView } from "../../utils/page_objects/terminal";
6+
import { Notifications } from "../../utils/page_objects/notifications";
47
import { mockUser } from "../../utils/user";
58
import { mockProject } from "../../utils/projects";
69

@@ -12,6 +15,8 @@ firebaseSuite("Emulators", async function () {
1215

1316
const sidebar = new FirebaseSidebar(workbench);
1417
const commands = new FirebaseCommands();
18+
const terminal = new TerminalView(workbench);
19+
const notifications = new Notifications(workbench);
1520

1621
await sidebar.openExtensionSidebar();
1722
await commands.waitForUser();
@@ -26,6 +31,15 @@ firebaseSuite("Emulators", async function () {
2631
const current = await sidebar.currentEmulators();
2732

2833
expect(current).toContain("dataconnect :9399");
34+
35+
await sidebar.clearEmulatorData();
36+
const text = await terminal.getTerminalText();
37+
expect(text.includes("Clearing data from Data Connect data sources")).toBeTruthy();
38+
39+
await sidebar.exportEmulatorData();
40+
const exportNotification = await notifications.getExportNotification();
41+
expect(exportNotification).toExist();
42+
2943
},
3044
);
3145
});

firebase-vscode/src/test/test_projects/fishfood/.firebaserc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"projects": {
3-
"default": "dart-firebase-admin"
3+
"default": "test-project"
44
},
55
"targets": {},
66
"etags": {},
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Workbench } from "wdio-vscode-service";
2+
3+
export class Notifications {
4+
constructor(readonly workbench: Workbench) {}
5+
6+
async getExportNotification() {
7+
const notifications = await this.workbench.getNotifications();
8+
return notifications.find(async n => {
9+
const message = await n.getMessage();
10+
return message.includes("Emulator Data exported to");
11+
});
12+
}
13+
}

0 commit comments

Comments
 (0)