diff --git a/src/dataconnect/freeTrial.ts b/src/dataconnect/freeTrial.ts index 465c36fe912..5a2485c431f 100644 --- a/src/dataconnect/freeTrial.ts +++ b/src/dataconnect/freeTrial.ts @@ -1,7 +1,6 @@ import * as clc from "colorette"; import { queryTimeSeries, CmQuery } from "../gcp/cloudmonitoring"; -import * as utils from "../utils"; export function freeTrialTermsLink(): string { return "https://firebase.google.com/pricing"; @@ -28,19 +27,11 @@ export async function checkFreeTrialInstanceUsed(projectId: string): Promise { let sandbox: sinon.SinonSandbox; let loadAllStub: sinon.SinonStub; let buildStub: sinon.SinonStub; - let checkBillingEnabledStub: sinon.SinonStub; let getResourceFiltersStub: sinon.SinonStub; let diffSchemaStub: sinon.SinonStub; let setupCloudSqlStub: sinon.SinonStub; @@ -27,7 +26,6 @@ describe("dataconnect prepare", () => { sandbox = sinon.createSandbox(); loadAllStub = sandbox.stub(load, "loadAll").resolves([]); buildStub = sandbox.stub(build, "build").resolves({} as any); - checkBillingEnabledStub = sandbox.stub(cloudbilling, "checkBillingEnabled").resolves(true); sandbox.stub(ensureApis, "ensureApis").resolves(); sandbox.stub(requireTosAcceptance, "requireTosAcceptance").returns(() => Promise.resolve()); getResourceFiltersStub = sandbox.stub(filters, "getResourceFilters").returns(undefined); @@ -35,6 +33,7 @@ describe("dataconnect prepare", () => { setupCloudSqlStub = sandbox.stub(provisionCloudSql, "setupCloudSql").resolves(); sandbox.stub(projectUtils, "needProjectId").returns("test-project"); sandbox.stub(utils, "logLabeledBullet"); + sandbox.stub(cloudbilling, "checkBillingEnabled").resolves(); }); afterEach(() => { @@ -57,16 +56,6 @@ describe("dataconnect prepare", () => { }); }); - it("should throw an error if billing is not enabled", async () => { - checkBillingEnabledStub.resolves(false); - const context = {}; - const options = { config: {} } as any; - await expect(prepare.default(context, options)).to.be.rejectedWith( - FirebaseError, - "To provision a CloudSQL Postgres instance on the Firebase Data Connect no-cost trial", - ); - }); - it("should build services", async () => { const serviceInfos = [{ sourceDirectory: "a" }, { sourceDirectory: "b" }]; loadAllStub.resolves(serviceInfos as any); diff --git a/src/deploy/dataconnect/prepare.ts b/src/deploy/dataconnect/prepare.ts index c4c8aa74cfa..e9f2ba8c0cf 100644 --- a/src/deploy/dataconnect/prepare.ts +++ b/src/deploy/dataconnect/prepare.ts @@ -11,12 +11,11 @@ import { ensureApis } from "../../dataconnect/ensureApis"; import { requireTosAcceptance } from "../../requireTosAcceptance"; import { DATA_CONNECT_TOS_ID } from "../../gcp/firedata"; import { setupCloudSql } from "../../dataconnect/provisionCloudSql"; -import { checkBillingEnabled } from "../../gcp/cloudbilling"; import { parseServiceName } from "../../dataconnect/names"; import { FirebaseError } from "../../error"; import { mainSchema, requiresVector } from "../../dataconnect/types"; import { diffSchema } from "../../dataconnect/schemaMigration"; -import { upgradeInstructions } from "../../dataconnect/freeTrial"; +import { checkBillingEnabled } from "../../gcp/cloudbilling"; import { Context, initDeployStats } from "./context"; /** @@ -35,7 +34,6 @@ export default async function (context: Context, options: DeployOptions): Promis const { serviceInfos, filters, deployStats } = context.dataconnect; if (!(await checkBillingEnabled(projectId))) { deployStats.missingBilling = true; - throw new FirebaseError(upgradeInstructions(projectId)); } await requireTosAcceptance(DATA_CONNECT_TOS_ID)(options); for (const si of serviceInfos) { diff --git a/src/experiments.ts b/src/experiments.ts index 7ec9aa6975c..9d9871ba7f6 100644 --- a/src/experiments.ts +++ b/src/experiments.ts @@ -144,6 +144,11 @@ export const ALL_EXPERIMENTS = experiments({ default: false, public: true, }, + fdcift: { + shortDescription: "Enable instrumentless trial for Data Connect", + public: false, + default: false, + }, apptesting: { shortDescription: "Adds experimental App Testing feature", public: true, diff --git a/src/gcp/cloudsql/cloudsqladmin.spec.ts b/src/gcp/cloudsql/cloudsqladmin.spec.ts index 23ffa0fff7b..b56c112bd3c 100644 --- a/src/gcp/cloudsql/cloudsqladmin.spec.ts +++ b/src/gcp/cloudsql/cloudsqladmin.spec.ts @@ -149,7 +149,15 @@ describe("cloudsqladmin", () => { }); describe("createInstance", () => { - it("should create an instance", async () => { + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + nock.cleanAll(); + }); + it("should create an paid instance", async () => { nock(cloudSQLAdminOrigin()) .post(`/${API_VERSION}/projects/${PROJECT_ID}/instances`) .reply(200, {}); @@ -164,6 +172,22 @@ describe("cloudsqladmin", () => { expect(nock.isDone()).to.be.true; }); + + it("should create a free instance.", async () => { + nock(cloudSQLAdminOrigin()) + .post(`/${API_VERSION}/projects/${PROJECT_ID}/instances`) + .reply(200, {}); + + await sqladmin.createInstance({ + projectId: PROJECT_ID, + location: "us-central", + instanceId: INSTANCE_ID, + enableGoogleMlIntegration: false, + freeTrialLabel: "ft", + }); + + expect(nock.isDone()).to.be.true; + }); }); describe("updateInstanceForDataConnect", () => { diff --git a/src/gcp/cloudsql/cloudsqladmin.ts b/src/gcp/cloudsql/cloudsqladmin.ts index 39340255336..1984010f4dd 100755 --- a/src/gcp/cloudsql/cloudsqladmin.ts +++ b/src/gcp/cloudsql/cloudsqladmin.ts @@ -8,6 +8,7 @@ import { Options } from "../../options"; import { logger } from "../../logger"; import { testIamPermissions } from "../iam"; import { FirebaseError } from "../../error"; + const API_VERSION = "v1"; const client = new Client({ diff --git a/src/init/features/dataconnect/index.ts b/src/init/features/dataconnect/index.ts index 144c65d8440..c02ac99d43e 100644 --- a/src/init/features/dataconnect/index.ts +++ b/src/init/features/dataconnect/index.ts @@ -29,6 +29,8 @@ import { promiseWithSpinner, logLabeledError, newUniqueId, + logLabeledWarning, + logLabeledSuccess, } from "../../../utils"; import { isBillingEnabled } from "../../../gcp/cloudbilling"; import * as sdk from "./sdk"; @@ -40,6 +42,7 @@ import { } from "../../../gemini/fdcExperience"; import { configstore } from "../../../configstore"; import { trackGA4 } from "../../../track"; +import { isEnabled } from "../../../experiments"; // Default GCP region for Data Connect export const FDC_DEFAULT_REGION = "us-east4"; @@ -126,7 +129,6 @@ export async function askQuestions(setup: Setup): Promise { shouldProvisionCSQL: false, }; if (setup.projectId) { - const hasBilling = await isBillingEnabled(setup); await ensureApis(setup.projectId); await promptForExistingServices(setup, info); if (!info.serviceGql) { @@ -154,11 +156,7 @@ export async function askQuestions(setup: Setup): Promise { }); } } - if (hasBilling) { - await promptForCloudSQL(setup, info); - } else if (info.appDescription) { - await promptForLocation(setup, info); - } + await promptForCloudSQL(setup, info); } setup.featureInfo = setup.featureInfo || {}; setup.featureInfo.dataconnect = info; @@ -216,9 +214,6 @@ export async function actuate(setup: Setup, config: Config, options: any): Promi https://console.firebase.google.com/project/${setup.projectId!}/dataconnect/locations/${info.locationId}/services/${info.serviceId}/schema`, ); } - if (!(await isBillingEnabled(setup))) { - setup.instructions.push(upgradeInstructions(setup.projectId || "your-firebase-project")); - } setup.instructions.push( `Install the Data Connect VS Code Extensions. You can explore Data Connect Query on local pgLite and Cloud SQL Postgres Instance.`, ); @@ -238,9 +233,7 @@ async function actuateWithInfo( } await ensureApis(projectId, /* silent =*/ true); - const provisionCSQL = info.shouldProvisionCSQL && (await isBillingEnabled(setup)); - if (provisionCSQL) { - // Kicks off Cloud SQL provisioning if the project has billing enabled. + if (info.shouldProvisionCSQL) { await setupCloudSql({ projectId: projectId, location: info.locationId, @@ -296,7 +289,7 @@ async function actuateWithInfo( projectId, info, schemaFiles, - provisionCSQL, + info.shouldProvisionCSQL, ); await upsertSchema(saveSchemaGql); if (waitForCloudSQLProvision) { @@ -695,6 +688,31 @@ async function promptForCloudSQL(setup: Setup, info: RequiredInfo): Promise info.locationId === "" || info.locationId === c.location); if (choices.length) { - if (!(await checkFreeTrialInstanceUsed(setup.projectId))) { + if (freeTrialAvailable) { choices.push({ name: "Create a new free trial instance", value: "", location: "" }); } else { choices.push({ name: "Create a new CloudSQL instance", value: "", location: "" }); @@ -737,7 +755,7 @@ async function promptForCloudSQL(setup: Setup, info: RequiredInfo): Promise