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 15 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 |
---|---|---|
@@ -1,2 +1,3 @@ | ||
- Fix Storage Emulator crashing with NoClassDefFoundError in some cases (#3481). | ||
- Add the "experiments" family of commands (#4994) | ||
- Fixes `init functions` throwing error when detecting existing legacy singleton functions configuration (#5020). |
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:clear <experiment>") | ||
.description("clear preferences for an experiment on this machine") | ||
.action((experiment: string) => { | ||
if (experiments.isValidExperiment(experiment)) { | ||
experiments.setEnabled(experiment, null); | ||
experiments.flushToDisk(); | ||
logger.info(`Cleared preferences for 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 * 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)) { | ||
logger.error(`Cannot find experiment ${bold(experiment)}`); | ||
const potentials = experiments.experimentNameAutocorrect(experiment); | ||
if (potentials.length === 1) { | ||
logger.error(`Did you mean ${potentials[0]}?`); | ||
} else if (potentials.length) { | ||
logger.error(`Did you mean ${potentials.slice(0, -1).join(",")} or ${last(potentials)}?`); | ||
} | ||
return; | ||
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. Should this throw instead? 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. Done |
||
} | ||
|
||
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()); | ||
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. One detail that can be punted on until later: for 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. I couldn't get this to work. You'll have to teach me how --json works with the commands library |
||
}); |
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.
Should this throw instead?
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.
Actually, Michael asked us to remove this feature for now.