lib/nimbusRecipeCollection.ts (80 lines of code) (raw):
import { NimbusRecipe } from "../lib/nimbusRecipe";
import { BranchInfo, RecipeInfo, RecipeOrBranchInfo } from "@/app/columns";
import { getCTRPercentData } from "./looker";
import { getExperimentLookerDashboardDate } from "./lookerUtils";
import { Platform } from "./types";
const nimbusExperimentV7Schema = require("@mozilla/nimbus-schemas/schemas/NimbusExperimentV7.schema.json");
type NimbusExperiment = typeof nimbusExperimentV7Schema.properties;
type NimbusRecipeCollectionType = {
recipes: Array<NimbusRecipe>;
isCompleted: boolean;
fetchRecipes: () => Promise<Array<NimbusRecipe>>;
platform: Platform;
};
/**
* @returns an array of BranchInfo with updated CTR percents for the recipe
*/
async function updateBranchesCTR(recipe: NimbusRecipe): Promise<BranchInfo[]> {
return await Promise.all(
recipe
.getBranchInfos()
.map(async (branchInfo: BranchInfo): Promise<BranchInfo> => {
if (branchInfo.nimbusExperiment.appName === "fenix") {
console.log(branchInfo.id + ": " + branchInfo.template);
}
const proposedEndDate = getExperimentLookerDashboardDate(
branchInfo.nimbusExperiment.startDate,
branchInfo.nimbusExperiment.proposedDuration,
);
// We are making all branch ids upper case to make up for
// Looker being case sensitive
const ctrPercentData = await getCTRPercentData(
branchInfo.id,
branchInfo.nimbusExperiment.appName,
branchInfo.template!,
undefined,
branchInfo.nimbusExperiment.slug,
branchInfo.slug,
branchInfo.nimbusExperiment.startDate,
proposedEndDate,
);
if (ctrPercentData) {
branchInfo.ctrPercent = ctrPercentData.ctrPercent;
branchInfo.impressions = ctrPercentData.impressions;
}
return branchInfo;
}),
);
}
export class NimbusRecipeCollection implements NimbusRecipeCollectionType {
recipes: Array<NimbusRecipe>;
isCompleted: boolean;
platform: Platform;
// XXX remove this default platform, it's a total footgun
constructor(
isCompleted: boolean = false,
platform: Platform = "firefox-desktop",
) {
this.recipes = [];
this.isCompleted = isCompleted;
this.platform = platform;
}
async fetchRecipes(): Promise<Array<NimbusRecipe>> {
// XXX should really be using URL.parse and URLSearchParams to manage all
// this stuff
let experimenterUrl = `${process.env.EXPERIMENTER_API_PREFIX}?status=Live&application=${this.platform}`;
if (this.isCompleted) {
// XXX rename isCompleted to isComplete for consistency
experimenterUrl = `${process.env.EXPERIMENTER_API_PREFIX}?status=Complete&application=${this.platform}`;
}
// console.log("experimenterURL = ", experimenterUrl)
const response = await fetch(experimenterUrl, {
credentials: "omit",
});
// console.log("response = ", response)
const experiments: NimbusExperiment[] = await response.json();
// console.log('returned experiments', experiments)
this.recipes = experiments.map(
(nimbusExp: NimbusExperiment) =>
new NimbusRecipe(nimbusExp, this.isCompleted),
);
return this.recipes;
}
/**
* @returns a list of RecipeInfo of recipes in this collection with updated
* ctrPercent properties
*/
async getExperimentAndBranchInfos(): Promise<RecipeOrBranchInfo[]> {
return await Promise.all(
this.recipes.map(async (recipe: NimbusRecipe): Promise<RecipeInfo> => {
let updatedRecipe = recipe.getRecipeInfo();
// Update all branches with CTR data for the recipe
updatedRecipe.branches = await updateBranchesCTR(recipe);
return updatedRecipe;
}),
);
}
}