New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement Experiments feature #4994
Changes from all commits
269cf66
edc5fb7
fbc4800
7fb879d
a5cc083
e51cc64
8de1c7f
e7445c0
7038e3c
aa77661
363dc93
f19c209
a069947
4984825
8a461f8
7808be8
ede6af4
05ac750
b0dfaa8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
- Add the "experiments" family of commands (#4994) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { bold } from "colorette"; | ||
|
||
import { Command } from "../command"; | ||
import { FirebaseError } from "../error"; | ||
import * as experiments from "../experiments"; | ||
import { logger } from "../logger"; | ||
import { last } from "../utils"; | ||
|
||
export const command = new Command("experiments:describe <experiment>") | ||
.description("enable an experiment on this machine") | ||
.action((experiment: string) => { | ||
if (!experiments.isValidExperiment(experiment)) { | ||
let message = `Cannot find experiment ${bold(experiment)}`; | ||
const potentials = experiments.experimentNameAutocorrect(experiment); | ||
if (potentials.length === 1) { | ||
message = `${message}\nDid you mean ${potentials[0]}?`; | ||
} else if (potentials.length) { | ||
message = `${message}\nDid you mean ${potentials.slice(0, -1).join(",")} or ${last( | ||
potentials | ||
)}?`; | ||
} | ||
throw new FirebaseError(message); | ||
} | ||
|
||
const spec = experiments.ALL_EXPERIMENTS[experiment]; | ||
logger.info(`${bold("Name")}: ${experiment}`); | ||
logger.info(`${bold("Enabled")}: ${experiments.isEnabled(experiment) ? "yes" : "no"}`); | ||
if (spec.docsUri) { | ||
logger.info(`${bold("Documentation")}: ${spec.docsUri}`); | ||
} | ||
logger.info(`${bold("Description")}: ${spec.fullDescription || spec.shortDescription}`); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { bold } from "colorette"; | ||
|
||
import { Command } from "../command"; | ||
import { FirebaseError } from "../error"; | ||
import * as experiments from "../experiments"; | ||
import { logger } from "../logger"; | ||
import { last } from "../utils"; | ||
|
||
export const command = new Command("experiments:disable <experiment>") | ||
.description("disable an experiment on this machine") | ||
.action((experiment: string) => { | ||
if (experiments.isValidExperiment(experiment)) { | ||
experiments.setEnabled(experiment, false); | ||
experiments.flushToDisk(); | ||
logger.info(`Disabled experiment ${bold(experiment)}`); | ||
return; | ||
} | ||
|
||
let message = `Cannot find experiment ${bold(experiment)}`; | ||
const potentials = experiments.experimentNameAutocorrect(experiment); | ||
if (potentials.length === 1) { | ||
message = `${message}\nDid you mean ${potentials[0]}?`; | ||
} else if (potentials.length) { | ||
message = `${message}\nDid you mean ${potentials.slice(0, -1).join(",")} or ${last( | ||
potentials | ||
)}?`; | ||
} | ||
throw new FirebaseError(message); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { bold } from "colorette"; | ||
|
||
import { Command } from "../command"; | ||
import { FirebaseError } from "../error"; | ||
import * as experiments from "../experiments"; | ||
import { logger } from "../logger"; | ||
import { last } from "../utils"; | ||
|
||
export const command = new Command("experiments:enable <experiment>") | ||
.description("enable an experiment on this machine") | ||
.action((experiment: string) => { | ||
if (experiments.isValidExperiment(experiment)) { | ||
experiments.setEnabled(experiment, true); | ||
experiments.flushToDisk(); | ||
logger.info(`Enabled experiment ${bold(experiment)}`); | ||
return; | ||
} | ||
|
||
let message = `Cannot find experiment ${bold(experiment)}`; | ||
const potentials = experiments.experimentNameAutocorrect(experiment); | ||
if (potentials.length === 1) { | ||
message = `${message}\nDid you mean ${potentials[0]}?`; | ||
} else if (potentials.length) { | ||
message = `${message}\nDid you mean ${potentials.slice(0, -1).join(",")} or ${last( | ||
potentials | ||
)}?`; | ||
} | ||
throw new FirebaseError(message); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { Command } from "../command"; | ||
import Table = require("cli-table"); | ||
import * as experiments from "../experiments"; | ||
import { partition } from "../functional"; | ||
import { logger } from "../logger"; | ||
|
||
export const command = new Command("experiments:list").action(() => { | ||
const table = new Table({ | ||
head: ["Enabled", "Name", "Description"], | ||
style: { head: ["yellow"] }, | ||
}); | ||
const [enabled, disabled] = partition(Object.entries(experiments.ALL_EXPERIMENTS), ([name]) => { | ||
return experiments.isEnabled(name as experiments.ExperimentName); | ||
}); | ||
for (const [name, exp] of enabled) { | ||
table.push(["y", name, exp.shortDescription]); | ||
} | ||
for (const [name, exp] of disabled) { | ||
if (!exp.public) { | ||
continue; | ||
} | ||
table.push(["n", name, exp.shortDescription]); | ||
} | ||
logger.info(table.toString()); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,7 @@ import * as backend from "./backend"; | |
import * as proto from "../../gcp/proto"; | ||
import * as api from "../../.../../api"; | ||
import * as params from "./params"; | ||
import { previews } from "../../previews"; | ||
import * as experiments from "../../experiments"; | ||
import { FirebaseError } from "../../error"; | ||
import { assertExhaustive, mapObject, nullsafeVisitor } from "../../functional"; | ||
import { UserEnvsOpts, writeUserEnvs } from "../../functions/env"; | ||
|
@@ -281,7 +281,7 @@ export async function resolveBackend( | |
nonInteractive?: boolean | ||
): Promise<{ backend: backend.Backend; envs: Record<string, params.ParamValue> }> { | ||
let paramValues: Record<string, params.ParamValue> = {}; | ||
if (previews.functionsparams) { | ||
if (experiments.isEnabled("functionsparams")) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Berlioz Does this still need to be hidden behind an experiment or do we think we are stable enough to roll forward? (If not, can we leave a comment on when this should be rolled forward?) |
||
paramValues = await params.resolveParams( | ||
build.params, | ||
firebaseConfig, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,7 +29,7 @@ import { FirebaseError } from "../../error"; | |
import { configForCodebase, normalizeAndValidate } from "../../functions/projectConfig"; | ||
import { AUTH_BLOCKING_EVENTS } from "../../functions/events/v1"; | ||
import { generateServiceIdentity } from "../../gcp/serviceusage"; | ||
import { previews } from "../../previews"; | ||
import * as experiments from "../../experiments"; | ||
import { applyBackendHashToBackends } from "./cache/applyHash"; | ||
import { allEndpoints, Backend } from "./backend"; | ||
|
||
|
@@ -272,7 +272,7 @@ export async function prepare( | |
* This must be called after `await validate.secretsAreValid`. | ||
*/ | ||
updateEndpointTargetedStatus(wantBackends, context.filters || []); | ||
if (previews.skipdeployingnoopfunctions) { | ||
if (experiments.isEnabled("skipdeployingnoopfunctions")) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @TheIronDev When does this experiment go away? |
||
applyBackendHashToBackends(wantBackends, context); | ||
} | ||
} | ||
|
@@ -346,6 +346,9 @@ function maybeCopyTriggerRegion(wantE: backend.Endpoint, haveE: backend.Endpoint | |
wantE.eventTrigger.region = haveE.eventTrigger.region; | ||
} | ||
|
||
/** | ||
* Determines whether endpoints are targeted by an --only flag. | ||
*/ | ||
export function updateEndpointTargetedStatus( | ||
wantBackends: Record<string, Backend>, | ||
endpointFilters: EndpointFilter[] | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One detail that can be punted on until later: for
--json
(machine readable) output, we could export the table as a JSON-ish object (just return an object in this method). Would that be easy enough to do?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I couldn't get this to work. You'll have to teach me how --json works with the commands library