diff --git a/.github/workflows/node-test.yml b/.github/workflows/node-test.yml index 6a629ea694c..9ecf3d737a8 100644 --- a/.github/workflows/node-test.yml +++ b/.github/workflows/node-test.yml @@ -176,6 +176,7 @@ jobs: - npm run test:triggers-end-to-end - npm run test:triggers-end-to-end:inspect - npm run test:dataconnect-deploy + - npm run test:dataconnect-emulator steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v3 @@ -231,6 +232,7 @@ jobs: - npm run test:storage-deploy # - npm run test:storage-emulator-integration # - npm run test:dataconnect-deploy # TODO (joehanley): Reenable this - it should be safe to run in parallel + # - npm run test:dataconnect-emulator # TODO (joehanley): Figure out why this is failing - npm run test:frameworks steps: - name: Setup Java JDK @@ -260,7 +262,7 @@ jobs: - run: ${{ matrix.script }} - name: Print debug logs if: failure() - run: type *debug.log + run: dir "*.log" /s/b | type check-package-lock: runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index d805d2b5509..695bbf3c996 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ src/test/emulators/extensions/firebase/storage-resize-images@0.1.18/functions/pa src/test/emulators/extensions/firebase/storage-resize-images@0.1.18/functions/package-lock.json scripts/functions-deploy-tests/**/package-lock.json scripts/functions-discover-tests/**/**/package-lock.json +.dataconnect +*-debug.log /.vscode node_modules diff --git a/CHANGELOG.md b/CHANGELOG.md index f35e7695c2d..c0860bb757f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,3 @@ - Improved error messaging when using a project that does not have Firebase enabled. - Changes default CF3 runtime to nodejs22 (#8037) +- Fixed an issue where `--import` would error for the Data Connect emulator if `dataDir` was also set. diff --git a/firebase-vscode/CHANGELOG.md b/firebase-vscode/CHANGELOG.md index ca6fd829d00..ed107c1d6e6 100644 --- a/firebase-vscode/CHANGELOG.md +++ b/firebase-vscode/CHANGELOG.md @@ -1,5 +1,11 @@ ## NEXT +## 0.11.1 + +- [Fixed] Fixed IDX analytics issue + +## 0.11.0 + - Updated internal firebase-tools dependency to 13.28.0 - [Fixed] Fixed an issue where generating an ad-hoc file would break codelenses diff --git a/firebase-vscode/package-lock.json b/firebase-vscode/package-lock.json index acc8c4ea917..a12947a3a2b 100644 --- a/firebase-vscode/package-lock.json +++ b/firebase-vscode/package-lock.json @@ -1,12 +1,12 @@ { "name": "firebase-dataconnect-vscode", - "version": "0.11.0", + "version": "0.11.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "firebase-dataconnect-vscode", - "version": "0.11.0", + "version": "0.11.1", "dependencies": { "@preact/signals-core": "^1.4.0", "@preact/signals-react": "1.3.6", diff --git a/firebase-vscode/package.json b/firebase-vscode/package.json index 6845b6805be..db57bfc9f27 100644 --- a/firebase-vscode/package.json +++ b/firebase-vscode/package.json @@ -4,7 +4,7 @@ "publisher": "GoogleCloudTools", "icon": "./resources/firebase_dataconnect_logo.png", "description": "Firebase Data Connect for VSCode", - "version": "0.11.0", + "version": "0.11.1", "engines": { "vscode": "^1.69.0" }, diff --git a/firebase-vscode/src/analytics.ts b/firebase-vscode/src/analytics.ts index 6ae5376a3cb..a92f4213f17 100644 --- a/firebase-vscode/src/analytics.ts +++ b/firebase-vscode/src/analytics.ts @@ -35,12 +35,12 @@ export enum DATA_CONNECT_EVENT_NAME { } export class AnalyticsLogger { - readonly logger: TelemetryLogger; + readonly logger: TelemetryLogger | IDXLogger; private disposable: vscode.Disposable; private sessionCharCount = 0; // Track total chars for the session - constructor() { - this.logger = env.createTelemetryLogger( + constructor(context: vscode.ExtensionContext) { + this.logger = monospaceEnv.value.isMonospace ? new IDXLogger(new GA4TelemetrySender(pluginLogger), context) : env.createTelemetryLogger( new GA4TelemetrySender(pluginLogger), ); @@ -146,6 +146,19 @@ export class AnalyticsLogger { } } +export class IDXLogger { + constructor(private sender: GA4TelemetrySender, private context: vscode.ExtensionContext) {} + public logUsage(eventName: string, data?: any) { + const packageJson = this.context.extension.packageJSON; + data = { ...data, extversion: packageJson.version, extname: this.context.extension.id, isidx: true }; + this.sender.sendEventData(eventName, data); + } + + public logError() { + // TODO + } +} + class GA4TelemetrySender implements TelemetrySender { private hasSentData = false; constructor(readonly pluginLogger: { warn: (s: string) => void }) {} @@ -154,12 +167,6 @@ class GA4TelemetrySender implements TelemetrySender { eventName: string, data?: Record | undefined, ): void { - // telemtry flag does not exist in monospace - if (!env.isTelemetryEnabled && !monospaceEnv.value.isMonospace) { - this.pluginLogger.warn("Telemetry is not enabled."); - return; - } - // telemetry logger adds prefixes to eventName and params that are disallowed in GA4 eventName = eventName.replace( "GoogleCloudTools.firebase-dataconnect-vscode/", @@ -176,15 +183,14 @@ class GA4TelemetrySender implements TelemetrySender { } } data = { ...data }; - const idxPrepend = monospaceEnv.value.isMonospace ? "idx_" : ""; if (!this.hasSentData) { trackVSCode( - `${idxPrepend}${DATA_CONNECT_EVENT_NAME.EXTENSION_USED}`, + DATA_CONNECT_EVENT_NAME.EXTENSION_USED, data as AnalyticsParams, ); this.hasSentData = true; } - trackVSCode(`${idxPrepend}${eventName}`, data as AnalyticsParams); + trackVSCode(eventName, data as AnalyticsParams); } sendErrorData(error: Error, data?: Record | undefined): void { diff --git a/firebase-vscode/src/core/index.ts b/firebase-vscode/src/core/index.ts index 9c20fb6567d..1b70508edd1 100644 --- a/firebase-vscode/src/core/index.ts +++ b/firebase-vscode/src/core/index.ts @@ -1,4 +1,4 @@ -import vscode, { Disposable, ExtensionContext, TelemetryLogger } from "vscode"; +import vscode, { Disposable, ExtensionContext } from "vscode"; import { ExtensionBrokerImpl } from "../extension-broker"; import { getRootFolders, registerConfig } from "./config"; import { EmulatorsController } from "./emulators"; @@ -14,11 +14,12 @@ import { upsertFile } from "../data-connect/file-utils"; import { registerWebhooks } from "./webhook"; import { createE2eMockable } from "../utils/test_hooks"; import { runTerminalTask } from "../data-connect/terminal"; +import { AnalyticsLogger } from "../analytics"; export async function registerCore( broker: ExtensionBrokerImpl, context: ExtensionContext, - telemetryLogger: TelemetryLogger, + analyticsLogger: AnalyticsLogger, ): Promise<[EmulatorsController, vscode.Disposable]> { const settings = getSettings(); @@ -69,7 +70,7 @@ export async function registerCore( ? `${settings.firebasePath} init dataconnect --project ${currentProjectId.value}` : `${settings.firebasePath} init dataconnect`; - initSpy.call("firebase init", initCommand, {focus: true}); + initSpy.call("firebase init", initCommand, { focus: true }); }); const emulatorsController = new EmulatorsController(broker); @@ -103,8 +104,8 @@ export async function registerCore( initSpy, registerOptions(context), registerEnv(broker), - registerUser(broker, telemetryLogger), - registerProject(broker, telemetryLogger), + registerUser(broker, analyticsLogger), + registerProject(broker, analyticsLogger), registerQuickstart(broker), await registerWebhooks(), { dispose: sub1 }, diff --git a/firebase-vscode/src/core/project.ts b/firebase-vscode/src/core/project.ts index d55cc5cd240..7bca63c8b63 100644 --- a/firebase-vscode/src/core/project.ts +++ b/firebase-vscode/src/core/project.ts @@ -1,4 +1,4 @@ -import vscode, { Disposable, TelemetryLogger } from "vscode"; +import vscode, { Disposable } from "vscode"; import { ExtensionBrokerImpl } from "../extension-broker"; import { computed, effect, Signal } from "@preact/signals-react"; import { firebaseRC, updateFirebaseRCProject } from "./config"; @@ -9,7 +9,7 @@ import { pluginLogger } from "../logger-wrapper"; import { globalSignal } from "../utils/globals"; import { firstWhereDefined } from "../utils/signal"; import { User } from "../types/auth"; -import { DATA_CONNECT_EVENT_NAME } from "../analytics"; +import { DATA_CONNECT_EVENT_NAME, AnalyticsLogger } from "../analytics"; /** Available projects */ export const projects = globalSignal>( {}, @@ -30,7 +30,7 @@ const userScopedProjects = computed( export function registerProject( broker: ExtensionBrokerImpl, - telemetryLogger: TelemetryLogger, + analyticsLogger: AnalyticsLogger, ): Disposable { // For testing purposes. const demoProjectCommand = vscode.commands.registerCommand( @@ -103,7 +103,7 @@ export function registerProject( return; } else { try { - telemetryLogger.logUsage( + analyticsLogger.logger.logUsage( DATA_CONNECT_EVENT_NAME.PROJECT_SELECT_CLICKED, ); @@ -120,7 +120,9 @@ export function registerProject( } : undefined, }); - telemetryLogger.logUsage(DATA_CONNECT_EVENT_NAME.PROJECT_SELECTED); + analyticsLogger.logger.logUsage( + DATA_CONNECT_EVENT_NAME.PROJECT_SELECTED, + ); } catch (e: any) { vscode.window.showErrorMessage(e.message); } diff --git a/firebase-vscode/src/core/user.ts b/firebase-vscode/src/core/user.ts index 139bdbc9329..8485cead304 100644 --- a/firebase-vscode/src/core/user.ts +++ b/firebase-vscode/src/core/user.ts @@ -1,11 +1,11 @@ import { Signal, computed, effect } from "@preact/signals-react"; -import { Disposable, TelemetryLogger } from "vscode"; +import { Disposable } from "vscode"; import { ServiceAccountUser } from "../types"; import { User as AuthUser } from "../../../src/types/auth"; import { ExtensionBrokerImpl } from "../extension-broker"; import { login, logoutUser, requireAuthWrapper } from "../cli"; import { globalSignal } from "../utils/globals"; -import { DATA_CONNECT_EVENT_NAME } from "../analytics"; +import { DATA_CONNECT_EVENT_NAME, AnalyticsLogger } from "../analytics"; import * as vscode from "vscode"; type User = ServiceAccountUser | AuthUser; @@ -24,7 +24,7 @@ export async function checkLogin() { export function registerUser( broker: ExtensionBrokerImpl, - telemetryLogger: TelemetryLogger, + analyticsLogger: AnalyticsLogger, ): Disposable { // For testing purposes. const userMockCommand = vscode.commands.registerCommand( @@ -58,7 +58,7 @@ export function registerUser( }); const addUserSub = broker.on("addUser", async () => { - telemetryLogger.logUsage(DATA_CONNECT_EVENT_NAME.LOGIN); + analyticsLogger.logger.logUsage(DATA_CONNECT_EVENT_NAME.LOGIN); const { user } = await login(); currentUser.value = user; }); diff --git a/firebase-vscode/src/data-connect/ad-hoc-mutations.ts b/firebase-vscode/src/data-connect/ad-hoc-mutations.ts index a5de72d1e69..eaa3f3157b0 100644 --- a/firebase-vscode/src/data-connect/ad-hoc-mutations.ts +++ b/firebase-vscode/src/data-connect/ad-hoc-mutations.ts @@ -1,4 +1,4 @@ -import vscode, { Disposable, TelemetryLogger } from "vscode"; +import vscode, { Disposable } from "vscode"; import { DocumentNode, GraphQLInputField, @@ -18,10 +18,11 @@ import { DataConnectService } from "./service"; import { DATA_CONNECT_EVENT_NAME } from "../analytics"; import { dataConnectConfigs } from "./config"; import { firstWhereDefined } from "../utils/signal"; +import {AnalyticsLogger} from "../analytics"; export function registerAdHoc( dataConnectService: DataConnectService, - telemetryLogger: TelemetryLogger, + analyticsLogger: AnalyticsLogger, ): Disposable { const defaultScalarValues = { Any: "{}", @@ -262,14 +263,14 @@ query { vscode.commands.registerCommand( "firebase.dataConnect.schemaAddData", (ast, uri) => { - telemetryLogger.logUsage(DATA_CONNECT_EVENT_NAME.ADD_DATA); + analyticsLogger.logger.logUsage(DATA_CONNECT_EVENT_NAME.ADD_DATA); schemaAddData(ast, uri); }, ), vscode.commands.registerCommand( "firebase.dataConnect.schemaReadData", (document, ast, uri) => { - telemetryLogger.logUsage(DATA_CONNECT_EVENT_NAME.READ_DATA); + analyticsLogger.logger.logUsage(DATA_CONNECT_EVENT_NAME.READ_DATA); schemaReadData(document, ast, uri); }, ), diff --git a/firebase-vscode/src/data-connect/connectors.ts b/firebase-vscode/src/data-connect/connectors.ts index 7de76c517b6..be4d3f5b816 100644 --- a/firebase-vscode/src/data-connect/connectors.ts +++ b/firebase-vscode/src/data-connect/connectors.ts @@ -3,7 +3,6 @@ import vscode, { ExtensionContext, InputBoxValidationMessage, InputBoxValidationSeverity, - TelemetryLogger, } from "vscode"; import { ExtensionBrokerImpl } from "../extension-broker"; import { @@ -39,13 +38,13 @@ import { DataConnectService } from "./service"; import { OperationLocation } from "./types"; import { checkIfFileExists } from "./file-utils"; import * as path from "path"; -import { DATA_CONNECT_EVENT_NAME } from "../analytics"; +import { DATA_CONNECT_EVENT_NAME, AnalyticsLogger } from "../analytics"; export function registerConnectors( context: ExtensionContext, broker: ExtensionBrokerImpl, dataConnectService: DataConnectService, - telemetryLogger: TelemetryLogger, + analyticsLogger: AnalyticsLogger, ): Disposable { async function moveOperationToConnector( defIndex: number, // The index of the definition to move. @@ -477,7 +476,9 @@ export function registerConnectors( vscode.commands.registerCommand( "firebase.dataConnect.moveOperationToConnector", (number, location, connectorPath) => { - telemetryLogger.logUsage(DATA_CONNECT_EVENT_NAME.MOVE_TO_CONNECTOR); + analyticsLogger.logger.logUsage( + DATA_CONNECT_EVENT_NAME.MOVE_TO_CONNECTOR, + ); moveOperationToConnector(number, location, connectorPath); }, ), diff --git a/firebase-vscode/src/data-connect/deploy.ts b/firebase-vscode/src/data-connect/deploy.ts index a199b723ffd..ba43d285a9d 100644 --- a/firebase-vscode/src/data-connect/deploy.ts +++ b/firebase-vscode/src/data-connect/deploy.ts @@ -5,7 +5,7 @@ import { dataConnectConfigs } from "./config"; import { createE2eMockable } from "../utils/test_hooks"; import { runCommand } from "./terminal"; import { ExtensionBrokerImpl } from "../extension-broker"; -import { DATA_CONNECT_EVENT_NAME } from "../analytics"; +import { DATA_CONNECT_EVENT_NAME, AnalyticsLogger } from "../analytics"; import { getSettings } from "../utils/settings"; function createDeployOnlyCommand(serviceConnectorMap: { @@ -28,7 +28,7 @@ function createDeployOnlyCommand(serviceConnectorMap: { export function registerFdcDeploy( broker: ExtensionBrokerImpl, - telemetryLogger: vscode.TelemetryLogger, + analyticsLogger: AnalyticsLogger, ): vscode.Disposable { const settings = getSettings(); @@ -42,14 +42,14 @@ export function registerFdcDeploy( ); const deployAllCmd = vscode.commands.registerCommand("fdc.deploy-all", () => { - telemetryLogger.logUsage(DATA_CONNECT_EVENT_NAME.DEPLOY_ALL, { + analyticsLogger.logger.logUsage(DATA_CONNECT_EVENT_NAME.DEPLOY_ALL, { firebase_binary_kind: settings.firebaseBinaryKind, }); deploySpy.call(`${settings.firebasePath} deploy --only dataconnect`); }); const deployCmd = vscode.commands.registerCommand("fdc.deploy", async () => { - telemetryLogger.logUsage(DATA_CONNECT_EVENT_NAME.DEPLOY_INDIVIDUAL, { + analyticsLogger.logger.logUsage(DATA_CONNECT_EVENT_NAME.DEPLOY_INDIVIDUAL, { firebase_binary_kind: settings.firebaseBinaryKind, }); const configs = await firstWhereDefined(dataConnectConfigs).then( diff --git a/firebase-vscode/src/data-connect/execution.ts b/firebase-vscode/src/data-connect/execution.ts index 1a2da686552..25d2282d1b2 100644 --- a/firebase-vscode/src/data-connect/execution.ts +++ b/firebase-vscode/src/data-connect/execution.ts @@ -2,7 +2,6 @@ import vscode, { ConfigurationTarget, Disposable, ExtensionContext, - TelemetryLogger, } from "vscode"; import { ExtensionBrokerImpl } from "../extension-broker"; import { registerWebview } from "../webview"; @@ -23,13 +22,13 @@ import { DataConnectService } from "./service"; import { DataConnectError, toSerializedError } from "../../common/error"; import { OperationLocation } from "./types"; import { InstanceType } from "./code-lens-provider"; -import { DATA_CONNECT_EVENT_NAME } from "../analytics"; +import { DATA_CONNECT_EVENT_NAME, AnalyticsLogger } from "../analytics"; export function registerExecution( context: ExtensionContext, broker: ExtensionBrokerImpl, dataConnectService: DataConnectService, - telemetryLogger: TelemetryLogger, + analyticsLogger: AnalyticsLogger, ): Disposable { const treeDataProvider = new ExecutionHistoryTreeDataProvider(); const executionHistoryTreeView = vscode.window.createTreeView( @@ -190,7 +189,7 @@ export function registerExecution( vscode.commands.registerCommand( "firebase.dataConnect.executeOperation", (ast, location, instanceType: InstanceType) => { - telemetryLogger.logUsage( + analyticsLogger.logger.logUsage( instanceType === InstanceType.LOCAL ? DATA_CONNECT_EVENT_NAME.RUN_LOCAL : DATA_CONNECT_EVENT_NAME.RUN_PROD, diff --git a/firebase-vscode/src/data-connect/index.ts b/firebase-vscode/src/data-connect/index.ts index d7a155d5204..ba2f75c227c 100644 --- a/firebase-vscode/src/data-connect/index.ts +++ b/firebase-vscode/src/data-connect/index.ts @@ -28,10 +28,10 @@ import { Result } from "../result"; import { LanguageClient } from "vscode-languageclient/node"; import { registerTerminalTasks } from "./terminal"; import { registerWebview } from "../webview"; - import { DataConnectToolkit } from "./toolkit"; import { registerFdcSdkGeneration } from "./sdk-generation"; import { registerDiagnostics } from "./diagnostics"; +import { AnalyticsLogger } from "../analytics"; class CodeActionsProvider implements vscode.CodeActionProvider { constructor( @@ -133,7 +133,7 @@ export function registerFdc( broker: ExtensionBrokerImpl, authService: AuthService, emulatorController: EmulatorsController, - telemetryLogger: TelemetryLogger, + analyticsLogger: AnalyticsLogger, ): Disposable { registerDiagnostics(context, dataConnectConfigs); const dataConnectToolkit = new DataConnectToolkit(broker); @@ -222,14 +222,14 @@ export function registerFdc( selectedProjectStatus.show(); }), }, - registerExecution(context, broker, fdcService, telemetryLogger), + registerExecution(context, broker, fdcService, analyticsLogger), registerExplorer(context, broker, fdcService), registerWebview({ name: "data-connect", context, broker }), - registerAdHoc(fdcService, telemetryLogger), - registerConnectors(context, broker, fdcService, telemetryLogger), - registerFdcDeploy(broker, telemetryLogger), - registerFdcSdkGeneration(broker, telemetryLogger), - registerTerminalTasks(broker, telemetryLogger), + registerAdHoc(fdcService, analyticsLogger), + registerConnectors(context, broker, fdcService, analyticsLogger), + registerFdcDeploy(broker, analyticsLogger), + registerFdcSdkGeneration(broker, analyticsLogger), + registerTerminalTasks(broker, analyticsLogger), operationCodeLensProvider, vscode.languages.registerCodeLensProvider( // **Hack**: For testing purposes, enable code lenses on all graphql files diff --git a/firebase-vscode/src/data-connect/sdk-generation.ts b/firebase-vscode/src/data-connect/sdk-generation.ts index 4c1ec5ba69c..137671473d6 100644 --- a/firebase-vscode/src/data-connect/sdk-generation.ts +++ b/firebase-vscode/src/data-connect/sdk-generation.ts @@ -16,10 +16,11 @@ import { generateSdkYaml, } from "../../../src/init/features/dataconnect/sdk"; import { createE2eMockable } from "../utils/test_hooks"; +import { AnalyticsLogger} from "../analytics"; export function registerFdcSdkGeneration( broker: ExtensionBrokerImpl, - telemetryLogger: vscode.TelemetryLogger, + analyticsLogger: AnalyticsLogger, ): vscode.Disposable { const settings = getSettings(); @@ -37,7 +38,7 @@ export function registerFdcSdkGeneration( const initSdkCmd = vscode.commands.registerCommand( "fdc.init-sdk", (args: { appFolder: string }) => { - telemetryLogger.logUsage(DATA_CONNECT_EVENT_NAME.INIT_SDK_CLI, { + analyticsLogger.logger.logUsage(DATA_CONNECT_EVENT_NAME.INIT_SDK_CLI, { firebase_binary_kind: settings.firebaseBinaryKind, }); // Lets do it from the right directory @@ -50,7 +51,9 @@ export function registerFdcSdkGeneration( const configureSDKCodelense = vscode.commands.registerCommand( "fdc.connector.configure-sdk", async (connectorConfig) => { - telemetryLogger.logUsage(DATA_CONNECT_EVENT_NAME.INIT_SDK_CODELENSE); + analyticsLogger.logger.logUsage( + DATA_CONNECT_EVENT_NAME.INIT_SDK_CODELENSE, + ); const configs = await firstWhereDefined(dataConnectConfigs).then( (c) => c.requireValue, ); @@ -62,7 +65,7 @@ export function registerFdcSdkGeneration( const configureSDK = vscode.commands.registerCommand( "fdc.configure-sdk", async () => { - telemetryLogger.logUsage(DATA_CONNECT_EVENT_NAME.INIT_SDK); + analyticsLogger.logger.logUsage(DATA_CONNECT_EVENT_NAME.INIT_SDK); const configs = await firstWhereDefined(dataConnectConfigs).then( (c) => c.requireValue, ); diff --git a/firebase-vscode/src/data-connect/terminal.ts b/firebase-vscode/src/data-connect/terminal.ts index 0de7382da8a..9c836760cc0 100644 --- a/firebase-vscode/src/data-connect/terminal.ts +++ b/firebase-vscode/src/data-connect/terminal.ts @@ -2,7 +2,7 @@ import { TelemetryLogger, TerminalOptions } from "vscode"; import { ExtensionBrokerImpl } from "../extension-broker"; import vscode, { Disposable } from "vscode"; import { checkLogin } from "../core/user"; -import { DATA_CONNECT_EVENT_NAME } from "../analytics"; +import { DATA_CONNECT_EVENT_NAME, AnalyticsLogger } from "../analytics"; import { getSettings } from "../utils/settings"; import { currentProjectId } from "../core/project"; @@ -69,12 +69,12 @@ export function runTerminalTask( export function registerTerminalTasks( broker: ExtensionBrokerImpl, - telemetryLogger: TelemetryLogger, + analyticsLogger: AnalyticsLogger, ): Disposable { const settings = getSettings(); const loginTaskBroker = broker.on("executeLogin", () => { - telemetryLogger.logUsage(DATA_CONNECT_EVENT_NAME.IDX_LOGIN, { + analyticsLogger.logger.logUsage(DATA_CONNECT_EVENT_NAME.IDX_LOGIN, { firebase_binary_kind: settings.firebaseBinaryKind, }); runTerminalTask( @@ -86,7 +86,7 @@ export function registerTerminalTasks( }); const startEmulatorsTaskBroker = broker.on("runStartEmulators", () => { - telemetryLogger.logUsage(DATA_CONNECT_EVENT_NAME.START_EMULATORS, { + analyticsLogger.logger.logUsage(DATA_CONNECT_EVENT_NAME.START_EMULATORS, { firebase_binary_kind: settings.firebaseBinaryKind, }); // TODO: optional debug mode @@ -104,9 +104,12 @@ export function registerTerminalTasks( vscode.commands.registerCommand( "firebase.dataConnect.runTerminalTask", (taskName, command) => { - telemetryLogger.logUsage(DATA_CONNECT_EVENT_NAME.COMMAND_EXECUTION, { - commandName: command, - }); + analyticsLogger.logger.logUsage( + DATA_CONNECT_EVENT_NAME.COMMAND_EXECUTION, + { + commandName: command, + }, + ); runTerminalTask(taskName, command); }, ), diff --git a/firebase-vscode/src/extension.ts b/firebase-vscode/src/extension.ts index 8644eddbac0..d74ef6f6831 100644 --- a/firebase-vscode/src/extension.ts +++ b/firebase-vscode/src/extension.ts @@ -20,7 +20,6 @@ import { registerFdc } from "./data-connect"; import { AuthService } from "./auth/service"; import { AnalyticsLogger, - DATA_CONNECT_EVENT_NAME, IDX_METRIC_NOTICE, } from "./analytics"; import { env } from "./core/env"; @@ -29,12 +28,12 @@ import { suggestGraphqlSyntaxExtension } from "./data-connect/graphql-syntax-hig // This method is called when your extension is activated export async function activate(context: vscode.ExtensionContext) { - const analyticsLogger = new AnalyticsLogger(); + const analyticsLogger = new AnalyticsLogger(context); // Suggest installing the GraphQL syntax highlighter extension await suggestGraphqlSyntaxExtension(); - await setupFirebasePath(analyticsLogger.logger); + await setupFirebasePath(analyticsLogger); const settings = getSettings(); logSetup(); pluginLogger.debug("Activating Firebase extension."); @@ -60,7 +59,7 @@ export async function activate(context: vscode.ExtensionContext) { const [emulatorsController, coreDisposable] = await registerCore( broker, context, - analyticsLogger.logger, + analyticsLogger, ); context.subscriptions.push( @@ -78,7 +77,7 @@ export async function activate(context: vscode.ExtensionContext) { broker, authService, emulatorsController, - analyticsLogger.logger, + analyticsLogger, ), ); } diff --git a/firebase-vscode/src/utils/settings.ts b/firebase-vscode/src/utils/settings.ts index 3f9d3ac1b44..a1ba94a1568 100644 --- a/firebase-vscode/src/utils/settings.ts +++ b/firebase-vscode/src/utils/settings.ts @@ -1,6 +1,5 @@ -import * as vscode from "vscode"; -import { ConfigurationTarget, window, workspace } from "vscode"; -import { DATA_CONNECT_EVENT_NAME } from "../analytics"; +import { ConfigurationTarget, workspace } from "vscode"; +import { DATA_CONNECT_EVENT_NAME, AnalyticsLogger } from "../analytics"; export interface Settings { readonly firebasePath: string; @@ -47,7 +46,7 @@ export function updateIdxSetting(shouldShow: boolean) { } // Persist env var as path setting when path setting doesn't exist -export function setupFirebasePath(telemetryLogger: vscode.TelemetryLogger) { +export function setupFirebasePath(analyticsLogger: AnalyticsLogger) { const config = workspace.getConfiguration("firebase"); if (process.env.FIREBASE_BINARY && !config.get("firebasePath")) { config.update( @@ -56,5 +55,5 @@ export function setupFirebasePath(telemetryLogger: vscode.TelemetryLogger) { ConfigurationTarget.Global, ); } - telemetryLogger.logUsage(DATA_CONNECT_EVENT_NAME.SETUP_FIREBASE_BINARY); + analyticsLogger.logger.logUsage(DATA_CONNECT_EVENT_NAME.SETUP_FIREBASE_BINARY); } diff --git a/package.json b/package.json index f21c913cba3..e87e0efa6f5 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "test:client-integration": "bash ./scripts/client-integration-tests/run.sh", "test:compile": "tsc --project tsconfig.compile.json", "test:dataconnect-deploy": "bash ./scripts/dataconnect-test/run.sh", + "test:dataconnect-emulator": "bash ./scripts/dataconnect-emulator-tests/run.sh", "test:all-emulators": "npm run test:emulator && npm run test:extensions-emulator && npm run test:import-export && npm run test:storage-emulator-integration", "test:emulator": "bash ./scripts/emulator-tests/run.sh", "test:extensions-deploy": "bash ./scripts/extensions-deploy-tests/run.sh", diff --git a/scripts/clean-install.sh b/scripts/clean-install.sh index ce66373508f..dcc9f2a5fa3 100755 --- a/scripts/clean-install.sh +++ b/scripts/clean-install.sh @@ -1,5 +1,5 @@ #!/bin/bash -set -e +set -ex function cleanup() { echo "Cleaning up artifacts..." diff --git a/scripts/dataconnect-emulator-tests/fdc-test/connector/connector.yaml b/scripts/dataconnect-emulator-tests/fdc-test/connector/connector.yaml new file mode 100644 index 00000000000..68215053ca4 --- /dev/null +++ b/scripts/dataconnect-emulator-tests/fdc-test/connector/connector.yaml @@ -0,0 +1,2 @@ +connectorId: "connectorId" +authMode: "PUBLIC" diff --git a/scripts/dataconnect-emulator-tests/fdc-test/connector/queries.gql b/scripts/dataconnect-emulator-tests/fdc-test/connector/queries.gql new file mode 100644 index 00000000000..51e05570ca5 --- /dev/null +++ b/scripts/dataconnect-emulator-tests/fdc-test/connector/queries.gql @@ -0,0 +1,3 @@ +mutation createOrder($name: String!) { + order_insert(data : {name: $name}) +} diff --git a/scripts/dataconnect-emulator-tests/fdc-test/dataconnect.yaml b/scripts/dataconnect-emulator-tests/fdc-test/dataconnect.yaml new file mode 100644 index 00000000000..6a2d2fe1c2a --- /dev/null +++ b/scripts/dataconnect-emulator-tests/fdc-test/dataconnect.yaml @@ -0,0 +1,11 @@ +specVersion: "v1beta" +serviceId: "fake-service" +location: "us-central1" +schema: + source: "./schema" + datasource: + postgresql: + database: "postgres" + cloudSql: + instanceId: "dataconnect-test" +connectorDirs: ["./connector"] diff --git a/scripts/dataconnect-emulator-tests/fdc-test/schema/schema.gql b/scripts/dataconnect-emulator-tests/fdc-test/schema/schema.gql new file mode 100644 index 00000000000..b6ea799498c --- /dev/null +++ b/scripts/dataconnect-emulator-tests/fdc-test/schema/schema.gql @@ -0,0 +1,3 @@ +type Order @table { + name: String! +} diff --git a/scripts/dataconnect-emulator-tests/firebase.json b/scripts/dataconnect-emulator-tests/firebase.json new file mode 100644 index 00000000000..bb70b69cbe2 --- /dev/null +++ b/scripts/dataconnect-emulator-tests/firebase.json @@ -0,0 +1,5 @@ +{ + "dataconnect": { + "source": "fdc-test" + } +} diff --git a/scripts/dataconnect-emulator-tests/run.sh b/scripts/dataconnect-emulator-tests/run.sh new file mode 100644 index 00000000000..fcb35afa786 --- /dev/null +++ b/scripts/dataconnect-emulator-tests/run.sh @@ -0,0 +1,14 @@ +#!/bin/bash +set -ex # Immediately exit on failure +# Globally link the CLI for the testing framework +./scripts/clean-install.sh +source scripts/set-default-credentials.sh + +echo "Running in ${CWD}" +echo "Running with node: $(which node)" +echo "Running with npm: $(which npm)" +echo "Running with Application Creds: ${GOOGLE_APPLICATION_CREDENTIALS}" + +cd scripts/dataconnect-emulator-tests +firebase emulators:exec "cd ." --only dataconnect -P demo-test +# rm -rf ../../clean diff --git a/src/emulator/controller.ts b/src/emulator/controller.ts index 5668381c7b8..a6d574a754f 100755 --- a/src/emulator/controller.ts +++ b/src/emulator/controller.ts @@ -28,7 +28,7 @@ import { LoggingEmulator } from "./loggingEmulator"; import * as dbRulesConfig from "../database/rulesConfig"; import { EmulatorLogger, Verbosity } from "./emulatorLogger"; import { EmulatorHubClient } from "./hubClient"; -import { confirm } from "../prompt"; +import { confirm, promptOnce } from "../prompt"; import { FLAG_EXPORT_ON_EXIT_NAME, JAVA_DEPRECATION_WARNING, @@ -893,6 +893,26 @@ export async function startAll( importDirAbsPath, exportMetadata.dataconnect.path, ); + const dataDirectory = options.config.get("emulators.dataconnect.dataDir"); + if (exportMetadataFilePath && dataDirectory) { + EmulatorLogger.forEmulator(Emulators.DATACONNECT).logLabeled( + "WARN", + "dataconnect", + "'firebase.json#emulators.dataconnect.dataDir' is set and `--import` flag was passed. " + + "This will overwrite any data saved from previous runs.", + ); + if ( + !options.nonInteractive && + !(await promptOnce({ + type: "confirm", + message: `Do you wish to continue and overwrite data in ${dataDirectory}?`, + default: false, + })) + ) { + await cleanShutdown(); + return { deprecationNotices: [] }; + } + } EmulatorLogger.forEmulator(Emulators.DATACONNECT).logLabeled( "BULLET", diff --git a/src/emulator/dataconnect/pgliteServer.ts b/src/emulator/dataconnect/pgliteServer.ts index 3550ff859ad..1aee1c2272c 100644 --- a/src/emulator/dataconnect/pgliteServer.ts +++ b/src/emulator/dataconnect/pgliteServer.ts @@ -17,6 +17,7 @@ import { } from "./pg-gateway/index"; import { fromNodeSocket } from "./pg-gateway/platforms/node"; import { logger } from "../../logger"; +import { hasMessage } from "../../error"; export const TRUNCATE_TABLES_SQL = ` DO $do$ BEGIN @@ -98,7 +99,7 @@ export class PostgresServer { const file = new File([rf], this.importPath); pgliteArgs.loadDataDir = file; } - this.db = await PGlite.create(pgliteArgs); + this.db = await this.forceCreateDB(pgliteArgs); await this.db.waitReady; } return this.db; @@ -116,6 +117,21 @@ export class PostgresServer { fs.writeFileSync(exportPath, new Uint8Array(arrayBuff)); } + async forceCreateDB(pgliteArgs: PGliteOptions): Promise { + try { + const db = await PGlite.create(pgliteArgs); + return db; + } catch (err: unknown) { + if (pgliteArgs.dataDir && hasMessage(err) && /Database already exists/.test(err.message)) { + // Clear out the current pglite data + fs.rmSync(pgliteArgs.dataDir, { force: true, recursive: true }); + const db = await PGlite.create(pgliteArgs); + return db; + } + throw err; + } + } + constructor(database: string, username: string, dataDirectory?: string, importPath?: string) { this.username = username; this.database = database; diff --git a/src/emulator/downloadableEmulators.ts b/src/emulator/downloadableEmulators.ts index 8df3b721bfd..51431926b66 100755 --- a/src/emulator/downloadableEmulators.ts +++ b/src/emulator/downloadableEmulators.ts @@ -9,7 +9,7 @@ import { } from "./types"; import { Constants } from "./constants"; -import { FirebaseError } from "../error"; +import { FirebaseError, hasMessage } from "../error"; import * as childProcess from "child_process"; import * as utils from "../utils"; import { EmulatorLogger } from "./emulatorLogger"; @@ -655,7 +655,6 @@ export async function start( } export function isIncomaptibleArchError(err: unknown): boolean { - const hasMessage = (e: any): e is { message: string } => !!e?.message; return ( hasMessage(err) && /Unknown system error/.test(err.message ?? "") && diff --git a/src/error.ts b/src/error.ts index 93a5ac62f03..e85ddeaef71 100644 --- a/src/error.ts +++ b/src/error.ts @@ -121,3 +121,8 @@ export function isBillingError(e: { ); }); } + +/** + * Checks whether an unknown object (such as an error) has a message field + */ +export const hasMessage = (e: any): e is { message: string } => !!e?.message;