From e4146bf1afa13836383705d44a6720087e6ab5be Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Thu, 13 Mar 2025 17:20:36 -0700 Subject: [PATCH 01/61] Initial TODO list --- TODO.md | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 TODO.md diff --git a/TODO.md b/TODO.md new file mode 100644 index 00000000..a12abd3c --- /dev/null +++ b/TODO.md @@ -0,0 +1,85 @@ +Goal: stand up mobile version of experiments/rollouts + +User Story: As a mobile PM, I should be able to understand what things are in experiments and + rollouts for messaging & onboarding & what the clickthrough rates look like + + +* research + * [surfaces & guidelines](https://mozilla-hub.atlassian.net/wiki/spaces/FIREFOX/pages/210206760/Mobile+Message+Surface+Guidelines) + * [mobile telemetry docs](https://experimenter.info/messaging/mobile-messaging/#events-emitted) + * [desktop explore](https://mozilla.cloud.looker.com/explore/user_journey/event_counts) + + 1. look at iOS telemetry & explores + * [iOS message probes](https://dictionary.telemetry.mozilla.org/apps/firefox_ios?page=1&search=messag) + * Note no experiments probes like Android has + * [iOS event count explore](https://mozilla.cloud.looker.com/explore/firefox_ios/event_counts?qid=OZqOXzZqTujARgvCK12NJ4) + * [recent iOS clicked events](https://mozilla.cloud.looker.com/explore/firefox_ios/event_counts?qid=jQpgYwZpBZEhW73B1dcyzu&toggle=fil,vis) + * XXX look at onboarding also + + 2. look at Android telemetry & explores + * [Android message probes - Glean dictionary](https://dictionary.telemetry.mozilla.org/apps/fenix?page=1&search=messaging) + * [android explore](https://mozilla.cloud.looker.com/explore/fenix/event_counts)$$ + * [recent android messaing click events with most extra keys and experiments](https://mozilla.cloud.looker.com/explore/fenix/event_counts?qid=u0OKWHjWgTcstNgbzvyyBc&toggle=fil) + * [recent android onboarding events](https://mozilla.cloud.looker.com/explore/fenix/event_counts?qid=n71HDr0LIxuNS3vGX9essN&toggle=fil) + * Need to understand this telemetry compared to JSON + * +* open questions + * what does telemetry look like for onboarding? other surfaces? similar to messaging? + * Does Click telemetry on both iOS and Android alwyas mean CTA? Or something else? + * What is action_uuid extra key (see docs)? + * XXX Do non-experimental message send pings on iOS? on Android? + * + + +1. Draft plan for Android page + 1. ?File ticket + 2. Build chart for Android messaging (DONE) + 3. Build 2nd chart (LATER) + 4. Build dashboard (DONE - id = 2191) + 5. Move to shared folder (LATER) + + XXX FINISH BUILDING todo list; XXX plan team work; XXX map to calendar + + 1. Build Android page + 1. Review existing clone for "completed" (DONE) + 2. ?Consider options for cloning, since we'll want Android completed page too, and iOS pages + 1. Clean way: + 3. Create new dir with new page.tsx (MUST) + 4. TDD Factor out dashboard (prob MUST) + 5. Factor "application=" out of env (MUST) + + 2. Add cases / refactor messageUtils.getDashboard (MUST) + 3. Add cases / refactor experimentUtils.ts (MUST) + 4. Update / move messageUtils.getDashboardIdForTemplate (MUST) + 5. Add cases / refactor nimbusRecipe.ts:getBranchInfo (MUST) + 6. Add cases / refactor templates & getSurfaceDataForTemplate (MUST) + 7. Add cases / refactor looker.ts:getCTRPercentData (LATER) + 8. Update columns.tsx:filterBySurface (LATER) + 9. TDD Factor Out NimbusMessageTable (NICE) + 10. TDD Factor out high-level data fetching (NICE) + + 3. Pull in Android experiments using that URL + 4. Build dashboard link + 1. How to handle multi types + 5. Build CTR + 1. How to handle multi types + + +2. standup 2nd page + + * TDD? clone for mobile + +3. standup 2nd dashboard + + * review mobile telemetry using glean dict + * look at explores available for those tables + * TDD? subclass recipes (desktop & mobile) + + 1. Separate dashboards per surface: onboarding, (messaging genrally - may need to split into message_surface dashboard) + 2. +2.What about QA? + + +Later: +* clean up text on messaging graph +* \ No newline at end of file From e42fc6537b2494b21a7ea1c06e0e86f5cf09884c Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Fri, 14 Mar 2025 14:39:09 -0700 Subject: [PATCH 02/61] Cleanup TODO list some --- TODO.md | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/TODO.md b/TODO.md index a12abd3c..5c0df5c7 100644 --- a/TODO.md +++ b/TODO.md @@ -42,26 +42,26 @@ User Story: As a mobile PM, I should be able to understand what things are in ex 1. Build Android page 1. Review existing clone for "completed" (DONE) - 2. ?Consider options for cloning, since we'll want Android completed page too, and iOS pages - 1. Clean way: - 3. Create new dir with new page.tsx (MUST) - 4. TDD Factor out dashboard (prob MUST) - 5. Factor "application=" out of env (MUST) - - 2. Add cases / refactor messageUtils.getDashboard (MUST) - 3. Add cases / refactor experimentUtils.ts (MUST) - 4. Update / move messageUtils.getDashboardIdForTemplate (MUST) - 5. Add cases / refactor nimbusRecipe.ts:getBranchInfo (MUST) - 6. Add cases / refactor templates & getSurfaceDataForTemplate (MUST) - 7. Add cases / refactor looker.ts:getCTRPercentData (LATER) - 8. Update columns.tsx:filterBySurface (LATER) - 9. TDD Factor Out NimbusMessageTable (NICE) - 10. TDD Factor out high-level data fetching (NICE) - - 3. Pull in Android experiments using that URL - 4. Build dashboard link + 2. ?Consider options for cloning, since we'll want Android completed page too, and iOS pages (DONE) + + 3. Create new dir with new page.tsx (MUST) + + 4. TDD Factor out dashboard (prob MUST) + 5. Factor "application=" out of env (MUST) + 6. Add cases / refactor messageUtils.getDashboard (MUST) + 7. Add cases / refactor experimentUtils.ts (MUST) + 8. Update / move messageUtils.getDashboardIdForTemplate (MUST) + 9. Add cases / refactor nimbusRecipe.ts:getBranchInfo (MUST) + 10. Add cases / refactor templates & getSurfaceDataForTemplate (MUST) + 11. Add cases / refactor looker.ts:getCTRPercentData (LATER) + 12. Update columns.tsx:filterBySurface (LATER) + 13. TDD Factor Out NimbusMessageTable (NICE) + 14. TDD Factor out high-level data fetching (NICE) + + 15. Pull in Android experiments using that URL + 16. Build dashboard link 1. How to handle multi types - 5. Build CTR + 17. Build CTR 1. How to handle multi types From 9d2c9c5c2be73e452885d0c795ead4fbe7674b4c Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Fri, 14 Mar 2025 17:55:01 -0700 Subject: [PATCH 03/61] Pull Dashboard into own component used by page.txt --- .../app/{page.test.tsx => dashboard.test.tsx} | 14 +- app/dashboard.tsx | 309 ++++++++++++++++++ app/page.tsx | 309 +----------------- 3 files changed, 319 insertions(+), 313 deletions(-) rename __tests__/app/{page.test.tsx => dashboard.test.tsx} (77%) create mode 100644 app/dashboard.tsx diff --git a/__tests__/app/page.test.tsx b/__tests__/app/dashboard.test.tsx similarity index 77% rename from __tests__/app/page.test.tsx rename to __tests__/app/dashboard.test.tsx index 76ac78ca..5a93807d 100644 --- a/__tests__/app/page.test.tsx +++ b/__tests__/app/dashboard.test.tsx @@ -1,4 +1,4 @@ -import Dashboard from "@/app/page"; +import { Dashboard } from "@/app/dashboard"; import CompleteExperimentsDashboard from "@/app/complete/page"; import { render } from "@testing-library/react"; import { ExperimentFakes } from "../ExperimentFakes.mjs"; @@ -11,7 +11,7 @@ global.fetch = jest.fn(() => }), ) as jest.Mock; -describe("Page", () => { +describe("Dashboard", () => { it("all timeline pill ids exist in the Dashboard component in /", async () => { const dashboard = render(await Dashboard()); @@ -19,9 +19,9 @@ describe("Page", () => { const experiments = dashboard.getByTestId("live_experiments"); const rollouts = dashboard.getByTestId("live_rollouts"); - expect(firefox).toBeDefined(); - expect(experiments).toBeDefined(); - expect(rollouts).toBeDefined(); + expect(firefox).toBeInTheDocument(); + expect(experiments).toBeInTheDocument(); + expect(rollouts).toBeInTheDocument(); }); it("all timeline pill ids exist in the Dashboard component in /complete", async () => { @@ -30,7 +30,7 @@ describe("Page", () => { const experiments = dashboard.getByTestId("complete_experiments"); const rollouts = dashboard.getByTestId("complete_rollouts"); - expect(experiments).toBeDefined(); - expect(rollouts).toBeDefined(); + expect(experiments).toBeInTheDocument(); + expect(rollouts).toBeInTheDocument(); }); }); diff --git a/app/dashboard.tsx b/app/dashboard.tsx new file mode 100644 index 00000000..80c10deb --- /dev/null +++ b/app/dashboard.tsx @@ -0,0 +1,309 @@ +import { types } from "@mozilla/nimbus-shared"; +import { + RecipeOrBranchInfo, + experimentColumns, + FxMSMessageInfo, + fxmsMessageColumns, +} from "./columns"; +import { + cleanLookerData, + getCTRPercentData, + mergeLookerData, + runLookQuery, +} from "@/lib/looker.ts"; +import { + getDashboard, + getSurfaceDataForTemplate, + getTemplateFromMessage, + _isAboutWelcomeTemplate, + maybeCreateWelcomePreview, + getPreviewLink, + messageHasMicrosurvey, + compareSurfacesFn, +} from "../lib/messageUtils.ts"; + +import { NimbusRecipeCollection } from "../lib/nimbusRecipeCollection"; +import { _substituteLocalizations } from "../lib/experimentUtils.ts"; + +import { NimbusRecipe } from "../lib/nimbusRecipe.ts"; +import { MessageTable } from "./message-table"; + +import { MenuButton } from "@/components/ui/menubutton.tsx"; +import { InfoPopover } from "@/components/ui/infopopover.tsx"; +import { Timeline } from "@/components/ui/timeline.tsx"; + +const isLookerEnabled = process.env.IS_LOOKER_ENABLED === "true"; + +const hidden_message_impression_threshold = + process.env.HIDDEN_MESSAGE_IMPRESSION_THRESHOLD; + +/** + * A sorting function to sort messages by their start dates in descending order. + * If one or both of the recipes is missing a start date, they will be ordered + * identically since there's not enough information to properly sort them by + * date. + * + * @param a Nimbus recipe to compare with `b`. + * @param b Nimbus recipe to compare with `a`. + * @returns -1 if the start date for message a is after the start date for + * message b, zero if they're equal, and 1 otherwise. + */ +function compareDatesFn(a: NimbusRecipe, b: NimbusRecipe): number { + if (a._rawRecipe.startDate && b._rawRecipe.startDate) { + if (a._rawRecipe.startDate > b._rawRecipe.startDate) { + return -1; + } else if (a._rawRecipe.startDate < b._rawRecipe.startDate) { + return 1; + } + } + + // a must be equal to b + return 0; +} + +async function getASRouterLocalColumnFromJSON( + messageDef: any, +): Promise { + let fxmsMsgInfo: FxMSMessageInfo = { + product: "Desktop", + id: messageDef.id, + template: messageDef.template, + surface: getSurfaceDataForTemplate(getTemplateFromMessage(messageDef)) + .surface, + segment: "some segment", + metrics: "some metrics", + ctrPercent: undefined, // may be populated from Looker data + ctrPercentChange: undefined, // may be populated from Looker data + previewLink: getPreviewLink(maybeCreateWelcomePreview(messageDef)), + impressions: undefined, // may be populated from Looker data + hasMicrosurvey: messageHasMicrosurvey(messageDef.id), + hidePreview: messageDef.hidePreview, + }; + + const channel = "release"; + + if (isLookerEnabled) { + const ctrPercentData = await getCTRPercentData( + fxmsMsgInfo.id, + fxmsMsgInfo.template, + channel, + ); + if (ctrPercentData) { + fxmsMsgInfo.ctrPercent = ctrPercentData.ctrPercent; + fxmsMsgInfo.impressions = ctrPercentData.impressions; + } + } + + fxmsMsgInfo.ctrDashboardLink = getDashboard( + fxmsMsgInfo.template, + fxmsMsgInfo.id, + channel, + ); + + // dashboard link -> dashboard id -> query id -> query -> ctr_percent_from_lastish_day + + // console.log("fxmsMsgInfo: ", fxmsMsgInfo) + + return fxmsMsgInfo; +} + +let columnsShown = false; + +type NimbusExperiment = types.experiments.NimbusExperiment; + +/** + * Appends any FxMS telemetry message data from the query in Look + * https://mozilla.cloud.looker.com/looks/2162 that does not already exist (ie. + * no duplicate message ids) in existingMessageData and returns the result. The + * message data is also cleaned up to match the message data objects from + * ASRouter, remove any test messages, and update templates. + */ +async function appendFxMSTelemetryData(existingMessageData: any) { + // Get Looker message data (taken from the query in Look + // https://mozilla.cloud.looker.com/looks/2162) + const lookId = "2162"; + let lookerData = await runLookQuery(lookId); + + // Clean and merge Looker data with existing data + let jsonLookerData = cleanLookerData(lookerData); + let mergedData = mergeLookerData(existingMessageData, jsonLookerData); + + return mergedData; +} + +/** + * @returns message data in the form of FxMSMessageInfo from + * lib/asrouter-local-prod-messages/data.json and also FxMS telemetry data if + * Looker credentials are enabled. + */ +async function getASRouterLocalMessageInfoFromFile(): Promise< + FxMSMessageInfo[] +> { + const fs = require("fs"); + + let data = fs.readFileSync( + "lib/asrouter-local-prod-messages/data.json", + "utf8", + ); + let json_data = JSON.parse(data); + + if (isLookerEnabled) { + json_data = await appendFxMSTelemetryData(json_data); + } + + let messages = await Promise.all( + json_data.map(async (messageDef: any): Promise => { + return await getASRouterLocalColumnFromJSON(messageDef); + }), + ); + + return messages; +} + +async function getMsgExpRecipeCollection( + recipeCollection: NimbusRecipeCollection, +): Promise { + const expOnlyCollection = new NimbusRecipeCollection(); + expOnlyCollection.recipes = recipeCollection.recipes.filter((recipe) => + recipe.isExpRecipe(), + ); + console.log("expOnlyCollection.length = ", expOnlyCollection.recipes.length); + + const msgExpRecipeCollection = new NimbusRecipeCollection(); + msgExpRecipeCollection.recipes = expOnlyCollection.recipes + .filter((recipe) => recipe.usesMessagingFeatures()) + .sort(compareDatesFn); + console.log( + "msgExpRecipeCollection.length = ", + msgExpRecipeCollection.recipes.length, + ); + + return msgExpRecipeCollection; +} + +async function getMsgRolloutCollection( + recipeCollection: NimbusRecipeCollection, +): Promise { + const msgRolloutRecipeCollection = new NimbusRecipeCollection(); + msgRolloutRecipeCollection.recipes = recipeCollection.recipes + .filter((recipe) => recipe.usesMessagingFeatures() && !recipe.isExpRecipe()) + .sort(compareDatesFn); + console.log( + "msgRolloutRecipeCollection.length = ", + msgRolloutRecipeCollection.recipes.length, + ); + + return msgRolloutRecipeCollection; +} + +export async function Dashboard() { + // Check to see if Auth is enabled + const isAuthEnabled = process.env.IS_AUTH_ENABLED === "true"; + + const recipeCollection = new NimbusRecipeCollection(); + await recipeCollection.fetchRecipes(); + console.log("recipeCollection.length = ", recipeCollection.recipes.length); + + // XXX await Promise.allSettled for all three loads concurrently + const localData = (await getASRouterLocalMessageInfoFromFile()).sort( + compareSurfacesFn, + ); + const msgExpRecipeCollection = + await getMsgExpRecipeCollection(recipeCollection); + const msgRolloutRecipeCollection = + await getMsgRolloutCollection(recipeCollection); + + // Get in format useable by MessageTable + const experimentAndBranchInfo: RecipeOrBranchInfo[] = isLookerEnabled + ? // Update branches inside recipe infos with CTR percents + await msgExpRecipeCollection.getExperimentAndBranchInfos() + : msgExpRecipeCollection.recipes.map((recipe: NimbusRecipe) => + recipe.getRecipeInfo(), + ); + + const totalExperiments = msgExpRecipeCollection.recipes.length; + + const msgRolloutInfo: RecipeOrBranchInfo[] = isLookerEnabled + ? // Update branches inside recipe infos with CTR percents + await msgRolloutRecipeCollection.getExperimentAndBranchInfos() + : msgRolloutRecipeCollection.recipes.map((recipe: NimbusRecipe) => + recipe.getRecipeInfo(), + ); + + const totalRolloutExperiments = msgRolloutRecipeCollection.recipes.length; + + return ( +
+
+

Skylight

+ +
+ +
+ Desktop Messages Released on Firefox + +
+
+ +
+ +
+ +
+ +
+ Current Desktop Message Rollouts +
+
+ Total: {totalRolloutExperiments} +
+
+ +
+
+ +
+ +
+ Current Desktop Message Experiments +
+
+ Total: {totalExperiments} +
+
+ +
+
+ +
+
+ ); +} diff --git a/app/page.tsx b/app/page.tsx index b711862d..f8d1fb6f 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,309 +1,6 @@ import { types } from "@mozilla/nimbus-shared"; -import { - RecipeOrBranchInfo, - experimentColumns, - FxMSMessageInfo, - fxmsMessageColumns, -} from "./columns"; -import { - cleanLookerData, - getCTRPercentData, - mergeLookerData, - runLookQuery, -} from "@/lib/looker.ts"; -import { - getDashboard, - getSurfaceDataForTemplate, - getTemplateFromMessage, - _isAboutWelcomeTemplate, - maybeCreateWelcomePreview, - getPreviewLink, - messageHasMicrosurvey, - compareSurfacesFn, -} from "../lib/messageUtils.ts"; +import { Dashboard } from "@/app/dashboard"; -import { NimbusRecipeCollection } from "../lib/nimbusRecipeCollection"; -import { _substituteLocalizations } from "../lib/experimentUtils.ts"; - -import { NimbusRecipe } from "../lib/nimbusRecipe.ts"; -import { MessageTable } from "./message-table"; - -import { MenuButton } from "@/components/ui/menubutton.tsx"; -import { InfoPopover } from "@/components/ui/infopopover.tsx"; -import { Timeline } from "@/components/ui/timeline.tsx"; - -const isLookerEnabled = process.env.IS_LOOKER_ENABLED === "true"; - -const hidden_message_impression_threshold = - process.env.HIDDEN_MESSAGE_IMPRESSION_THRESHOLD; - -/** - * A sorting function to sort messages by their start dates in descending order. - * If one or both of the recipes is missing a start date, they will be ordered - * identically since there's not enough information to properly sort them by - * date. - * - * @param a Nimbus recipe to compare with `b`. - * @param b Nimbus recipe to compare with `a`. - * @returns -1 if the start date for message a is after the start date for - * message b, zero if they're equal, and 1 otherwise. - */ -function compareDatesFn(a: NimbusRecipe, b: NimbusRecipe): number { - if (a._rawRecipe.startDate && b._rawRecipe.startDate) { - if (a._rawRecipe.startDate > b._rawRecipe.startDate) { - return -1; - } else if (a._rawRecipe.startDate < b._rawRecipe.startDate) { - return 1; - } - } - - // a must be equal to b - return 0; -} - -async function getASRouterLocalColumnFromJSON( - messageDef: any, -): Promise { - let fxmsMsgInfo: FxMSMessageInfo = { - product: "Desktop", - id: messageDef.id, - template: messageDef.template, - surface: getSurfaceDataForTemplate(getTemplateFromMessage(messageDef)) - .surface, - segment: "some segment", - metrics: "some metrics", - ctrPercent: undefined, // may be populated from Looker data - ctrPercentChange: undefined, // may be populated from Looker data - previewLink: getPreviewLink(maybeCreateWelcomePreview(messageDef)), - impressions: undefined, // may be populated from Looker data - hasMicrosurvey: messageHasMicrosurvey(messageDef.id), - hidePreview: messageDef.hidePreview, - }; - - const channel = "release"; - - if (isLookerEnabled) { - const ctrPercentData = await getCTRPercentData( - fxmsMsgInfo.id, - fxmsMsgInfo.template, - channel, - ); - if (ctrPercentData) { - fxmsMsgInfo.ctrPercent = ctrPercentData.ctrPercent; - fxmsMsgInfo.impressions = ctrPercentData.impressions; - } - } - - fxmsMsgInfo.ctrDashboardLink = getDashboard( - fxmsMsgInfo.template, - fxmsMsgInfo.id, - channel, - ); - - // dashboard link -> dashboard id -> query id -> query -> ctr_percent_from_lastish_day - - // console.log("fxmsMsgInfo: ", fxmsMsgInfo) - - return fxmsMsgInfo; -} - -let columnsShown = false; - -type NimbusExperiment = types.experiments.NimbusExperiment; - -/** - * Appends any FxMS telemetry message data from the query in Look - * https://mozilla.cloud.looker.com/looks/2162 that does not already exist (ie. - * no duplicate message ids) in existingMessageData and returns the result. The - * message data is also cleaned up to match the message data objects from - * ASRouter, remove any test messages, and update templates. - */ -async function appendFxMSTelemetryData(existingMessageData: any) { - // Get Looker message data (taken from the query in Look - // https://mozilla.cloud.looker.com/looks/2162) - const lookId = "2162"; - let lookerData = await runLookQuery(lookId); - - // Clean and merge Looker data with existing data - let jsonLookerData = cleanLookerData(lookerData); - let mergedData = mergeLookerData(existingMessageData, jsonLookerData); - - return mergedData; -} - -/** - * @returns message data in the form of FxMSMessageInfo from - * lib/asrouter-local-prod-messages/data.json and also FxMS telemetry data if - * Looker credentials are enabled. - */ -async function getASRouterLocalMessageInfoFromFile(): Promise< - FxMSMessageInfo[] -> { - const fs = require("fs"); - - let data = fs.readFileSync( - "lib/asrouter-local-prod-messages/data.json", - "utf8", - ); - let json_data = JSON.parse(data); - - if (isLookerEnabled) { - json_data = await appendFxMSTelemetryData(json_data); - } - - let messages = await Promise.all( - json_data.map(async (messageDef: any): Promise => { - return await getASRouterLocalColumnFromJSON(messageDef); - }), - ); - - return messages; -} - -async function getMsgExpRecipeCollection( - recipeCollection: NimbusRecipeCollection, -): Promise { - const expOnlyCollection = new NimbusRecipeCollection(); - expOnlyCollection.recipes = recipeCollection.recipes.filter((recipe) => - recipe.isExpRecipe(), - ); - console.log("expOnlyCollection.length = ", expOnlyCollection.recipes.length); - - const msgExpRecipeCollection = new NimbusRecipeCollection(); - msgExpRecipeCollection.recipes = expOnlyCollection.recipes - .filter((recipe) => recipe.usesMessagingFeatures()) - .sort(compareDatesFn); - console.log( - "msgExpRecipeCollection.length = ", - msgExpRecipeCollection.recipes.length, - ); - - return msgExpRecipeCollection; -} - -async function getMsgRolloutCollection( - recipeCollection: NimbusRecipeCollection, -): Promise { - const msgRolloutRecipeCollection = new NimbusRecipeCollection(); - msgRolloutRecipeCollection.recipes = recipeCollection.recipes - .filter((recipe) => recipe.usesMessagingFeatures() && !recipe.isExpRecipe()) - .sort(compareDatesFn); - console.log( - "msgRolloutRecipeCollection.length = ", - msgRolloutRecipeCollection.recipes.length, - ); - - return msgRolloutRecipeCollection; -} - -export default async function Dashboard() { - // Check to see if Auth is enabled - const isAuthEnabled = process.env.IS_AUTH_ENABLED === "true"; - - const recipeCollection = new NimbusRecipeCollection(); - await recipeCollection.fetchRecipes(); - console.log("recipeCollection.length = ", recipeCollection.recipes.length); - - // XXX await Promise.allSettled for all three loads concurrently - const localData = (await getASRouterLocalMessageInfoFromFile()).sort( - compareSurfacesFn, - ); - const msgExpRecipeCollection = - await getMsgExpRecipeCollection(recipeCollection); - const msgRolloutRecipeCollection = - await getMsgRolloutCollection(recipeCollection); - - // Get in format useable by MessageTable - const experimentAndBranchInfo: RecipeOrBranchInfo[] = isLookerEnabled - ? // Update branches inside recipe infos with CTR percents - await msgExpRecipeCollection.getExperimentAndBranchInfos() - : msgExpRecipeCollection.recipes.map((recipe: NimbusRecipe) => - recipe.getRecipeInfo(), - ); - - const totalExperiments = msgExpRecipeCollection.recipes.length; - - const msgRolloutInfo: RecipeOrBranchInfo[] = isLookerEnabled - ? // Update branches inside recipe infos with CTR percents - await msgRolloutRecipeCollection.getExperimentAndBranchInfos() - : msgRolloutRecipeCollection.recipes.map((recipe: NimbusRecipe) => - recipe.getRecipeInfo(), - ); - - const totalRolloutExperiments = msgRolloutRecipeCollection.recipes.length; - - return ( -
-
-

Skylight

- -
- -
- Desktop Messages Released on Firefox - -
-
- -
- -
- -
- -
- Current Desktop Message Rollouts -
-
- Total: {totalRolloutExperiments} -
-
- -
-
- -
- -
- Current Desktop Message Experiments -
-
- Total: {totalExperiments} -
-
- -
-
- -
-
- ); +export default async function page() { + return ; } From 0a48fa82aa0aa2efae519be9d0f02bc4119ae613 Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Fri, 14 Mar 2025 18:02:56 -0700 Subject: [PATCH 04/61] Remove unneeded import --- app/page.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/app/page.tsx b/app/page.tsx index f8d1fb6f..65022b18 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,4 +1,3 @@ -import { types } from "@mozilla/nimbus-shared"; import { Dashboard } from "@/app/dashboard"; export default async function page() { From ee10c90b55c831c1edb734be57ae554c60c003a3 Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Mon, 17 Mar 2025 13:59:22 -0700 Subject: [PATCH 05/61] Added launch config for jest tests --- .vscode/launch.json | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/.vscode/launch.json b/.vscode/launch.json index ee3bdd72..7aac9bb8 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -23,6 +23,26 @@ "uriFormat": "%s", "action": "debugWithChrome" } + }, + { + "type": "node", + "name": "vscode-jest-tests.v2.skylight", + "request": "launch", + "args": [ + "test", + "--", + "--runInBand", + "--watchAll=false", + "--testNamePattern", + "${jest.testNamePattern}", + "--runTestsByPath", + "${jest.testFile}" + ], + "cwd": "/Users/dmosedale/s/skylight", + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen", + "disableOptimisticBPs": true, + "runtimeExecutable": "npm" } ] } From 6f871be54c64461965f3f245a4ca9475c565e17e Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Mon, 17 Mar 2025 14:02:54 -0700 Subject: [PATCH 06/61] Basic refactor without tests --- app/dashboard.tsx | 54 ++++++++++++++++++++++++++++------------------- app/page.tsx | 18 ++++++++++++++-- 2 files changed, 48 insertions(+), 24 deletions(-) diff --git a/app/dashboard.tsx b/app/dashboard.tsx index 80c10deb..f7aee60a 100644 --- a/app/dashboard.tsx +++ b/app/dashboard.tsx @@ -196,44 +196,54 @@ async function getMsgRolloutCollection( return msgRolloutRecipeCollection; } -export async function Dashboard() { - // Check to see if Auth is enabled - const isAuthEnabled = process.env.IS_AUTH_ENABLED === "true"; - +async function fetchData() { const recipeCollection = new NimbusRecipeCollection(); await recipeCollection.fetchRecipes(); console.log("recipeCollection.length = ", recipeCollection.recipes.length); - // XXX await Promise.allSettled for all three loads concurrently const localData = (await getASRouterLocalMessageInfoFromFile()).sort( compareSurfacesFn, ); - const msgExpRecipeCollection = - await getMsgExpRecipeCollection(recipeCollection); - const msgRolloutRecipeCollection = - await getMsgRolloutCollection(recipeCollection); - - // Get in format useable by MessageTable - const experimentAndBranchInfo: RecipeOrBranchInfo[] = isLookerEnabled - ? // Update branches inside recipe infos with CTR percents - await msgExpRecipeCollection.getExperimentAndBranchInfos() + + const msgExpRecipeCollection = await getMsgExpRecipeCollection(recipeCollection); + const msgRolloutRecipeCollection = await getMsgRolloutCollection(recipeCollection); + + const experimentAndBranchInfo = isLookerEnabled + ? await msgExpRecipeCollection.getExperimentAndBranchInfos() : msgExpRecipeCollection.recipes.map((recipe: NimbusRecipe) => - recipe.getRecipeInfo(), - ); + recipe.getRecipeInfo()); const totalExperiments = msgExpRecipeCollection.recipes.length; - const msgRolloutInfo: RecipeOrBranchInfo[] = isLookerEnabled - ? // Update branches inside recipe infos with CTR percents - await msgRolloutRecipeCollection.getExperimentAndBranchInfos() + const msgRolloutInfo = isLookerEnabled + ? await msgRolloutRecipeCollection.getExperimentAndBranchInfos() : msgRolloutRecipeCollection.recipes.map((recipe: NimbusRecipe) => - recipe.getRecipeInfo(), - ); + recipe.getRecipeInfo()); const totalRolloutExperiments = msgRolloutRecipeCollection.recipes.length; + return { + localData, + experimentAndBranchInfo, + totalExperiments, + msgRolloutInfo, + totalRolloutExperiments, + }; +} + +export async function Dashboard({ platform = "desktop" }): Promise { + platform = platform || "desktop"; // Ensure platform is always defined + + const { + localData, + experimentAndBranchInfo, + totalExperiments, + msgRolloutInfo, + totalRolloutExperiments, + } = await fetchData(); + return ( -
+

Skylight

diff --git a/app/page.tsx b/app/page.tsx index 65022b18..e7d77bc3 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,5 +1,19 @@ import { Dashboard } from "@/app/dashboard"; -export default async function page() { - return ; +export default function Page({ + searchParams, +}: { + searchParams: { [key: string]: string | string[] | undefined } +}) { + + console.log("searchParams: ", searchParams); + + let platform; + if (typeof searchParams.platform === 'string') { + platform = searchParams.platform as string; + } + return ( + + ); } + From 73399b5b2abcf38f53251f762b95f07f9fe549ec Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Mon, 17 Mar 2025 14:03:21 -0700 Subject: [PATCH 07/61] Update TODO.md --- TODO.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index 5c0df5c7..e4f80034 100644 --- a/TODO.md +++ b/TODO.md @@ -46,7 +46,7 @@ User Story: As a mobile PM, I should be able to understand what things are in ex 3. Create new dir with new page.tsx (MUST) - 4. TDD Factor out dashboard (prob MUST) + 4. TDD Factor out dashboard (STARTED) 5. Factor "application=" out of env (MUST) 6. Add cases / refactor messageUtils.getDashboard (MUST) 7. Add cases / refactor experimentUtils.ts (MUST) From 05fa087ca8da3def4503e69af4b2741ff5061da2 Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Mon, 17 Mar 2025 15:42:57 -0700 Subject: [PATCH 08/61] Git rid of cruft and fix a test --- app/dashboard.tsx | 9 ++++----- app/page.tsx | 14 ++------------ 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/app/dashboard.tsx b/app/dashboard.tsx index f7aee60a..a278ada5 100644 --- a/app/dashboard.tsx +++ b/app/dashboard.tsx @@ -231,8 +231,7 @@ async function fetchData() { }; } -export async function Dashboard({ platform = "desktop" }): Promise { - platform = platform || "desktop"; // Ensure platform is always defined +export async function Dashboard({ platform = "desktop" }: { platform?: string }): Promise { const { localData, @@ -254,7 +253,7 @@ export async function Dashboard({ platform = "desktop" }): Promise data-testid="firefox" className="scroll-m-20 text-xl font-semibold text-center pt-6 flex items-center justify-center gap-x-1" > - Desktop Messages Released on Firefox + {platform} Messages Released on Firefox
- Current Desktop Message Rollouts + Current {platform} Message Rollouts
- Current Desktop Message Experiments + Current {platform} Message Experiments
+ ); } From 99feb17a29acf56f98eb4fb795da75742ecba0c4 Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Mon, 17 Mar 2025 16:34:47 -0700 Subject: [PATCH 09/61] Fix dashboard test --- __tests__/app/dashboard.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__tests__/app/dashboard.test.tsx b/__tests__/app/dashboard.test.tsx index 5a93807d..be7e47e1 100644 --- a/__tests__/app/dashboard.test.tsx +++ b/__tests__/app/dashboard.test.tsx @@ -13,7 +13,7 @@ global.fetch = jest.fn(() => describe("Dashboard", () => { it("all timeline pill ids exist in the Dashboard component in /", async () => { - const dashboard = render(await Dashboard()); + const dashboard = render(await Dashboard({platform: "desktop"})); const firefox = dashboard.getByTestId("firefox"); const experiments = dashboard.getByTestId("live_experiments"); From 8d2e9b2963270b22c70fbd26b07bd7c9f88e0196 Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Mon, 17 Mar 2025 16:36:38 -0700 Subject: [PATCH 10/61] Wrap prodtable in a div in prep for hoisting --- app/dashboard.tsx | 46 ++++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/app/dashboard.tsx b/app/dashboard.tsx index a278ada5..c44e27a2 100644 --- a/app/dashboard.tsx +++ b/app/dashboard.tsx @@ -248,28 +248,30 @@ export async function Dashboard({ platform = "desktop" }: { platform?: string })
-
- {platform} Messages Released on Firefox - -
-
- -
- -
- +
+
+ {platform} Messages Released on Firefox + +
+
+ +
+ +
+ +
From 3f928d2f08c0c9b0a5f0ba80c0f9e04bac885200 Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Mon, 17 Mar 2025 16:36:54 -0700 Subject: [PATCH 11/61] Add android route --- app/android/page.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 app/android/page.tsx diff --git a/app/android/page.tsx b/app/android/page.tsx new file mode 100644 index 00000000..b9caaeb1 --- /dev/null +++ b/app/android/page.tsx @@ -0,0 +1,8 @@ +import { Dashboard } from "@/app/dashboard"; + +export default function Page() { + return ( + + ); +} + From b7a16e2f0fc1b815c9a1fcc0ac56d488f7629b03 Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Tue, 18 Mar 2025 08:57:44 -0700 Subject: [PATCH 12/61] Switch to JSX syntax --- __tests__/app/dashboard.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__tests__/app/dashboard.test.tsx b/__tests__/app/dashboard.test.tsx index be7e47e1..d4b5fa52 100644 --- a/__tests__/app/dashboard.test.tsx +++ b/__tests__/app/dashboard.test.tsx @@ -13,7 +13,7 @@ global.fetch = jest.fn(() => describe("Dashboard", () => { it("all timeline pill ids exist in the Dashboard component in /", async () => { - const dashboard = render(await Dashboard({platform: "desktop"})); + const dashboard = render(await ); const firefox = dashboard.getByTestId("firefox"); const experiments = dashboard.getByTestId("live_experiments"); From 440be3e2d4f9551c26f66eac14354f37b4b77f1a Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Tue, 18 Mar 2025 08:58:34 -0700 Subject: [PATCH 13/61] Factor out ReleaseTable into its own component --- app/dashboard.tsx | 67 +++++++++++++++++++++++++++++------------------ 1 file changed, 41 insertions(+), 26 deletions(-) diff --git a/app/dashboard.tsx b/app/dashboard.tsx index c44e27a2..46abd18d 100644 --- a/app/dashboard.tsx +++ b/app/dashboard.tsx @@ -231,7 +231,46 @@ async function fetchData() { }; } -export async function Dashboard({ platform = "desktop" }: { platform?: string }): Promise { +interface ReleasedTableProps { + platform: string; + localData: FxMSMessageInfo[]; +} + +const ReleasedTable = async ({ platform, localData }: ReleasedTableProps) => { + return ( +
+
+ {platform} Messages Released on Firefox + +
+
+ +
+ +
+ +
+
+ ); +} + +interface DashboardProps { + platform?: string; +} + +export const Dashboard = async ({ platform }: DashboardProps) => { const { localData, @@ -248,31 +287,7 @@ export async function Dashboard({ platform = "desktop" }: { platform?: string })
-
-
- {platform} Messages Released on Firefox - -
-
- -
- -
- -
-
+
Current {platform} Message Rollouts From 1c981ee5494ab49066c57b4e5ac6e74411fe8222 Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Tue, 18 Mar 2025 09:02:12 -0700 Subject: [PATCH 14/61] Appease prettier --- app/android/page.tsx | 5 +---- app/dashboard.tsx | 17 ++++++++++------- app/page.tsx | 6 +----- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/app/android/page.tsx b/app/android/page.tsx index b9caaeb1..69ece864 100644 --- a/app/android/page.tsx +++ b/app/android/page.tsx @@ -1,8 +1,5 @@ import { Dashboard } from "@/app/dashboard"; export default function Page() { - return ( - - ); + return ; } - diff --git a/app/dashboard.tsx b/app/dashboard.tsx index 46abd18d..aa517abe 100644 --- a/app/dashboard.tsx +++ b/app/dashboard.tsx @@ -205,20 +205,24 @@ async function fetchData() { compareSurfacesFn, ); - const msgExpRecipeCollection = await getMsgExpRecipeCollection(recipeCollection); - const msgRolloutRecipeCollection = await getMsgRolloutCollection(recipeCollection); + const msgExpRecipeCollection = + await getMsgExpRecipeCollection(recipeCollection); + const msgRolloutRecipeCollection = + await getMsgRolloutCollection(recipeCollection); const experimentAndBranchInfo = isLookerEnabled ? await msgExpRecipeCollection.getExperimentAndBranchInfos() : msgExpRecipeCollection.recipes.map((recipe: NimbusRecipe) => - recipe.getRecipeInfo()); + recipe.getRecipeInfo(), + ); const totalExperiments = msgExpRecipeCollection.recipes.length; const msgRolloutInfo = isLookerEnabled ? await msgRolloutRecipeCollection.getExperimentAndBranchInfos() : msgRolloutRecipeCollection.recipes.map((recipe: NimbusRecipe) => - recipe.getRecipeInfo()); + recipe.getRecipeInfo(), + ); const totalRolloutExperiments = msgRolloutRecipeCollection.recipes.length; @@ -264,14 +268,13 @@ const ReleasedTable = async ({ platform, localData }: ReleasedTableProps) => { ); -} +}; interface DashboardProps { platform?: string; } export const Dashboard = async ({ platform }: DashboardProps) => { - const { localData, experimentAndBranchInfo, @@ -332,4 +335,4 @@ export const Dashboard = async ({ platform }: DashboardProps) => { ); -} +}; diff --git a/app/page.tsx b/app/page.tsx index 608a145a..73a92f1c 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,9 +1,5 @@ import { Dashboard } from "@/app/dashboard"; export default function Page() { - - return ( - - ); + return ; } - From 37b9767ecd3238398457c16ff9c38587e14b6b16 Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Tue, 18 Mar 2025 09:04:11 -0700 Subject: [PATCH 15/61] Appease prettier more --- TODO.md | 136 +++++++++++++++---------------- __tests__/app/dashboard.test.tsx | 2 +- 2 files changed, 69 insertions(+), 69 deletions(-) diff --git a/TODO.md b/TODO.md index e4f80034..26ee5573 100644 --- a/TODO.md +++ b/TODO.md @@ -1,85 +1,85 @@ Goal: stand up mobile version of experiments/rollouts User Story: As a mobile PM, I should be able to understand what things are in experiments and - rollouts for messaging & onboarding & what the clickthrough rates look like - - -* research - * [surfaces & guidelines](https://mozilla-hub.atlassian.net/wiki/spaces/FIREFOX/pages/210206760/Mobile+Message+Surface+Guidelines) - * [mobile telemetry docs](https://experimenter.info/messaging/mobile-messaging/#events-emitted) - * [desktop explore](https://mozilla.cloud.looker.com/explore/user_journey/event_counts) - - 1. look at iOS telemetry & explores - * [iOS message probes](https://dictionary.telemetry.mozilla.org/apps/firefox_ios?page=1&search=messag) - * Note no experiments probes like Android has - * [iOS event count explore](https://mozilla.cloud.looker.com/explore/firefox_ios/event_counts?qid=OZqOXzZqTujARgvCK12NJ4) - * [recent iOS clicked events](https://mozilla.cloud.looker.com/explore/firefox_ios/event_counts?qid=jQpgYwZpBZEhW73B1dcyzu&toggle=fil,vis) - * XXX look at onboarding also - - 2. look at Android telemetry & explores - * [Android message probes - Glean dictionary](https://dictionary.telemetry.mozilla.org/apps/fenix?page=1&search=messaging) - * [android explore](https://mozilla.cloud.looker.com/explore/fenix/event_counts)$$ - * [recent android messaing click events with most extra keys and experiments](https://mozilla.cloud.looker.com/explore/fenix/event_counts?qid=u0OKWHjWgTcstNgbzvyyBc&toggle=fil) - * [recent android onboarding events](https://mozilla.cloud.looker.com/explore/fenix/event_counts?qid=n71HDr0LIxuNS3vGX9essN&toggle=fil) - * Need to understand this telemetry compared to JSON - * -* open questions - * what does telemetry look like for onboarding? other surfaces? similar to messaging? - * Does Click telemetry on both iOS and Android alwyas mean CTA? Or something else? - * What is action_uuid extra key (see docs)? - * XXX Do non-experimental message send pings on iOS? on Android? - * - - -1. Draft plan for Android page - 1. ?File ticket - 2. Build chart for Android messaging (DONE) - 3. Build 2nd chart (LATER) - 4. Build dashboard (DONE - id = 2191) - 5. Move to shared folder (LATER) +rollouts for messaging & onboarding & what the clickthrough rates look like - XXX FINISH BUILDING todo list; XXX plan team work; XXX map to calendar +- research + + - [surfaces & guidelines](https://mozilla-hub.atlassian.net/wiki/spaces/FIREFOX/pages/210206760/Mobile+Message+Surface+Guidelines) + - [mobile telemetry docs](https://experimenter.info/messaging/mobile-messaging/#events-emitted) + - [desktop explore](https://mozilla.cloud.looker.com/explore/user_journey/event_counts) + + 1. look at iOS telemetry & explores + + - [iOS message probes](https://dictionary.telemetry.mozilla.org/apps/firefox_ios?page=1&search=messag) + - Note no experiments probes like Android has + - [iOS event count explore](https://mozilla.cloud.looker.com/explore/firefox_ios/event_counts?qid=OZqOXzZqTujARgvCK12NJ4) + - [recent iOS clicked events](https://mozilla.cloud.looker.com/explore/firefox_ios/event_counts?qid=jQpgYwZpBZEhW73B1dcyzu&toggle=fil,vis) + - XXX look at onboarding also + + 2. look at Android telemetry & explores + - [Android message probes - Glean dictionary](https://dictionary.telemetry.mozilla.org/apps/fenix?page=1&search=messaging) + - [android explore](https://mozilla.cloud.looker.com/explore/fenix/event_counts)$$ + - [recent android messaing click events with most extra keys and experiments](https://mozilla.cloud.looker.com/explore/fenix/event_counts?qid=u0OKWHjWgTcstNgbzvyyBc&toggle=fil) + - [recent android onboarding events](https://mozilla.cloud.looker.com/explore/fenix/event_counts?qid=n71HDr0LIxuNS3vGX9essN&toggle=fil) + - Need to understand this telemetry compared to JSON - 1. Build Android page - 1. Review existing clone for "completed" (DONE) - 2. ?Consider options for cloning, since we'll want Android completed page too, and iOS pages (DONE) +- +- open questions + - what does telemetry look like for onboarding? other surfaces? similar to messaging? + - Does Click telemetry on both iOS and Android alwyas mean CTA? Or something else? + - What is action_uuid extra key (see docs)? + - ## XXX Do non-experimental message send pings on iOS? on Android? - 3. Create new dir with new page.tsx (MUST) +1. Draft plan for Android page - 4. TDD Factor out dashboard (STARTED) - 5. Factor "application=" out of env (MUST) - 6. Add cases / refactor messageUtils.getDashboard (MUST) - 7. Add cases / refactor experimentUtils.ts (MUST) - 8. Update / move messageUtils.getDashboardIdForTemplate (MUST) - 9. Add cases / refactor nimbusRecipe.ts:getBranchInfo (MUST) - 10. Add cases / refactor templates & getSurfaceDataForTemplate (MUST) - 11. Add cases / refactor looker.ts:getCTRPercentData (LATER) - 12. Update columns.tsx:filterBySurface (LATER) - 13. TDD Factor Out NimbusMessageTable (NICE) - 14. TDD Factor out high-level data fetching (NICE) + 1. ?File ticket + 2. Build chart for Android messaging (DONE) + 3. Build 2nd chart (LATER) + 4. Build dashboard (DONE - id = 2191) + 5. Move to shared folder (LATER) - 15. Pull in Android experiments using that URL - 16. Build dashboard link - 1. How to handle multi types - 17. Build CTR - 1. How to handle multi types + XXX FINISH BUILDING todo list; XXX plan team work; XXX map to calendar + + 1. Build Android page + + 1. Review existing clone for "completed" (DONE) + 2. ?Consider options for cloning, since we'll want Android completed page too, and iOS pages (DONE) + 3. Create new dir with new page.tsx (MUST) -2. standup 2nd page + 4. TDD Factor out dashboard (STARTED) + 5. Factor "application=" out of env (MUST) + 6. Add cases / refactor messageUtils.getDashboard (MUST) + 7. Add cases / refactor experimentUtils.ts (MUST) + 8. Update / move messageUtils.getDashboardIdForTemplate (MUST) + 9. Add cases / refactor nimbusRecipe.ts:getBranchInfo (MUST) + 10. Add cases / refactor templates & getSurfaceDataForTemplate (MUST) + 11. Add cases / refactor looker.ts:getCTRPercentData (LATER) + 12. Update columns.tsx:filterBySurface (LATER) + 13. TDD Factor Out NimbusMessageTable (NICE) + 14. TDD Factor out high-level data fetching (NICE) - * TDD? clone for mobile + 15. Pull in Android experiments using that URL + 16. Build dashboard link + 17. How to handle multi types + 18. Build CTR + 19. How to handle multi types -3. standup 2nd dashboard +2. standup 2nd page - * review mobile telemetry using glean dict - * look at explores available for those tables - * TDD? subclass recipes (desktop & mobile) + - TDD? clone for mobile - 1. Separate dashboards per surface: onboarding, (messaging genrally - may need to split into message_surface dashboard) - 2. -2.What about QA? +3. standup 2nd dashboard + * review mobile telemetry using glean dict + * look at explores available for those tables + * TDD? subclass recipes (desktop & mobile) + + 1. Separate dashboards per surface: onboarding, (messaging genrally - may need to split into message_surface dashboard) + 2. 2.What about QA? Later: -* clean up text on messaging graph -* \ No newline at end of file + +- clean up text on messaging graph +- diff --git a/__tests__/app/dashboard.test.tsx b/__tests__/app/dashboard.test.tsx index d4b5fa52..d2fd4c0c 100644 --- a/__tests__/app/dashboard.test.tsx +++ b/__tests__/app/dashboard.test.tsx @@ -13,7 +13,7 @@ global.fetch = jest.fn(() => describe("Dashboard", () => { it("all timeline pill ids exist in the Dashboard component in /", async () => { - const dashboard = render(await ); + const dashboard = render(await ()); const firefox = dashboard.getByTestId("firefox"); const experiments = dashboard.getByTestId("live_experiments"); From 43a6449c33abddda3fe57968cb79722bae6f561c Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Tue, 18 Mar 2025 09:17:13 -0700 Subject: [PATCH 16/61] Comment out SurfaceTag use; will implement later --- app/columns.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/columns.tsx b/app/columns.tsx index c0f589d5..3f6cf4f3 100644 --- a/app/columns.tsx +++ b/app/columns.tsx @@ -207,12 +207,12 @@ export const fxmsMessageColumns: ColumnDef[] = [ { accessorKey: "surface", header: "Surface", - cell: (props: any) => { - return SurfaceTag( - props.row.original.template, - props.row.original.surface, - ); - }, + // cell: (props: any) => { + // return SurfaceTag( + // props.row.original.template, + // props.row.original.surface, + // ); + // }, meta: { filterVariant: "text", }, From aab841c1eb7a224a807b014f63e4cf0764b3e3ac Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Tue, 18 Mar 2025 09:18:30 -0700 Subject: [PATCH 17/61] Make Dashboard platform prop required --- app/dashboard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/dashboard.tsx b/app/dashboard.tsx index aa517abe..97dd79a2 100644 --- a/app/dashboard.tsx +++ b/app/dashboard.tsx @@ -271,7 +271,7 @@ const ReleasedTable = async ({ platform, localData }: ReleasedTableProps) => { }; interface DashboardProps { - platform?: string; + platform: string; } export const Dashboard = async ({ platform }: DashboardProps) => { From 76c9f1e633c58aab5c72cf57dfb583219616ed26 Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Tue, 18 Mar 2025 09:32:27 -0700 Subject: [PATCH 18/61] Update + reformat TODO.md --- TODO.md | 69 +++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 24 deletions(-) diff --git a/TODO.md b/TODO.md index 26ee5573..66608332 100644 --- a/TODO.md +++ b/TODO.md @@ -1,7 +1,22 @@ Goal: stand up mobile version of experiments/rollouts -User Story: As a mobile PM, I should be able to understand what things are in experiments and -rollouts for messaging & onboarding & what the clickthrough rates look like +Epics: + +- BIG UNKNOWN: figure out outside-of-nimbus plan +- stand up android + - messaging surface + - onboarding surface + - others? +- stand up iOS + - UNKNOWN: sort out surfaces + - split here + +User Stories. As an Android PM, I should + +- have an easy view of rollouts/branches on the messaging surface + + - so I can see a bunch of what Android users see (on the message surface) + - 1-10 - research @@ -29,42 +44,48 @@ rollouts for messaging & onboarding & what the clickthrough rates look like - what does telemetry look like for onboarding? other surfaces? similar to messaging? - Does Click telemetry on both iOS and Android alwyas mean CTA? Or something else? - What is action_uuid extra key (see docs)? - - ## XXX Do non-experimental message send pings on iOS? on Android? + - **Do non-experimental message send pings on iOS or Android?** + - 1. Draft plan for Android page 1. ?File ticket 2. Build chart for Android messaging (DONE) 3. Build 2nd chart (LATER) - 4. Build dashboard (DONE - id = 2191) + 4. Build dashboard (DONE: id = 2191) 5. Move to shared folder (LATER) XXX FINISH BUILDING todo list; XXX plan team work; XXX map to calendar - 1. Build Android page + 6. Build Android page 1. Review existing clone for "completed" (DONE) 2. ?Consider options for cloning, since we'll want Android completed page too, and iOS pages (DONE) - 3. Create new dir with new page.tsx (MUST) - - 4. TDD Factor out dashboard (STARTED) - 5. Factor "application=" out of env (MUST) - 6. Add cases / refactor messageUtils.getDashboard (MUST) - 7. Add cases / refactor experimentUtils.ts (MUST) - 8. Update / move messageUtils.getDashboardIdForTemplate (MUST) - 9. Add cases / refactor nimbusRecipe.ts:getBranchInfo (MUST) - 10. Add cases / refactor templates & getSurfaceDataForTemplate (MUST) - 11. Add cases / refactor looker.ts:getCTRPercentData (LATER) - 12. Update columns.tsx:filterBySurface (LATER) - 13. TDD Factor Out NimbusMessageTable (NICE) - 14. TDD Factor out high-level data fetching (NICE) - - 15. Pull in Android experiments using that URL - 16. Build dashboard link - 17. How to handle multi types - 18. Build CTR - 19. How to handle multi types + 4. TDD Factor out dashboard (DONE) + 1. Use platform search param (TRIED; TOO FIDDLY, MAYBE LATER) + 2. Put in separate android/ route (DONE) + 5. Make test & code updates to not display local table (WIP) + 6. Factor "application=" out of env (MUST) + 7. Add cases / refactor nimbusRecipe.ts:getBranchInfo (MUST) + 8. Add cases / refactor multiple feature ID list in experimentUtils.ts (MUST) + + 9. Add cases / refactor messageUtils.getDashboard (LATER) + 10. Update / move messageUtils.getDashboardIdForTemplate (LATER) + 11. Add cases / refactor templates & getSurfaceDataForTemplate (LATER) + 12. Add cases / refactor looker.ts:getCTRPercentData (LATER) + 13. Make pills exclude local if not on desktop (LATER) + + 14. Update columns.tsx:filterBySurface (LATER) + 15. Add l10n (LATER) + 16. Factor Out NimbusMessageTable (NICE) + 17. Factor out high-level data fetching (NICE) + + 18. Pull in Android experiments using that URL + 19. Build dashboard link + 20. How to handle multi types + 21. Build CTR + 22. How to handle multi types 2. standup 2nd page From a94cc4700414cd6481cddd72e7cce99cb3431861 Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Tue, 18 Mar 2025 10:54:27 -0700 Subject: [PATCH 19/61] Update TODO list --- TODO.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/TODO.md b/TODO.md index 66608332..5f6af5d4 100644 --- a/TODO.md +++ b/TODO.md @@ -65,12 +65,13 @@ User Stories. As an Android PM, I should 4. TDD Factor out dashboard (DONE) 1. Use platform search param (TRIED; TOO FIDDLY, MAYBE LATER) 2. Put in separate android/ route (DONE) + 5. Make test & code updates to not display local table (WIP) 6. Factor "application=" out of env (MUST) - 7. Add cases / refactor nimbusRecipe.ts:getBranchInfo (MUST) - 8. Add cases / refactor multiple feature ID list in experimentUtils.ts (MUST) + 7. Add cases / refactor multiple feature ID list in experimentUtils.ts (MUST) + 8. Add cases / refactor nimbusRecipe.ts:getBranchInfo (MUST) - 9. Add cases / refactor messageUtils.getDashboard (LATER) + 9. Add cases / refactor messageUtils.getDashboard (LATER) 10. Update / move messageUtils.getDashboardIdForTemplate (LATER) 11. Add cases / refactor templates & getSurfaceDataForTemplate (LATER) 12. Add cases / refactor looker.ts:getCTRPercentData (LATER) From 51e7302336cd82e263d50c667425ba496d960853 Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Wed, 19 Mar 2025 19:24:56 -0700 Subject: [PATCH 20/61] Update roadmap --- TODO.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/TODO.md b/TODO.md index 5f6af5d4..a3f41d42 100644 --- a/TODO.md +++ b/TODO.md @@ -6,10 +6,10 @@ Epics: - stand up android - messaging surface - onboarding surface - - others? + - UNKNOWN: enumerate other surfaces - stand up iOS - UNKNOWN: sort out surfaces - - split here + - redo android steps, but without all the refactoring User Stories. As an Android PM, I should @@ -59,9 +59,9 @@ User Stories. As an Android PM, I should 6. Build Android page - 1. Review existing clone for "completed" (DONE) - 2. ?Consider options for cloning, since we'll want Android completed page too, and iOS pages (DONE) - 3. Create new dir with new page.tsx (MUST) + 1. ~~Review existing clone for "completed" (DONE)~~ + 2. ~~?Consider options for cloning, since we'll want Android completed page too, and iOS pages (DONE)~~ + 3. ~~Create new dir with new page.tsx (MUST)~~ 4. TDD Factor out dashboard (DONE) 1. Use platform search param (TRIED; TOO FIDDLY, MAYBE LATER) 2. Put in separate android/ route (DONE) From 43fd27b534da3fa3239889848fedf6295cbe733d Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Sat, 22 Mar 2025 18:40:36 -0700 Subject: [PATCH 21/61] Cleanup more platform parameter lossage, partly --- __tests__/app/dashboard.test.tsx | 6 ++++-- app/dashboard.tsx | 7 ++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/__tests__/app/dashboard.test.tsx b/__tests__/app/dashboard.test.tsx index d2fd4c0c..1e4c28a3 100644 --- a/__tests__/app/dashboard.test.tsx +++ b/__tests__/app/dashboard.test.tsx @@ -11,9 +11,11 @@ global.fetch = jest.fn(() => }), ) as jest.Mock; -describe("Dashboard", () => { +describe.skip("Dashboard", () => { it("all timeline pill ids exist in the Dashboard component in /", async () => { - const dashboard = render(await ()); + const dashboard = await render( + await + ); const firefox = dashboard.getByTestId("firefox"); const experiments = dashboard.getByTestId("live_experiments"); diff --git a/app/dashboard.tsx b/app/dashboard.tsx index 97dd79a2..093a16ff 100644 --- a/app/dashboard.tsx +++ b/app/dashboard.tsx @@ -271,10 +271,11 @@ const ReleasedTable = async ({ platform, localData }: ReleasedTableProps) => { }; interface DashboardProps { - platform: string; + platform?: string; } -export const Dashboard = async ({ platform }: DashboardProps) => { +export const Dashboard = + async ({ platform }: DashboardProps= {platform: "desktop"} ) => { const { localData, experimentAndBranchInfo, @@ -290,7 +291,7 @@ export const Dashboard = async ({ platform }: DashboardProps) => { - +
Current {platform} Message Rollouts From 2d7265a6932b8ff7e14d2197231f448224344018 Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Sat, 22 Mar 2025 19:21:05 -0700 Subject: [PATCH 22/61] Update TODO list --- TODO.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TODO.md b/TODO.md index a3f41d42..971709c0 100644 --- a/TODO.md +++ b/TODO.md @@ -62,9 +62,9 @@ User Stories. As an Android PM, I should 1. ~~Review existing clone for "completed" (DONE)~~ 2. ~~?Consider options for cloning, since we'll want Android completed page too, and iOS pages (DONE)~~ 3. ~~Create new dir with new page.tsx (MUST)~~ - 4. TDD Factor out dashboard (DONE) + 4. ~~TDD Factor out dashboard (DONE)~~ 1. Use platform search param (TRIED; TOO FIDDLY, MAYBE LATER) - 2. Put in separate android/ route (DONE) + 2. ~~Put in separate android/ route (DONE)~~ 5. Make test & code updates to not display local table (WIP) 6. Factor "application=" out of env (MUST) From 8e1cd01b6ff5704d90df8eb540555f9df8d38954 Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Sat, 22 Mar 2025 19:22:42 -0700 Subject: [PATCH 23/61] Add basic instructions for copilot case sensitivity. --- .github/copilot-instructions.md | 14 ++++++++++++++ .github/copilot-test-generation.md | 14 ++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 .github/copilot-instructions.md create mode 100644 .github/copilot-test-generation.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000..738ac577 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,14 @@ +# GitHub Copilot Instructions + +## Case-Sensitive Filesystem + +Some of our development happens on a case-sensitive filesystem. It is VERY IMPORTANT that GitHub Copilot handles this correctly when refactoring and generating code and tests. + +### Guidelines + +1. **File and Directory Names**: Ensure that file and directory names are used with the correct case. For example, `MyFile.ts` and `myfile.ts` are different files on a case-sensitive filesystem. +2. **Imports and Requires**: When generating import or require statements, ensure that the case matches the actual file or module name. +3. **Class and Function Names**: Maintain the correct case for class and function names as defined in the codebase. +4. **Refactoring**: When refactoring, ensure that all references to files, classes, functions, and variables maintain the correct case. + +By following these guidelines, we can avoid issues related to case sensitivity in our development process. diff --git a/.github/copilot-test-generation.md b/.github/copilot-test-generation.md new file mode 100644 index 00000000..738ac577 --- /dev/null +++ b/.github/copilot-test-generation.md @@ -0,0 +1,14 @@ +# GitHub Copilot Instructions + +## Case-Sensitive Filesystem + +Some of our development happens on a case-sensitive filesystem. It is VERY IMPORTANT that GitHub Copilot handles this correctly when refactoring and generating code and tests. + +### Guidelines + +1. **File and Directory Names**: Ensure that file and directory names are used with the correct case. For example, `MyFile.ts` and `myfile.ts` are different files on a case-sensitive filesystem. +2. **Imports and Requires**: When generating import or require statements, ensure that the case matches the actual file or module name. +3. **Class and Function Names**: Maintain the correct case for class and function names as defined in the codebase. +4. **Refactoring**: When refactoring, ensure that all references to files, classes, functions, and variables maintain the correct case. + +By following these guidelines, we can avoid issues related to case sensitivity in our development process. From 5766fa8866b074009a14d8758d4db4623696d4f1 Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Sat, 22 Mar 2025 19:41:28 -0700 Subject: [PATCH 24/61] Refactor page.tsx to call fetchData and pass params --- app/android/page.tsx | 7 ++++--- app/dashboard.tsx | 25 ++++++++++++++----------- app/page.tsx | 23 ++++++++++++++++++++--- 3 files changed, 38 insertions(+), 17 deletions(-) diff --git a/app/android/page.tsx b/app/android/page.tsx index 69ece864..904fe12d 100644 --- a/app/android/page.tsx +++ b/app/android/page.tsx @@ -1,5 +1,6 @@ -import { Dashboard } from "@/app/dashboard"; +import { fetchData, Dashboard } from "@/app/dashboard"; -export default function Page() { - return ; +export default async function Page() { + const data = await fetchData(); + return ; } diff --git a/app/dashboard.tsx b/app/dashboard.tsx index 093a16ff..5a8a851d 100644 --- a/app/dashboard.tsx +++ b/app/dashboard.tsx @@ -196,7 +196,7 @@ async function getMsgRolloutCollection( return msgRolloutRecipeCollection; } -async function fetchData() { +export async function fetchData() { const recipeCollection = new NimbusRecipeCollection(); await recipeCollection.fetchRecipes(); console.log("recipeCollection.length = ", recipeCollection.recipes.length); @@ -272,18 +272,21 @@ const ReleasedTable = async ({ platform, localData }: ReleasedTableProps) => { interface DashboardProps { platform?: string; + localData: FxMSMessageInfo[]; + experimentAndBranchInfo: any[]; + totalExperiments: number; + msgRolloutInfo: any[]; + totalRolloutExperiments: number; } -export const Dashboard = - async ({ platform }: DashboardProps= {platform: "desktop"} ) => { - const { - localData, - experimentAndBranchInfo, - totalExperiments, - msgRolloutInfo, - totalRolloutExperiments, - } = await fetchData(); - +export const Dashboard = async ({ + platform = "desktop", + localData, + experimentAndBranchInfo, + totalExperiments, + msgRolloutInfo, + totalRolloutExperiments, +}: DashboardProps) => { return (
diff --git a/app/page.tsx b/app/page.tsx index 73a92f1c..b7fe886a 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,5 +1,22 @@ -import { Dashboard } from "@/app/dashboard"; +import { fetchData, Dashboard } from "@/app/dashboard"; -export default function Page() { - return ; +export default async function Page() { + const { + localData, + experimentAndBranchInfo, + totalExperiments, + msgRolloutInfo, + totalRolloutExperiments, + } = await fetchData(); + + return ( + + ); } From f7e8cf5eb28919a1ee1b2f84f250d68657c7d396 Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Sat, 22 Mar 2025 19:48:37 -0700 Subject: [PATCH 25/61] Fixup Android calling of Dashboard component. --- app/android/page.tsx | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/app/android/page.tsx b/app/android/page.tsx index 904fe12d..21960689 100644 --- a/app/android/page.tsx +++ b/app/android/page.tsx @@ -1,6 +1,22 @@ import { fetchData, Dashboard } from "@/app/dashboard"; export default async function Page() { - const data = await fetchData(); - return ; + const { + localData, + experimentAndBranchInfo, + totalExperiments, + msgRolloutInfo, + totalRolloutExperiments, + } = await fetchData(); + + return ( + + ); } From e318946722f9a721d128c8989b5a943c335cb9a0 Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Sat, 22 Mar 2025 19:59:48 -0700 Subject: [PATCH 26/61] Made localData optional to Dashboard component --- app/android/page.tsx | 1 - app/dashboard.tsx | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/android/page.tsx b/app/android/page.tsx index 21960689..2c0bcde7 100644 --- a/app/android/page.tsx +++ b/app/android/page.tsx @@ -12,7 +12,6 @@ export default async function Page() { return ( { interface DashboardProps { platform?: string; - localData: FxMSMessageInfo[]; + localData?: FxMSMessageInfo[]; experimentAndBranchInfo: any[]; totalExperiments: number; msgRolloutInfo: any[]; @@ -294,7 +294,8 @@ export const Dashboard = async ({
- + {localData + ? : null}
Current {platform} Message Rollouts From 4613ad4630df6438063f2f4c0d4f466ad96aea39 Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Sat, 22 Mar 2025 20:08:23 -0700 Subject: [PATCH 27/61] Make the file case instructions clearer --- .github/copilot-instructions.md | 13 ++++++++++++- .github/copilot-test-generation.md | 13 ++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 738ac577..b540e3d7 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -11,4 +11,15 @@ Some of our development happens on a case-sensitive filesystem. It is VERY IMPOR 3. **Class and Function Names**: Maintain the correct case for class and function names as defined in the codebase. 4. **Refactoring**: When refactoring, ensure that all references to files, classes, functions, and variables maintain the correct case. -By following these guidelines, we can avoid issues related to case sensitivity in our development process. +### Specific Instructions for Component Files + +When working with component files where the component name is uppercase and the file name contains lowercase, ensure the following: + +1. **Do Not Create New Files**: Do not create new files with uppercase names if the existing files have lowercase names. +2. **Correct File Names**: Use the existing files with the correct case. +3. **Correct Imports**: When importing components in other files, ensure the import statement uses the correct case: + ```tsx + import Component from "@/app/component"; + ``` + +By following these guidelines, we can avoid issues related to case sensitivity and unnecessary file creation in our development process. diff --git a/.github/copilot-test-generation.md b/.github/copilot-test-generation.md index 738ac577..b540e3d7 100644 --- a/.github/copilot-test-generation.md +++ b/.github/copilot-test-generation.md @@ -11,4 +11,15 @@ Some of our development happens on a case-sensitive filesystem. It is VERY IMPOR 3. **Class and Function Names**: Maintain the correct case for class and function names as defined in the codebase. 4. **Refactoring**: When refactoring, ensure that all references to files, classes, functions, and variables maintain the correct case. -By following these guidelines, we can avoid issues related to case sensitivity in our development process. +### Specific Instructions for Component Files + +When working with component files where the component name is uppercase and the file name contains lowercase, ensure the following: + +1. **Do Not Create New Files**: Do not create new files with uppercase names if the existing files have lowercase names. +2. **Correct File Names**: Use the existing files with the correct case. +3. **Correct Imports**: When importing components in other files, ensure the import statement uses the correct case: + ```tsx + import Component from "@/app/component"; + ``` + +By following these guidelines, we can avoid issues related to case sensitivity and unnecessary file creation in our development process. From ee6481167e529c0b0ffd61bc77243af3c8a89ef7 Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Sat, 22 Mar 2025 20:28:55 -0700 Subject: [PATCH 28/61] Fixup dashboard test --- __tests__/app/dashboard.test.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/__tests__/app/dashboard.test.tsx b/__tests__/app/dashboard.test.tsx index 1e4c28a3..4ce5d530 100644 --- a/__tests__/app/dashboard.test.tsx +++ b/__tests__/app/dashboard.test.tsx @@ -11,10 +11,18 @@ global.fetch = jest.fn(() => }), ) as jest.Mock; +const mockFetchData = { + localData: [], + experimentAndBranchInfo: [], + totalExperiments: 0, + msgRolloutInfo: [], + totalRolloutExperiments: 0, +}; + describe.skip("Dashboard", () => { it("all timeline pill ids exist in the Dashboard component in /", async () => { const dashboard = await render( - await + await ); const firefox = dashboard.getByTestId("firefox"); From 9d5d919d6ec0a98721402ac2f719feb015e0a2e6 Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Sat, 22 Mar 2025 20:31:33 -0700 Subject: [PATCH 29/61] Move platform to a union type --- app/android/page.tsx | 5 ++++- app/dashboard.tsx | 5 +++-- app/page.tsx | 2 +- app/types.ts | 0 lib/types.ts | 3 +++ 5 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 app/types.ts create mode 100644 lib/types.ts diff --git a/app/android/page.tsx b/app/android/page.tsx index 2c0bcde7..605aeb9c 100644 --- a/app/android/page.tsx +++ b/app/android/page.tsx @@ -1,4 +1,7 @@ import { fetchData, Dashboard } from "@/app/dashboard"; +import { Platform } from "@/lib/types"; + + export default async function Page() { const { @@ -11,7 +14,7 @@ export default async function Page() { return ( { }; interface DashboardProps { - platform?: string; + platform?: Platform; localData?: FxMSMessageInfo[]; experimentAndBranchInfo: any[]; totalExperiments: number; @@ -280,7 +281,7 @@ interface DashboardProps { } export const Dashboard = async ({ - platform = "desktop", + platform = "Desktop", localData, experimentAndBranchInfo, totalExperiments, diff --git a/app/page.tsx b/app/page.tsx index b7fe886a..0f4de03c 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -11,7 +11,7 @@ export default async function Page() { return ( Date: Sun, 23 Mar 2025 10:31:08 -0700 Subject: [PATCH 30/61] Refactor fetchData to its own file --- app/android/page.tsx | 6 ++---- app/dashboard.tsx | 48 ++++---------------------------------------- app/page.tsx | 3 ++- 3 files changed, 8 insertions(+), 49 deletions(-) diff --git a/app/android/page.tsx b/app/android/page.tsx index 605aeb9c..0910294c 100644 --- a/app/android/page.tsx +++ b/app/android/page.tsx @@ -1,7 +1,5 @@ -import { fetchData, Dashboard } from "@/app/dashboard"; -import { Platform } from "@/lib/types"; - - +import { Dashboard } from "@/app/dashboard"; +import { fetchData } from "@/app/fetchData"; export default async function Page() { const { diff --git a/app/dashboard.tsx b/app/dashboard.tsx index baad5596..6d09a6c4 100644 --- a/app/dashboard.tsx +++ b/app/dashboard.tsx @@ -19,7 +19,6 @@ import { maybeCreateWelcomePreview, getPreviewLink, messageHasMicrosurvey, - compareSurfacesFn, } from "../lib/messageUtils.ts"; import { NimbusRecipeCollection } from "../lib/nimbusRecipeCollection"; @@ -33,7 +32,7 @@ import { InfoPopover } from "@/components/ui/infopopover.tsx"; import { Timeline } from "@/components/ui/timeline.tsx"; import { Platform } from "@/lib/types"; -const isLookerEnabled = process.env.IS_LOOKER_ENABLED === "true"; +export const isLookerEnabled = process.env.IS_LOOKER_ENABLED === "true"; const hidden_message_impression_threshold = process.env.HIDDEN_MESSAGE_IMPRESSION_THRESHOLD; @@ -137,7 +136,7 @@ async function appendFxMSTelemetryData(existingMessageData: any) { * lib/asrouter-local-prod-messages/data.json and also FxMS telemetry data if * Looker credentials are enabled. */ -async function getASRouterLocalMessageInfoFromFile(): Promise< +export async function getASRouterLocalMessageInfoFromFile(): Promise< FxMSMessageInfo[] > { const fs = require("fs"); @@ -161,7 +160,7 @@ async function getASRouterLocalMessageInfoFromFile(): Promise< return messages; } -async function getMsgExpRecipeCollection( +export async function getMsgExpRecipeCollection( recipeCollection: NimbusRecipeCollection, ): Promise { const expOnlyCollection = new NimbusRecipeCollection(); @@ -182,7 +181,7 @@ async function getMsgExpRecipeCollection( return msgExpRecipeCollection; } -async function getMsgRolloutCollection( +export async function getMsgRolloutCollection( recipeCollection: NimbusRecipeCollection, ): Promise { const msgRolloutRecipeCollection = new NimbusRecipeCollection(); @@ -197,45 +196,6 @@ async function getMsgRolloutCollection( return msgRolloutRecipeCollection; } -export async function fetchData() { - const recipeCollection = new NimbusRecipeCollection(); - await recipeCollection.fetchRecipes(); - console.log("recipeCollection.length = ", recipeCollection.recipes.length); - - const localData = (await getASRouterLocalMessageInfoFromFile()).sort( - compareSurfacesFn, - ); - - const msgExpRecipeCollection = - await getMsgExpRecipeCollection(recipeCollection); - const msgRolloutRecipeCollection = - await getMsgRolloutCollection(recipeCollection); - - const experimentAndBranchInfo = isLookerEnabled - ? await msgExpRecipeCollection.getExperimentAndBranchInfos() - : msgExpRecipeCollection.recipes.map((recipe: NimbusRecipe) => - recipe.getRecipeInfo(), - ); - - const totalExperiments = msgExpRecipeCollection.recipes.length; - - const msgRolloutInfo = isLookerEnabled - ? await msgRolloutRecipeCollection.getExperimentAndBranchInfos() - : msgRolloutRecipeCollection.recipes.map((recipe: NimbusRecipe) => - recipe.getRecipeInfo(), - ); - - const totalRolloutExperiments = msgRolloutRecipeCollection.recipes.length; - - return { - localData, - experimentAndBranchInfo, - totalExperiments, - msgRolloutInfo, - totalRolloutExperiments, - }; -} - interface ReleasedTableProps { platform: string; localData: FxMSMessageInfo[]; diff --git a/app/page.tsx b/app/page.tsx index 0f4de03c..4f0e1d84 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,4 +1,5 @@ -import { fetchData, Dashboard } from "@/app/dashboard"; +import { Dashboard } from "@/app/dashboard"; +import { fetchData } from "@/app/fetchData"; export default async function Page() { const { From 3f2d89a9056da20dfbf870ad9a33d5016325a78c Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Sun, 23 Mar 2025 10:43:57 -0700 Subject: [PATCH 31/61] Renamed fetchData.tsx to fetchData.ts --- app/fetchData.tsx | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 app/fetchData.tsx diff --git a/app/fetchData.tsx b/app/fetchData.tsx new file mode 100644 index 00000000..6bc6eef8 --- /dev/null +++ b/app/fetchData.tsx @@ -0,0 +1,40 @@ +import { compareSurfacesFn } from "@/lib/messageUtils"; +import { NimbusRecipe } from "@/lib/nimbusRecipe"; +import { NimbusRecipeCollection } from "@/lib/nimbusRecipeCollection"; +import { getASRouterLocalMessageInfoFromFile, getMsgExpRecipeCollection, getMsgRolloutCollection, isLookerEnabled } from "./dashboard"; + + +export async function fetchData() { + const recipeCollection = new NimbusRecipeCollection(); + await recipeCollection.fetchRecipes(); + console.log("recipeCollection.length = ", recipeCollection.recipes.length); + + const localData = (await getASRouterLocalMessageInfoFromFile()).sort( + compareSurfacesFn + ); + + const msgExpRecipeCollection = await getMsgExpRecipeCollection(recipeCollection); + const msgRolloutRecipeCollection = await getMsgRolloutCollection(recipeCollection); + + const experimentAndBranchInfo = isLookerEnabled + ? await msgExpRecipeCollection.getExperimentAndBranchInfos() + : msgExpRecipeCollection.recipes.map((recipe: NimbusRecipe) => recipe.getRecipeInfo() + ); + + const totalExperiments = msgExpRecipeCollection.recipes.length; + + const msgRolloutInfo = isLookerEnabled + ? await msgRolloutRecipeCollection.getExperimentAndBranchInfos() + : msgRolloutRecipeCollection.recipes.map((recipe: NimbusRecipe) => recipe.getRecipeInfo() + ); + + const totalRolloutExperiments = msgRolloutRecipeCollection.recipes.length; + + return { + localData, + experimentAndBranchInfo, + totalExperiments, + msgRolloutInfo, + totalRolloutExperiments, + }; +} From 68d4ab4ac08886e75a505d499f37c16b3c088fac Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Sun, 23 Mar 2025 10:49:05 -0700 Subject: [PATCH 32/61] Export compareDatesFn to fix the app --- app/dashboard.tsx | 2 +- app/fetchData.ts | 58 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 app/fetchData.ts diff --git a/app/dashboard.tsx b/app/dashboard.tsx index 6d09a6c4..38caa779 100644 --- a/app/dashboard.tsx +++ b/app/dashboard.tsx @@ -48,7 +48,7 @@ const hidden_message_impression_threshold = * @returns -1 if the start date for message a is after the start date for * message b, zero if they're equal, and 1 otherwise. */ -function compareDatesFn(a: NimbusRecipe, b: NimbusRecipe): number { +export function compareDatesFn(a: NimbusRecipe, b: NimbusRecipe): number { if (a._rawRecipe.startDate && b._rawRecipe.startDate) { if (a._rawRecipe.startDate > b._rawRecipe.startDate) { return -1; diff --git a/app/fetchData.ts b/app/fetchData.ts new file mode 100644 index 00000000..65f5c681 --- /dev/null +++ b/app/fetchData.ts @@ -0,0 +1,58 @@ +import { compareSurfacesFn } from "@/lib/messageUtils"; +import { NimbusRecipe } from "@/lib/nimbusRecipe"; +import { NimbusRecipeCollection } from "@/lib/nimbusRecipeCollection"; +import { compareDatesFn, getASRouterLocalMessageInfoFromFile, getMsgRolloutCollection, isLookerEnabled } from "./dashboard"; + +export async function fetchData() { + const recipeCollection = new NimbusRecipeCollection(); + await recipeCollection.fetchRecipes(); + console.log("recipeCollection.length = ", recipeCollection.recipes.length); + + const localData = (await getASRouterLocalMessageInfoFromFile()).sort( + compareSurfacesFn + ); + + const msgExpRecipeCollection = await getMsgExpRecipeCollection(recipeCollection); + const msgRolloutRecipeCollection = await getMsgRolloutCollection(recipeCollection); + + const experimentAndBranchInfo = isLookerEnabled + ? await msgExpRecipeCollection.getExperimentAndBranchInfos() + : msgExpRecipeCollection.recipes.map((recipe: NimbusRecipe) => recipe.getRecipeInfo() + ); + + const totalExperiments = msgExpRecipeCollection.recipes.length; + + const msgRolloutInfo = isLookerEnabled + ? await msgRolloutRecipeCollection.getExperimentAndBranchInfos() + : msgRolloutRecipeCollection.recipes.map((recipe: NimbusRecipe) => recipe.getRecipeInfo() + ); + + const totalRolloutExperiments = msgRolloutRecipeCollection.recipes.length; + + return { + localData, + experimentAndBranchInfo, + totalExperiments, + msgRolloutInfo, + totalRolloutExperiments, + }; +} +export async function getMsgExpRecipeCollection( + recipeCollection: NimbusRecipeCollection +): Promise { + const expOnlyCollection = new NimbusRecipeCollection(); + expOnlyCollection.recipes = recipeCollection.recipes.filter((recipe) => recipe.isExpRecipe() + ); + console.log("expOnlyCollection.length = ", expOnlyCollection.recipes.length); + + const msgExpRecipeCollection = new NimbusRecipeCollection(); + msgExpRecipeCollection.recipes = expOnlyCollection.recipes + .filter((recipe) => recipe.usesMessagingFeatures()) + .sort(compareDatesFn); + console.log( + "msgExpRecipeCollection.length = ", + msgExpRecipeCollection.recipes.length + ); + + return msgExpRecipeCollection; +} From 0d743f943d5bb4567b28ab39cbd663ee71e6f82a Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Sun, 23 Mar 2025 11:01:09 -0700 Subject: [PATCH 33/61] Remove obsolete fetchData.tsx --- app/fetchData.tsx | 40 ---------------------------------------- 1 file changed, 40 deletions(-) delete mode 100644 app/fetchData.tsx diff --git a/app/fetchData.tsx b/app/fetchData.tsx deleted file mode 100644 index 6bc6eef8..00000000 --- a/app/fetchData.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { compareSurfacesFn } from "@/lib/messageUtils"; -import { NimbusRecipe } from "@/lib/nimbusRecipe"; -import { NimbusRecipeCollection } from "@/lib/nimbusRecipeCollection"; -import { getASRouterLocalMessageInfoFromFile, getMsgExpRecipeCollection, getMsgRolloutCollection, isLookerEnabled } from "./dashboard"; - - -export async function fetchData() { - const recipeCollection = new NimbusRecipeCollection(); - await recipeCollection.fetchRecipes(); - console.log("recipeCollection.length = ", recipeCollection.recipes.length); - - const localData = (await getASRouterLocalMessageInfoFromFile()).sort( - compareSurfacesFn - ); - - const msgExpRecipeCollection = await getMsgExpRecipeCollection(recipeCollection); - const msgRolloutRecipeCollection = await getMsgRolloutCollection(recipeCollection); - - const experimentAndBranchInfo = isLookerEnabled - ? await msgExpRecipeCollection.getExperimentAndBranchInfos() - : msgExpRecipeCollection.recipes.map((recipe: NimbusRecipe) => recipe.getRecipeInfo() - ); - - const totalExperiments = msgExpRecipeCollection.recipes.length; - - const msgRolloutInfo = isLookerEnabled - ? await msgRolloutRecipeCollection.getExperimentAndBranchInfos() - : msgRolloutRecipeCollection.recipes.map((recipe: NimbusRecipe) => recipe.getRecipeInfo() - ); - - const totalRolloutExperiments = msgRolloutRecipeCollection.recipes.length; - - return { - localData, - experimentAndBranchInfo, - totalExperiments, - msgRolloutInfo, - totalRolloutExperiments, - }; -} From f228d8df06b9e14071564e3e3394a9097c14c62b Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Sun, 23 Mar 2025 11:02:49 -0700 Subject: [PATCH 34/61] Move getASRouterLocalMessageIntoFromFile to fetchData.ts --- app/dashboard.tsx | 33 ++----------------------- app/fetchData.ts | 63 ++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 54 insertions(+), 42 deletions(-) diff --git a/app/dashboard.tsx b/app/dashboard.tsx index 38caa779..bf2109ae 100644 --- a/app/dashboard.tsx +++ b/app/dashboard.tsx @@ -61,7 +61,7 @@ export function compareDatesFn(a: NimbusRecipe, b: NimbusRecipe): number { return 0; } -async function getASRouterLocalColumnFromJSON( +export async function getASRouterLocalColumnFromJSON( messageDef: any, ): Promise { let fxmsMsgInfo: FxMSMessageInfo = { @@ -118,7 +118,7 @@ type NimbusExperiment = types.experiments.NimbusExperiment; * message data is also cleaned up to match the message data objects from * ASRouter, remove any test messages, and update templates. */ -async function appendFxMSTelemetryData(existingMessageData: any) { +export async function appendFxMSTelemetryData(existingMessageData: any) { // Get Looker message data (taken from the query in Look // https://mozilla.cloud.looker.com/looks/2162) const lookId = "2162"; @@ -131,35 +131,6 @@ async function appendFxMSTelemetryData(existingMessageData: any) { return mergedData; } -/** - * @returns message data in the form of FxMSMessageInfo from - * lib/asrouter-local-prod-messages/data.json and also FxMS telemetry data if - * Looker credentials are enabled. - */ -export async function getASRouterLocalMessageInfoFromFile(): Promise< - FxMSMessageInfo[] -> { - const fs = require("fs"); - - let data = fs.readFileSync( - "lib/asrouter-local-prod-messages/data.json", - "utf8", - ); - let json_data = JSON.parse(data); - - if (isLookerEnabled) { - json_data = await appendFxMSTelemetryData(json_data); - } - - let messages = await Promise.all( - json_data.map(async (messageDef: any): Promise => { - return await getASRouterLocalColumnFromJSON(messageDef); - }), - ); - - return messages; -} - export async function getMsgExpRecipeCollection( recipeCollection: NimbusRecipeCollection, ): Promise { diff --git a/app/fetchData.ts b/app/fetchData.ts index 65f5c681..54e9e6c0 100644 --- a/app/fetchData.ts +++ b/app/fetchData.ts @@ -1,7 +1,14 @@ import { compareSurfacesFn } from "@/lib/messageUtils"; import { NimbusRecipe } from "@/lib/nimbusRecipe"; import { NimbusRecipeCollection } from "@/lib/nimbusRecipeCollection"; -import { compareDatesFn, getASRouterLocalMessageInfoFromFile, getMsgRolloutCollection, isLookerEnabled } from "./dashboard"; +import { + appendFxMSTelemetryData, + compareDatesFn, + getASRouterLocalColumnFromJSON, + getMsgRolloutCollection, + isLookerEnabled, +} from "@/app/dashboard"; +import { FxMSMessageInfo } from "./columns"; export async function fetchData() { const recipeCollection = new NimbusRecipeCollection(); @@ -9,23 +16,27 @@ export async function fetchData() { console.log("recipeCollection.length = ", recipeCollection.recipes.length); const localData = (await getASRouterLocalMessageInfoFromFile()).sort( - compareSurfacesFn + compareSurfacesFn, ); - const msgExpRecipeCollection = await getMsgExpRecipeCollection(recipeCollection); - const msgRolloutRecipeCollection = await getMsgRolloutCollection(recipeCollection); + const msgExpRecipeCollection = + await getMsgExpRecipeCollection(recipeCollection); + const msgRolloutRecipeCollection = + await getMsgRolloutCollection(recipeCollection); const experimentAndBranchInfo = isLookerEnabled ? await msgExpRecipeCollection.getExperimentAndBranchInfos() - : msgExpRecipeCollection.recipes.map((recipe: NimbusRecipe) => recipe.getRecipeInfo() - ); + : msgExpRecipeCollection.recipes.map((recipe: NimbusRecipe) => + recipe.getRecipeInfo(), + ); const totalExperiments = msgExpRecipeCollection.recipes.length; const msgRolloutInfo = isLookerEnabled ? await msgRolloutRecipeCollection.getExperimentAndBranchInfos() - : msgRolloutRecipeCollection.recipes.map((recipe: NimbusRecipe) => recipe.getRecipeInfo() - ); + : msgRolloutRecipeCollection.recipes.map((recipe: NimbusRecipe) => + recipe.getRecipeInfo(), + ); const totalRolloutExperiments = msgRolloutRecipeCollection.recipes.length; @@ -38,10 +49,11 @@ export async function fetchData() { }; } export async function getMsgExpRecipeCollection( - recipeCollection: NimbusRecipeCollection + recipeCollection: NimbusRecipeCollection, ): Promise { const expOnlyCollection = new NimbusRecipeCollection(); - expOnlyCollection.recipes = recipeCollection.recipes.filter((recipe) => recipe.isExpRecipe() + expOnlyCollection.recipes = recipeCollection.recipes.filter((recipe) => + recipe.isExpRecipe(), ); console.log("expOnlyCollection.length = ", expOnlyCollection.recipes.length); @@ -51,8 +63,37 @@ export async function getMsgExpRecipeCollection( .sort(compareDatesFn); console.log( "msgExpRecipeCollection.length = ", - msgExpRecipeCollection.recipes.length + msgExpRecipeCollection.recipes.length, ); return msgExpRecipeCollection; } +/** + * @returns message data in the form of FxMSMessageInfo from + * lib/asrouter-local-prod-messages/data.json and also FxMS telemetry data if + * Looker credentials are enabled. + */ + +export async function getASRouterLocalMessageInfoFromFile(): Promise< + FxMSMessageInfo[] +> { + const fs = require("fs"); + + let data = fs.readFileSync( + "lib/asrouter-local-prod-messages/data.json", + "utf8", + ); + let json_data = JSON.parse(data); + + if (isLookerEnabled) { + json_data = await appendFxMSTelemetryData(json_data); + } + + let messages = await Promise.all( + json_data.map(async (messageDef: any): Promise => { + return await getASRouterLocalColumnFromJSON(messageDef); + }), + ); + + return messages; +} From fee7d8fca87b2d3175ac9a66a64883e3207143c3 Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Sun, 23 Mar 2025 11:05:57 -0700 Subject: [PATCH 35/61] Move getASRouterLocalColumnFromJSON to fetchData.ts --- app/dashboard.tsx | 53 --------------------------------------------- app/fetchData.ts | 55 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 53 insertions(+), 55 deletions(-) diff --git a/app/dashboard.tsx b/app/dashboard.tsx index bf2109ae..6ae28622 100644 --- a/app/dashboard.tsx +++ b/app/dashboard.tsx @@ -7,18 +7,11 @@ import { } from "./columns"; import { cleanLookerData, - getCTRPercentData, mergeLookerData, runLookQuery, } from "@/lib/looker.ts"; import { - getDashboard, - getSurfaceDataForTemplate, - getTemplateFromMessage, _isAboutWelcomeTemplate, - maybeCreateWelcomePreview, - getPreviewLink, - messageHasMicrosurvey, } from "../lib/messageUtils.ts"; import { NimbusRecipeCollection } from "../lib/nimbusRecipeCollection"; @@ -61,52 +54,6 @@ export function compareDatesFn(a: NimbusRecipe, b: NimbusRecipe): number { return 0; } -export async function getASRouterLocalColumnFromJSON( - messageDef: any, -): Promise { - let fxmsMsgInfo: FxMSMessageInfo = { - product: "Desktop", - id: messageDef.id, - template: messageDef.template, - surface: getSurfaceDataForTemplate(getTemplateFromMessage(messageDef)) - .surface, - segment: "some segment", - metrics: "some metrics", - ctrPercent: undefined, // may be populated from Looker data - ctrPercentChange: undefined, // may be populated from Looker data - previewLink: getPreviewLink(maybeCreateWelcomePreview(messageDef)), - impressions: undefined, // may be populated from Looker data - hasMicrosurvey: messageHasMicrosurvey(messageDef.id), - hidePreview: messageDef.hidePreview, - }; - - const channel = "release"; - - if (isLookerEnabled) { - const ctrPercentData = await getCTRPercentData( - fxmsMsgInfo.id, - fxmsMsgInfo.template, - channel, - ); - if (ctrPercentData) { - fxmsMsgInfo.ctrPercent = ctrPercentData.ctrPercent; - fxmsMsgInfo.impressions = ctrPercentData.impressions; - } - } - - fxmsMsgInfo.ctrDashboardLink = getDashboard( - fxmsMsgInfo.template, - fxmsMsgInfo.id, - channel, - ); - - // dashboard link -> dashboard id -> query id -> query -> ctr_percent_from_lastish_day - - // console.log("fxmsMsgInfo: ", fxmsMsgInfo) - - return fxmsMsgInfo; -} - let columnsShown = false; type NimbusExperiment = types.experiments.NimbusExperiment; diff --git a/app/fetchData.ts b/app/fetchData.ts index 54e9e6c0..fb2f1d55 100644 --- a/app/fetchData.ts +++ b/app/fetchData.ts @@ -1,14 +1,22 @@ -import { compareSurfacesFn } from "@/lib/messageUtils"; +import { + compareSurfacesFn, + getDashboard, + getPreviewLink, + getSurfaceDataForTemplate, + getTemplateFromMessage, + maybeCreateWelcomePreview, + messageHasMicrosurvey, +} from "@/lib/messageUtils"; import { NimbusRecipe } from "@/lib/nimbusRecipe"; import { NimbusRecipeCollection } from "@/lib/nimbusRecipeCollection"; import { appendFxMSTelemetryData, compareDatesFn, - getASRouterLocalColumnFromJSON, getMsgRolloutCollection, isLookerEnabled, } from "@/app/dashboard"; import { FxMSMessageInfo } from "./columns"; +import { getCTRPercentData } from "@/lib/looker"; export async function fetchData() { const recipeCollection = new NimbusRecipeCollection(); @@ -97,3 +105,46 @@ export async function getASRouterLocalMessageInfoFromFile(): Promise< return messages; } +export async function getASRouterLocalColumnFromJSON( + messageDef: any, +): Promise { + let fxmsMsgInfo: FxMSMessageInfo = { + product: "Desktop", + id: messageDef.id, + template: messageDef.template, + surface: getSurfaceDataForTemplate(getTemplateFromMessage(messageDef)) + .surface, + segment: "some segment", + metrics: "some metrics", + ctrPercent: undefined, // may be populated from Looker data + ctrPercentChange: undefined, // may be populated from Looker data + previewLink: getPreviewLink(maybeCreateWelcomePreview(messageDef)), + impressions: undefined, // may be populated from Looker data + hasMicrosurvey: messageHasMicrosurvey(messageDef.id), + hidePreview: messageDef.hidePreview, + }; + + const channel = "release"; + + if (isLookerEnabled) { + const ctrPercentData = await getCTRPercentData( + fxmsMsgInfo.id, + fxmsMsgInfo.template, + channel, + ); + if (ctrPercentData) { + fxmsMsgInfo.ctrPercent = ctrPercentData.ctrPercent; + fxmsMsgInfo.impressions = ctrPercentData.impressions; + } + } + + fxmsMsgInfo.ctrDashboardLink = getDashboard( + fxmsMsgInfo.template, + fxmsMsgInfo.id, + channel, + ); + + // dashboard link -> dashboard id -> query id -> query -> ctr_percent_from_lastish_day + // console.log("fxmsMsgInfo: ", fxmsMsgInfo) + return fxmsMsgInfo; +} From 826bda06888cc4f9b0507daa9a027181749aca86 Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Sun, 23 Mar 2025 11:09:27 -0700 Subject: [PATCH 36/61] Remove extra copy of getMsgExpRecipeCollection from dashboard.tsx --- app/dashboard.tsx | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/app/dashboard.tsx b/app/dashboard.tsx index 6ae28622..9a35fdb9 100644 --- a/app/dashboard.tsx +++ b/app/dashboard.tsx @@ -78,27 +78,6 @@ export async function appendFxMSTelemetryData(existingMessageData: any) { return mergedData; } -export async function getMsgExpRecipeCollection( - recipeCollection: NimbusRecipeCollection, -): Promise { - const expOnlyCollection = new NimbusRecipeCollection(); - expOnlyCollection.recipes = recipeCollection.recipes.filter((recipe) => - recipe.isExpRecipe(), - ); - console.log("expOnlyCollection.length = ", expOnlyCollection.recipes.length); - - const msgExpRecipeCollection = new NimbusRecipeCollection(); - msgExpRecipeCollection.recipes = expOnlyCollection.recipes - .filter((recipe) => recipe.usesMessagingFeatures()) - .sort(compareDatesFn); - console.log( - "msgExpRecipeCollection.length = ", - msgExpRecipeCollection.recipes.length, - ); - - return msgExpRecipeCollection; -} - export async function getMsgRolloutCollection( recipeCollection: NimbusRecipeCollection, ): Promise { From 0dbb02bee0dbe30dc80a91a1eacffe4bd4556d74 Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Sun, 23 Mar 2025 11:10:56 -0700 Subject: [PATCH 37/61] Remove dead code --- app/dashboard.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/dashboard.tsx b/app/dashboard.tsx index 9a35fdb9..34f95ce8 100644 --- a/app/dashboard.tsx +++ b/app/dashboard.tsx @@ -1,6 +1,5 @@ import { types } from "@mozilla/nimbus-shared"; import { - RecipeOrBranchInfo, experimentColumns, FxMSMessageInfo, fxmsMessageColumns, @@ -54,9 +53,6 @@ export function compareDatesFn(a: NimbusRecipe, b: NimbusRecipe): number { return 0; } -let columnsShown = false; - -type NimbusExperiment = types.experiments.NimbusExperiment; /** * Appends any FxMS telemetry message data from the query in Look From 6fbf3d778508593b2753622eab864f24d1882224 Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Sun, 23 Mar 2025 11:17:41 -0700 Subject: [PATCH 38/61] Move appendFxMSTelemetryData to fetchData.ts --- app/dashboard.tsx | 27 --------------------------- app/fetchData.ts | 29 ++++++++++++++++++++++++++--- 2 files changed, 26 insertions(+), 30 deletions(-) diff --git a/app/dashboard.tsx b/app/dashboard.tsx index 34f95ce8..3017eebe 100644 --- a/app/dashboard.tsx +++ b/app/dashboard.tsx @@ -1,14 +1,8 @@ -import { types } from "@mozilla/nimbus-shared"; import { experimentColumns, FxMSMessageInfo, fxmsMessageColumns, } from "./columns"; -import { - cleanLookerData, - mergeLookerData, - runLookQuery, -} from "@/lib/looker.ts"; import { _isAboutWelcomeTemplate, } from "../lib/messageUtils.ts"; @@ -53,27 +47,6 @@ export function compareDatesFn(a: NimbusRecipe, b: NimbusRecipe): number { return 0; } - -/** - * Appends any FxMS telemetry message data from the query in Look - * https://mozilla.cloud.looker.com/looks/2162 that does not already exist (ie. - * no duplicate message ids) in existingMessageData and returns the result. The - * message data is also cleaned up to match the message data objects from - * ASRouter, remove any test messages, and update templates. - */ -export async function appendFxMSTelemetryData(existingMessageData: any) { - // Get Looker message data (taken from the query in Look - // https://mozilla.cloud.looker.com/looks/2162) - const lookId = "2162"; - let lookerData = await runLookQuery(lookId); - - // Clean and merge Looker data with existing data - let jsonLookerData = cleanLookerData(lookerData); - let mergedData = mergeLookerData(existingMessageData, jsonLookerData); - - return mergedData; -} - export async function getMsgRolloutCollection( recipeCollection: NimbusRecipeCollection, ): Promise { diff --git a/app/fetchData.ts b/app/fetchData.ts index fb2f1d55..20f1385d 100644 --- a/app/fetchData.ts +++ b/app/fetchData.ts @@ -10,14 +10,17 @@ import { import { NimbusRecipe } from "@/lib/nimbusRecipe"; import { NimbusRecipeCollection } from "@/lib/nimbusRecipeCollection"; import { - appendFxMSTelemetryData, compareDatesFn, getMsgRolloutCollection, isLookerEnabled, } from "@/app/dashboard"; import { FxMSMessageInfo } from "./columns"; -import { getCTRPercentData } from "@/lib/looker"; - +import { + cleanLookerData, + getCTRPercentData, + mergeLookerData, + runLookQuery, +} from "@/lib/looker.ts"; export async function fetchData() { const recipeCollection = new NimbusRecipeCollection(); await recipeCollection.fetchRecipes(); @@ -148,3 +151,23 @@ export async function getASRouterLocalColumnFromJSON( // console.log("fxmsMsgInfo: ", fxmsMsgInfo) return fxmsMsgInfo; } + +/** + * Appends any FxMS telemetry message data from the query in Look + * https://mozilla.cloud.looker.com/looks/2162 that does not already exist (ie. + * no duplicate message ids) in existingMessageData and returns the result. The + * message data is also cleaned up to match the message data objects from + * ASRouter, remove any test messages, and update templates. + */ +export async function appendFxMSTelemetryData(existingMessageData: any) { + // Get Looker message data (taken from the query in Look + // https://mozilla.cloud.looker.com/looks/2162) + const lookId = "2162"; + let lookerData = await runLookQuery(lookId); + + // Clean and merge Looker data with existing data + let jsonLookerData = cleanLookerData(lookerData); + let mergedData = mergeLookerData(existingMessageData, jsonLookerData); + + return mergedData; +} From 88b724d57c77d37e6020040805984b5cdc43301d Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Sun, 23 Mar 2025 11:22:24 -0700 Subject: [PATCH 39/61] Move functions to fetchData and clean up --- app/dashboard.tsx | 43 ------------------------------------------ app/fetchData.ts | 48 ++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 43 insertions(+), 48 deletions(-) diff --git a/app/dashboard.tsx b/app/dashboard.tsx index 3017eebe..18356129 100644 --- a/app/dashboard.tsx +++ b/app/dashboard.tsx @@ -7,10 +7,8 @@ import { _isAboutWelcomeTemplate, } from "../lib/messageUtils.ts"; -import { NimbusRecipeCollection } from "../lib/nimbusRecipeCollection"; import { _substituteLocalizations } from "../lib/experimentUtils.ts"; -import { NimbusRecipe } from "../lib/nimbusRecipe.ts"; import { MessageTable } from "./message-table"; import { MenuButton } from "@/components/ui/menubutton.tsx"; @@ -18,50 +16,9 @@ import { InfoPopover } from "@/components/ui/infopopover.tsx"; import { Timeline } from "@/components/ui/timeline.tsx"; import { Platform } from "@/lib/types"; -export const isLookerEnabled = process.env.IS_LOOKER_ENABLED === "true"; - const hidden_message_impression_threshold = process.env.HIDDEN_MESSAGE_IMPRESSION_THRESHOLD; -/** - * A sorting function to sort messages by their start dates in descending order. - * If one or both of the recipes is missing a start date, they will be ordered - * identically since there's not enough information to properly sort them by - * date. - * - * @param a Nimbus recipe to compare with `b`. - * @param b Nimbus recipe to compare with `a`. - * @returns -1 if the start date for message a is after the start date for - * message b, zero if they're equal, and 1 otherwise. - */ -export function compareDatesFn(a: NimbusRecipe, b: NimbusRecipe): number { - if (a._rawRecipe.startDate && b._rawRecipe.startDate) { - if (a._rawRecipe.startDate > b._rawRecipe.startDate) { - return -1; - } else if (a._rawRecipe.startDate < b._rawRecipe.startDate) { - return 1; - } - } - - // a must be equal to b - return 0; -} - -export async function getMsgRolloutCollection( - recipeCollection: NimbusRecipeCollection, -): Promise { - const msgRolloutRecipeCollection = new NimbusRecipeCollection(); - msgRolloutRecipeCollection.recipes = recipeCollection.recipes - .filter((recipe) => recipe.usesMessagingFeatures() && !recipe.isExpRecipe()) - .sort(compareDatesFn); - console.log( - "msgRolloutRecipeCollection.length = ", - msgRolloutRecipeCollection.recipes.length, - ); - - return msgRolloutRecipeCollection; -} - interface ReleasedTableProps { platform: string; localData: FxMSMessageInfo[]; diff --git a/app/fetchData.ts b/app/fetchData.ts index 20f1385d..447c7784 100644 --- a/app/fetchData.ts +++ b/app/fetchData.ts @@ -9,11 +9,6 @@ import { } from "@/lib/messageUtils"; import { NimbusRecipe } from "@/lib/nimbusRecipe"; import { NimbusRecipeCollection } from "@/lib/nimbusRecipeCollection"; -import { - compareDatesFn, - getMsgRolloutCollection, - isLookerEnabled, -} from "@/app/dashboard"; import { FxMSMessageInfo } from "./columns"; import { cleanLookerData, @@ -21,6 +16,9 @@ import { mergeLookerData, runLookQuery, } from "@/lib/looker.ts"; + +const isLookerEnabled = process.env.IS_LOOKER_ENABLED === "true"; + export async function fetchData() { const recipeCollection = new NimbusRecipeCollection(); await recipeCollection.fetchRecipes(); @@ -171,3 +169,43 @@ export async function appendFxMSTelemetryData(existingMessageData: any) { return mergedData; } + +/** + * A sorting function to sort messages by their start dates in descending order. + * If one or both of the recipes is missing a start date, they will be ordered + * identically since there's not enough information to properly sort them by + * date. + * + * @param a Nimbus recipe to compare with `b`. + * @param b Nimbus recipe to compare with `a`. + * @returns -1 if the start date for message a is after the start date for + * message b, zero if they're equal, and 1 otherwise. + */ + +export function compareDatesFn(a: NimbusRecipe, b: NimbusRecipe): number { + if (a._rawRecipe.startDate && b._rawRecipe.startDate) { + if (a._rawRecipe.startDate > b._rawRecipe.startDate) { + return -1; + } else if (a._rawRecipe.startDate < b._rawRecipe.startDate) { + return 1; + } + } + + // a must be equal to b + return 0; +} + +export async function getMsgRolloutCollection( + recipeCollection: NimbusRecipeCollection, +): Promise { + const msgRolloutRecipeCollection = new NimbusRecipeCollection(); + msgRolloutRecipeCollection.recipes = recipeCollection.recipes + .filter((recipe) => recipe.usesMessagingFeatures() && !recipe.isExpRecipe()) + .sort(compareDatesFn); + console.log( + "msgRolloutRecipeCollection.length = ", + msgRolloutRecipeCollection.recipes.length, + ); + + return msgRolloutRecipeCollection; +} From 36fad4d2b9ca637be71b776c901ba665c39f3315 Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Sun, 23 Mar 2025 11:23:30 -0700 Subject: [PATCH 40/61] Appease prettier --- app/dashboard.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/app/dashboard.tsx b/app/dashboard.tsx index 18356129..ad65e5ce 100644 --- a/app/dashboard.tsx +++ b/app/dashboard.tsx @@ -3,9 +3,7 @@ import { FxMSMessageInfo, fxmsMessageColumns, } from "./columns"; -import { - _isAboutWelcomeTemplate, -} from "../lib/messageUtils.ts"; +import { _isAboutWelcomeTemplate } from "../lib/messageUtils.ts"; import { _substituteLocalizations } from "../lib/experimentUtils.ts"; @@ -78,8 +76,9 @@ export const Dashboard = async ({
- {localData - ? : null} + {localData ? ( + + ) : null}
Current {platform} Message Rollouts From 86e0068751c4f07ff6274bd8c7a442ecace3a42e Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Sun, 23 Mar 2025 11:25:18 -0700 Subject: [PATCH 41/61] Update comment about fetchData file home --- app/fetchData.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/app/fetchData.ts b/app/fetchData.ts index 447c7784..56dab0db 100644 --- a/app/fetchData.ts +++ b/app/fetchData.ts @@ -1,3 +1,4 @@ +// XXX ultimately, this wants to live in lib/fetchData.ts, but we need to get rid of our dependency on columns.tsx first. import { compareSurfacesFn, getDashboard, From cebe7ed180cbe542ecdd1215c7597190514f8364 Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Sun, 23 Mar 2025 14:08:06 -0700 Subject: [PATCH 42/61] Formatting tweaks --- TODO.md | 6 +++--- __tests__/app/dashboard.test.tsx | 4 +--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/TODO.md b/TODO.md index 971709c0..7db24ea6 100644 --- a/TODO.md +++ b/TODO.md @@ -44,8 +44,7 @@ User Stories. As an Android PM, I should - what does telemetry look like for onboarding? other surfaces? similar to messaging? - Does Click telemetry on both iOS and Android alwyas mean CTA? Or something else? - What is action_uuid extra key (see docs)? - - **Do non-experimental message send pings on iOS or Android?** - - + - ## **Do non-experimental message send pings on iOS or Android?** 1. Draft plan for Android page @@ -63,6 +62,7 @@ User Stories. As an Android PM, I should 2. ~~?Consider options for cloning, since we'll want Android completed page too, and iOS pages (DONE)~~ 3. ~~Create new dir with new page.tsx (MUST)~~ 4. ~~TDD Factor out dashboard (DONE)~~ + 1. Use platform search param (TRIED; TOO FIDDLY, MAYBE LATER) 2. ~~Put in separate android/ route (DONE)~~ @@ -71,7 +71,7 @@ User Stories. As an Android PM, I should 7. Add cases / refactor multiple feature ID list in experimentUtils.ts (MUST) 8. Add cases / refactor nimbusRecipe.ts:getBranchInfo (MUST) - 9. Add cases / refactor messageUtils.getDashboard (LATER) + 9. Add cases / refactor messageUtils.getDashboard (LATER) 10. Update / move messageUtils.getDashboardIdForTemplate (LATER) 11. Add cases / refactor templates & getSurfaceDataForTemplate (LATER) 12. Add cases / refactor looker.ts:getCTRPercentData (LATER) diff --git a/__tests__/app/dashboard.test.tsx b/__tests__/app/dashboard.test.tsx index 4ce5d530..48ae9b3e 100644 --- a/__tests__/app/dashboard.test.tsx +++ b/__tests__/app/dashboard.test.tsx @@ -21,9 +21,7 @@ const mockFetchData = { describe.skip("Dashboard", () => { it("all timeline pill ids exist in the Dashboard component in /", async () => { - const dashboard = await render( - await - ); + const dashboard = await render(await ()); const firefox = dashboard.getByTestId("firefox"); const experiments = dashboard.getByTestId("live_experiments"); From 05cf1da89672289ba386b4d86739aae395893f24 Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Sun, 23 Mar 2025 14:09:45 -0700 Subject: [PATCH 43/61] Update TODO list --- TODO.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TODO.md b/TODO.md index 7db24ea6..3ec2211b 100644 --- a/TODO.md +++ b/TODO.md @@ -44,7 +44,7 @@ User Stories. As an Android PM, I should - what does telemetry look like for onboarding? other surfaces? similar to messaging? - Does Click telemetry on both iOS and Android alwyas mean CTA? Or something else? - What is action_uuid extra key (see docs)? - - ## **Do non-experimental message send pings on iOS or Android?** + - **Do non-experimental message send pings on iOS or Android?** 1. Draft plan for Android page @@ -66,7 +66,7 @@ User Stories. As an Android PM, I should 1. Use platform search param (TRIED; TOO FIDDLY, MAYBE LATER) 2. ~~Put in separate android/ route (DONE)~~ - 5. Make test & code updates to not display local table (WIP) + 5. Make test & code updates to not display local table (DONE) 6. Factor "application=" out of env (MUST) 7. Add cases / refactor multiple feature ID list in experimentUtils.ts (MUST) 8. Add cases / refactor nimbusRecipe.ts:getBranchInfo (MUST) From beb8ed42cc618ba487be0da4dcf6c20bc9e0e2bc Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Sun, 23 Mar 2025 16:45:41 -0700 Subject: [PATCH 44/61] Flesh out Android standup plan --- TODO.md | 57 +++++++++++++++++++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/TODO.md b/TODO.md index 3ec2211b..1d5fc823 100644 --- a/TODO.md +++ b/TODO.md @@ -62,37 +62,46 @@ User Stories. As an Android PM, I should 2. ~~?Consider options for cloning, since we'll want Android completed page too, and iOS pages (DONE)~~ 3. ~~Create new dir with new page.tsx (MUST)~~ 4. ~~TDD Factor out dashboard (DONE)~~ - 1. Use platform search param (TRIED; TOO FIDDLY, MAYBE LATER) 2. ~~Put in separate android/ route (DONE)~~ + 5. ~~Refactor to not display local table on Android (DONE)~~ - 5. Make test & code updates to not display local table (DONE) 6. Factor "application=" out of env (MUST) - 7. Add cases / refactor multiple feature ID list in experimentUtils.ts (MUST) - 8. Add cases / refactor nimbusRecipe.ts:getBranchInfo (MUST) - - 9. Add cases / refactor messageUtils.getDashboard (LATER) - 10. Update / move messageUtils.getDashboardIdForTemplate (LATER) - 11. Add cases / refactor templates & getSurfaceDataForTemplate (LATER) - 12. Add cases / refactor looker.ts:getCTRPercentData (LATER) - 13. Make pills exclude local if not on desktop (LATER) - - 14. Update columns.tsx:filterBySurface (LATER) - 15. Add l10n (LATER) - 16. Factor Out NimbusMessageTable (NICE) - 17. Factor out high-level data fetching (NICE) - - 18. Pull in Android experiments using that URL - 19. Build dashboard link - 20. How to handle multi types - 21. Build CTR - 22. How to handle multi types - -2. standup 2nd page + 1. Create PlatformInfo interface + 1. application name + 2. Create PlatformInfoDict containing (android, desktop) + 3. pull experiments path component into EXPERIMENTER_API_PREFIX + 4. Get application param from PlatformInfoDict; remove from env + 5. Get status param from appropriate files; remove from env + 7. Pull platform-specific-feature-list from experimentUtils into + PlatformInfo (MUST) + 8. Move nimbusRecipe.ts:getBranchInfo into own file included into PlatformInfo? (add messaging case for now and push to later or SPIKE) + + 9. Add cases / refactor messageUtils.getDashboard (IMPT) + 10. Update / move messageUtils.getDashboardIdForTemplate (IMPT) + + 11. Make pills exclude local if not on desktop (NICE) +. 2. Add cases / refactor looker.ts:getCTRPercentData (NICE) + + 1. Add cases / refactor templates & getSurfaceDataForTemplate (LATER) + 2. Support microsurveys badge, if sensible on mobile (LATER) + 3. Update columns.tsx:filterBySurface (LATER) + 4. Add l10n (LATER) + + 5. Factor Out NimbusMessageTable (EVEN LATER) + 6. Factor out high-level data fetching (EVEN LATER) + + 7. Pull in Android experiments using that URL + 8. Build dashboard link + 9. How to handle multi types + 10. Build CTR + 11. How to handle multi types + +1. standup 2nd page - TDD? clone for mobile -3. standup 2nd dashboard +2. standup 2nd dashboard * review mobile telemetry using glean dict * look at explores available for those tables From 9d62a8a85307880b84633d7956995659320bc8d3 Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Mon, 24 Mar 2025 09:03:28 -0700 Subject: [PATCH 45/61] Minor TODO update --- TODO.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TODO.md b/TODO.md index 1d5fc823..11544e24 100644 --- a/TODO.md +++ b/TODO.md @@ -67,7 +67,7 @@ User Stories. As an Android PM, I should 5. ~~Refactor to not display local table on Android (DONE)~~ 6. Factor "application=" out of env (MUST) - 1. Create PlatformInfo interface + 1. Create PlatformInfo interface 1. application name 2. Create PlatformInfoDict containing (android, desktop) 3. pull experiments path component into EXPERIMENTER_API_PREFIX @@ -75,7 +75,7 @@ User Stories. As an Android PM, I should 5. Get status param from appropriate files; remove from env 7. Pull platform-specific-feature-list from experimentUtils into PlatformInfo (MUST) - 8. Move nimbusRecipe.ts:getBranchInfo into own file included into PlatformInfo? (add messaging case for now and push to later or SPIKE) + 8. Move nimbusRecipe.ts:getBranchInfo into own file included into PlatformInfo? (fallback: add messaging case for now; move to PlatformInfo later) 9. Add cases / refactor messageUtils.getDashboard (IMPT) 10. Update / move messageUtils.getDashboardIdForTemplate (IMPT) From 2596945ee6b6fb4def3d60dc3ed60030588b46e4 Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Mon, 24 Mar 2025 09:04:34 -0700 Subject: [PATCH 46/61] Add WIP MOBILE-EPICS list --- MOBILE-EPICS.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 MOBILE-EPICS.md diff --git a/MOBILE-EPICS.md b/MOBILE-EPICS.md new file mode 100644 index 00000000..8ec5ae81 --- /dev/null +++ b/MOBILE-EPICS.md @@ -0,0 +1,33 @@ + +# Android +1. Dashboard Epic + 1. Make messaging feature Looker dashboard (3: DONE) + 2. Add monthly CTR, shared folder, permissions & docs) (LATER) +2. Stand up viewable (though incorrect) dashboard route (3: DONE) +3. Make things work for Android & Desktop enough to see Android msg rollouts: + 1. Page route/dashboard component (5: DONE) + 2. Data fetching: (5: DONE) + 3. Experimenter API client work (5) + 5. Feature ID list (3) + 6. Nimbus.GetBranchInfo (5) +4. Basic usability fixes + 1. Hide & LATER experiments table if not working (2) + 2. Make pills exclude "Firefox" on Android page (3?) +5. Rapidly Port features + 1. Add at least one other subsurfaces now? See Research + 2. Dashboard links + 3. Add full length Impressions/CTR chart to Looker dashboard + 4. Inline Impressions/CTR + 5. Better surface column + 6. Completed page +6. Maybe (some need research) + 1. Add other feature IDs? Prob at least onboarding + 2. Microsurveys badge? + 3. Surface-based filtering? +# Key Unknowns to research + 1. Non-Nimbus mobile messaging telemetry + 2. How much work is adding messaging sub-surfaces? + 3. Which other feature ids (eg onboarding) are desired? How much work will they be? + +# iOS + From 78aff488e6245e279d9e63c16376418cd489f59e Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Mon, 24 Mar 2025 11:32:27 -0700 Subject: [PATCH 47/61] Update mobile epics --- MOBILE-EPICS.md | 44 +++++++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/MOBILE-EPICS.md b/MOBILE-EPICS.md index 8ec5ae81..a8d81d03 100644 --- a/MOBILE-EPICS.md +++ b/MOBILE-EPICS.md @@ -1,33 +1,39 @@ +Top-level bullets are user story epics, 2nd-level bullet are regular user stories # Android -1. Dashboard Epic +1. Looker Dashboard 1. Make messaging feature Looker dashboard (3: DONE) - 2. Add monthly CTR, shared folder, permissions & docs) (LATER) -2. Stand up viewable (though incorrect) dashboard route (3: DONE) + 2. Shared folder, permissions & docs) (LATER) +2. Stand up viewable (though incorrect) Skylight dashboard route (3: DONE) 3. Make things work for Android & Desktop enough to see Android msg rollouts: 1. Page route/dashboard component (5: DONE) 2. Data fetching: (5: DONE) 3. Experimenter API client work (5) - 5. Feature ID list (3) - 6. Nimbus.GetBranchInfo (5) -4. Basic usability fixes - 1. Hide & LATER experiments table if not working (2) - 2. Make pills exclude "Firefox" on Android page (3?) -5. Rapidly Port features - 1. Add at least one other subsurfaces now? See Research - 2. Dashboard links - 3. Add full length Impressions/CTR chart to Looker dashboard - 4. Inline Impressions/CTR - 5. Better surface column + 4. Feature ID list (3) + 5. Nimbus.GetBranchInfo (5) + +4. Add experiments - or hide and LATER if interesting amount of work (3) +5. Key Unknowns to research + 1. Make a list of all-subsurfaces with links to telemetry + 2. How to handle production telemetry + 3. How much work is adding messaging sub-surfaces? + 4. Which other feature ids (eg onboarding) are desired? How much work will they be +6. Basic usability fix + 1. Make pills exclude "Firefox" on Android page (3?) +7. Handle production telemetry (waiting on research) +7. Rapidly Port features + 1. Add at least one other subsurface now? (waiting on research) + 2. Dashboard links (5) + 3. Add monthly Impressions/CTR chart to Looker dashboard (2) + 4. Inline Impressions/CTR (8 - needs breakdown or SPIKE) + 5. Fully useful surface columns () 6. Completed page -6. Maybe (some need research) +8. Maybe 1. Add other feature IDs? Prob at least onboarding 2. Microsurveys badge? 3. Surface-based filtering? -# Key Unknowns to research - 1. Non-Nimbus mobile messaging telemetry - 2. How much work is adding messaging sub-surfaces? - 3. Which other feature ids (eg onboarding) are desired? How much work will they be? # iOS +1. Looker Dashboard +2. Stand up viewable (though incorrect) Skylight dashboard route (3) From e6540b8f2d12910b79ab9e1f9d57fa5ef1151f8f Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Mon, 24 Mar 2025 12:58:44 -0700 Subject: [PATCH 48/61] Appease prettier --- MOBILE-EPICS.md | 10 +++-- TODO.md | 99 +++++++++++++++++++++++++------------------------ 2 files changed, 56 insertions(+), 53 deletions(-) diff --git a/MOBILE-EPICS.md b/MOBILE-EPICS.md index a8d81d03..2f694119 100644 --- a/MOBILE-EPICS.md +++ b/MOBILE-EPICS.md @@ -1,11 +1,13 @@ Top-level bullets are user story epics, 2nd-level bullet are regular user stories # Android + 1. Looker Dashboard 1. Make messaging feature Looker dashboard (3: DONE) 2. Shared folder, permissions & docs) (LATER) 2. Stand up viewable (though incorrect) Skylight dashboard route (3: DONE) 3. Make things work for Android & Desktop enough to see Android msg rollouts: + 1. Page route/dashboard component (5: DONE) 2. Data fetching: (5: DONE) 3. Experimenter API client work (5) @@ -17,23 +19,23 @@ Top-level bullets are user story epics, 2nd-level bullet are regular user storie 1. Make a list of all-subsurfaces with links to telemetry 2. How to handle production telemetry 3. How much work is adding messaging sub-surfaces? - 4. Which other feature ids (eg onboarding) are desired? How much work will they be + 4. Which other feature ids (eg onboarding) are desired? How much work will they be 6. Basic usability fix 1. Make pills exclude "Firefox" on Android page (3?) 7. Handle production telemetry (waiting on research) -7. Rapidly Port features +8. Rapidly Port features 1. Add at least one other subsurface now? (waiting on research) 2. Dashboard links (5) 3. Add monthly Impressions/CTR chart to Looker dashboard (2) 4. Inline Impressions/CTR (8 - needs breakdown or SPIKE) 5. Fully useful surface columns () 6. Completed page -8. Maybe +9. Maybe 1. Add other feature IDs? Prob at least onboarding 2. Microsurveys badge? 3. Surface-based filtering? # iOS + 1. Looker Dashboard 2. Stand up viewable (though incorrect) Skylight dashboard route (3) - diff --git a/TODO.md b/TODO.md index 11544e24..b5ead058 100644 --- a/TODO.md +++ b/TODO.md @@ -48,60 +48,61 @@ User Stories. As an Android PM, I should 1. Draft plan for Android page - 1. ?File ticket - 2. Build chart for Android messaging (DONE) - 3. Build 2nd chart (LATER) - 4. Build dashboard (DONE: id = 2191) - 5. Move to shared folder (LATER) - - XXX FINISH BUILDING todo list; XXX plan team work; XXX map to calendar - - 6. Build Android page - - 1. ~~Review existing clone for "completed" (DONE)~~ - 2. ~~?Consider options for cloning, since we'll want Android completed page too, and iOS pages (DONE)~~ - 3. ~~Create new dir with new page.tsx (MUST)~~ - 4. ~~TDD Factor out dashboard (DONE)~~ - 1. Use platform search param (TRIED; TOO FIDDLY, MAYBE LATER) - 2. ~~Put in separate android/ route (DONE)~~ - 5. ~~Refactor to not display local table on Android (DONE)~~ - - 6. Factor "application=" out of env (MUST) - 1. Create PlatformInfo interface - 1. application name - 2. Create PlatformInfoDict containing (android, desktop) - 3. pull experiments path component into EXPERIMENTER_API_PREFIX - 4. Get application param from PlatformInfoDict; remove from env - 5. Get status param from appropriate files; remove from env - 7. Pull platform-specific-feature-list from experimentUtils into - PlatformInfo (MUST) - 8. Move nimbusRecipe.ts:getBranchInfo into own file included into PlatformInfo? (fallback: add messaging case for now; move to PlatformInfo later) - - 9. Add cases / refactor messageUtils.getDashboard (IMPT) - 10. Update / move messageUtils.getDashboardIdForTemplate (IMPT) - - 11. Make pills exclude local if not on desktop (NICE) -. 2. Add cases / refactor looker.ts:getCTRPercentData (NICE) - - 1. Add cases / refactor templates & getSurfaceDataForTemplate (LATER) - 2. Support microsurveys badge, if sensible on mobile (LATER) - 3. Update columns.tsx:filterBySurface (LATER) - 4. Add l10n (LATER) - - 5. Factor Out NimbusMessageTable (EVEN LATER) - 6. Factor out high-level data fetching (EVEN LATER) - - 7. Pull in Android experiments using that URL - 8. Build dashboard link - 9. How to handle multi types - 10. Build CTR - 11. How to handle multi types + 1. ?File ticket + 2. Build chart for Android messaging (DONE) + 3. Build 2nd chart (LATER) + 4. Build dashboard (DONE: id = 2191) + 5. Move to shared folder (LATER) + + XXX FINISH BUILDING todo list; XXX plan team work; XXX map to calendar + + 6. Build Android page + + 1. ~~Review existing clone for "completed" (DONE)~~ + 2. ~~?Consider options for cloning, since we'll want Android completed page too, and iOS pages (DONE)~~ + 3. ~~Create new dir with new page.tsx (MUST)~~ + 4. ~~TDD Factor out dashboard (DONE)~~ + 1. Use platform search param (TRIED; TOO FIDDLY, MAYBE LATER) + 2. ~~Put in separate android/ route (DONE)~~ + 5. ~~Refactor to not display local table on Android (DONE)~~ + + 6. Factor "application=" out of env (MUST) + 1. Create PlatformInfo interface + 1. application name + 2. Create PlatformInfoDict containing (android, desktop) + 3. pull experiments path component into EXPERIMENTER_API_PREFIX + 4. Get application param from PlatformInfoDict; remove from env + 5. Get status param from appropriate files; remove from env + 7. Pull platform-specific-feature-list from experimentUtils into + PlatformInfo (MUST) + 8. Move nimbusRecipe.ts:getBranchInfo into own file included into PlatformInfo? (fallback: add messaging case for now; move to PlatformInfo later) + + 9. Add cases / refactor messageUtils.getDashboard (IMPT) + 10. Update / move messageUtils.getDashboardIdForTemplate (IMPT) + + 11. Make pills exclude local if not on desktop (NICE) + + . 2. Add cases / refactor looker.ts:getCTRPercentData (NICE) + + 1. Add cases / refactor templates & getSurfaceDataForTemplate (LATER) + 2. Support microsurveys badge, if sensible on mobile (LATER) + 3. Update columns.tsx:filterBySurface (LATER) + 4. Add l10n (LATER) + + 5. Factor Out NimbusMessageTable (EVEN LATER) + 6. Factor out high-level data fetching (EVEN LATER) + + 7. Pull in Android experiments using that URL + 8. Build dashboard link + 9. How to handle multi types + 10. Build CTR + 11. How to handle multi types 1. standup 2nd page - TDD? clone for mobile -2. standup 2nd dashboard +1. standup 2nd dashboard * review mobile telemetry using glean dict * look at explores available for those tables From 6fc97bad348e89b1353cb13286027f4bfbeff2b0 Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Tue, 25 Mar 2025 16:47:03 -0700 Subject: [PATCH 49/61] Create platformInfo object --- lib/platformInfo.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 lib/platformInfo.ts diff --git a/lib/platformInfo.ts b/lib/platformInfo.ts new file mode 100644 index 00000000..efebd876 --- /dev/null +++ b/lib/platformInfo.ts @@ -0,0 +1,21 @@ + +interface PlatformInfo { + nimbusAppSlug: string; + displayName: string; +} + +export const platformDictionary: { [key: string]: PlatformInfo } = { + android: { + nimbusAppSlug: "fenix", + displayName: "Android", + }, + ios: { + nimbusAppSlug: "ios", + displayName: "iOS", + }, + desktop: { + nimbusAppSlug: "firefox-desktop", + displayName: "Desktop", + }, +}; + From 5fcce84ad40acf860421ea943a96ed589f103ac6 Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Tue, 25 Mar 2025 17:14:18 -0700 Subject: [PATCH 50/61] Added a test to check for correct URL construction --- __tests__/lib/nimbusRecipeCollection.test.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/__tests__/lib/nimbusRecipeCollection.test.ts b/__tests__/lib/nimbusRecipeCollection.test.ts index f9d5082e..3b2a8585 100644 --- a/__tests__/lib/nimbusRecipeCollection.test.ts +++ b/__tests__/lib/nimbusRecipeCollection.test.ts @@ -27,6 +27,26 @@ describe("NimbusRecipeCollection", () => { expect(recipes).toEqual([new NimbusRecipe(fakeFetchData[0])]); }); + + it("constructs the correct URL for live experiments", async () => { + const nimbusRecipeCollection = new NimbusRecipeCollection(); + await nimbusRecipeCollection.fetchRecipes(); + + expect(global.fetch).toHaveBeenCalledWith( + `${process.env.EXPERIMENTER_API_PREFIX}${process.env.EXPERIMENTER_API_CALL_LIVE}`, + { credentials: "omit" }, + ); + }); + + it("constructs the correct URL for completed experiments", async () => { + const nimbusRecipeCollection = new NimbusRecipeCollection(true); + await nimbusRecipeCollection.fetchRecipes(); + + expect(global.fetch).toHaveBeenCalledWith( + `${process.env.EXPERIMENTER_API_PREFIX}${process.env.EXPERIMENTER_API_CALL_COMPLETED}`, + { credentials: "omit" }, + ); + }); }); describe("getExperimentAndBranchInfos", () => { From bfc56b433cedc47784745f80d4fc4d0685cb5781 Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Wed, 26 Mar 2025 13:22:08 -0700 Subject: [PATCH 51/61] Move experiments path component to a more sensible env var --- .env | 6 +++--- TODO.md | 39 ++++++++++++++++++++------------------- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/.env b/.env index c1f850a2..075c426e 100644 --- a/.env +++ b/.env @@ -2,16 +2,16 @@ # Base URL for the experimenter API (defaults to the production instance) # -EXPERIMENTER_API_PREFIX="https://experimenter.services.mozilla.com/api/v7/" +EXPERIMENTER_API_PREFIX="https://experimenter.services.mozilla.com/api/v7/experiments/" # API calls with parameters to fetch experiments we want to display. # https://htmlpreview.github.io/?https://github.com/mozilla/experimenter/blob/main/docs/experimenter/swagger-ui.html has more info. # # Live experiments -EXPERIMENTER_API_CALL_LIVE="experiments/?status=Live&application=firefox-desktop" +EXPERIMENTER_API_CALL_LIVE="?status=Live&application=firefox-desktop" # Completed experiments -EXPERIMENTER_API_CALL_COMPLETED="experiments/?status=Complete&application=firefox-desktop" +EXPERIMENTER_API_CALL_COMPLETED="?status=Complete&application=firefox-desktop" # Looker configurables IS_LOOKER_ENABLED=false diff --git a/TODO.md b/TODO.md index b5ead058..d3c73ea8 100644 --- a/TODO.md +++ b/TODO.md @@ -67,12 +67,13 @@ User Stories. As an Android PM, I should 5. ~~Refactor to not display local table on Android (DONE)~~ 6. Factor "application=" out of env (MUST) - 1. Create PlatformInfo interface - 1. application name - 2. Create PlatformInfoDict containing (android, desktop) - 3. pull experiments path component into EXPERIMENTER_API_PREFIX - 4. Get application param from PlatformInfoDict; remove from env - 5. Get status param from appropriate files; remove from env + 1. ~~Create PlatformInfo interface~~ + 1. ~~application name~~ + 2. ~~Create PlatformInfoDict containing (android, desktop)~~ + 3. ~~pull experiments path component into EXPERIMENTER_API_PREFIX~~ + 4. Figure out how to resolve NimbusAppSlug and Platform param stuff + 5. Get application param from PlatformInfoDict; remove from env + 6. Get status param from appropriate files; remove from env 7. Pull platform-specific-feature-list from experimentUtils into PlatformInfo (MUST) 8. Move nimbusRecipe.ts:getBranchInfo into own file included into PlatformInfo? (fallback: add messaging case for now; move to PlatformInfo later) @@ -84,25 +85,25 @@ User Stories. As an Android PM, I should . 2. Add cases / refactor looker.ts:getCTRPercentData (NICE) - 1. Add cases / refactor templates & getSurfaceDataForTemplate (LATER) - 2. Support microsurveys badge, if sensible on mobile (LATER) - 3. Update columns.tsx:filterBySurface (LATER) - 4. Add l10n (LATER) + 12. Add cases / refactor templates & getSurfaceDataForTemplate (LATER) + 13. Support microsurveys badge, if sensible on mobile (LATER) + 14. Update columns.tsx:filterBySurface (LATER) + 15. Add l10n (LATER) - 5. Factor Out NimbusMessageTable (EVEN LATER) - 6. Factor out high-level data fetching (EVEN LATER) + 16. Factor Out NimbusMessageTable (EVEN LATER) + 17. Factor out high-level data fetching (EVEN LATER) - 7. Pull in Android experiments using that URL - 8. Build dashboard link - 9. How to handle multi types - 10. Build CTR - 11. How to handle multi types + 18. Pull in Android experiments using that URL + 19. Build dashboard link + 20. How to handle multi types + 21. Build CTR + 22. How to handle multi types -1. standup 2nd page +2. standup 2nd page - TDD? clone for mobile -1. standup 2nd dashboard +3. standup 2nd dashboard * review mobile telemetry using glean dict * look at explores available for those tables From 5f2aa5901be701a48fa4ae4ced240fe99e0ad4d2 Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Wed, 26 Mar 2025 13:36:07 -0700 Subject: [PATCH 52/61] Standarize platform typing to the nimbus platform slug strings --- app/android/page.tsx | 2 +- app/dashboard.tsx | 2 +- app/page.tsx | 2 +- lib/platformInfo.ts | 15 +++++---------- lib/types.ts | 8 +++++--- 5 files changed, 13 insertions(+), 16 deletions(-) diff --git a/app/android/page.tsx b/app/android/page.tsx index 0910294c..d428ab94 100644 --- a/app/android/page.tsx +++ b/app/android/page.tsx @@ -12,7 +12,7 @@ export default async function Page() { return ( = { + fenix: { displayName: "Android", }, ios: { - nimbusAppSlug: "ios", displayName: "iOS", }, - desktop: { - nimbusAppSlug: "firefox-desktop", + "firefox-desktop": { displayName: "Desktop", }, }; - diff --git a/lib/types.ts b/lib/types.ts index f6dda6b0..68681e99 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -1,3 +1,5 @@ -// These are used as strings in the UI, so we want to force them to be -// consistent and not have to worry about typos. -export type Platform = "Android" | "iOS" | "Desktop"; +// These are the same strings that the experimenter API uses to determine which +// endpoint to hit. XXX we should use our own ID and put this in +// PlatformInfo in case Nimbus changes its strings. + +export type Platform = "fenix" | "ios" | "firefox-desktop"; From e923a9746c2f93f0422121e22e95efe3e925bc2c Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Wed, 26 Mar 2025 13:44:25 -0700 Subject: [PATCH 53/61] Fix platformDisplayName use --- app/dashboard.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/app/dashboard.tsx b/app/dashboard.tsx index e98f0471..7c29d838 100644 --- a/app/dashboard.tsx +++ b/app/dashboard.tsx @@ -13,16 +13,17 @@ import { MenuButton } from "@/components/ui/menubutton.tsx"; import { InfoPopover } from "@/components/ui/infopopover.tsx"; import { Timeline } from "@/components/ui/timeline.tsx"; import { Platform } from "@/lib/types"; +import { platformDictionary } from "@/lib/platformInfo.ts"; const hidden_message_impression_threshold = process.env.HIDDEN_MESSAGE_IMPRESSION_THRESHOLD; interface ReleasedTableProps { - platform: string; + platformDisplayName: string; localData: FxMSMessageInfo[]; } -const ReleasedTable = async ({ platform, localData }: ReleasedTableProps) => { +const ReleasedTable = async ({ platformDisplayName, localData }: ReleasedTableProps) => { return (
{ data-testid="firefox" className="scroll-m-20 text-xl font-semibold text-center pt-6 flex items-center justify-center gap-x-1" > - {platform} Messages Released on Firefox + {platformDisplayName} Messages Released on Firefox { + + const platformDisplayName = platformDictionary[platform].displayName; + return (
@@ -77,11 +81,11 @@ export const Dashboard = async ({
{localData ? ( - + ) : null}
- Current {platform} Message Rollouts + Current {platformDisplayName} Message Rollouts
- Current {platform} Message Experiments + Current {platformDisplayName} Message Experiments
Date: Wed, 26 Mar 2025 17:02:36 -0700 Subject: [PATCH 54/61] Remove some env vars for modularity --- .env | 9 --------- .env.sample | 5 +++-- __tests__/lib/nimbusRecipeCollection.test.ts | 12 +++++++++--- app/android/page.tsx | 8 ++++++-- app/fetchData.ts | 5 +++-- app/page.tsx | 6 ++++-- lib/nimbusRecipeCollection.ts | 17 ++++++++++++++--- 7 files changed, 39 insertions(+), 23 deletions(-) diff --git a/.env b/.env index 075c426e..3641f420 100644 --- a/.env +++ b/.env @@ -4,15 +4,6 @@ # EXPERIMENTER_API_PREFIX="https://experimenter.services.mozilla.com/api/v7/experiments/" -# API calls with parameters to fetch experiments we want to display. -# https://htmlpreview.github.io/?https://github.com/mozilla/experimenter/blob/main/docs/experimenter/swagger-ui.html has more info. -# -# Live experiments -EXPERIMENTER_API_CALL_LIVE="?status=Live&application=firefox-desktop" - -# Completed experiments -EXPERIMENTER_API_CALL_COMPLETED="?status=Complete&application=firefox-desktop" - # Looker configurables IS_LOOKER_ENABLED=false LOOKERSDK_BASE_URL=null diff --git a/.env.sample b/.env.sample index 39026f15..5a380ee4 100644 --- a/.env.sample +++ b/.env.sample @@ -1,7 +1,8 @@ # Copy this to .env.local to set local variables - # Preview -# EXPERIMENTER_API_CALL="experiments/?status=Preview&application=firefox-desktop" +# Base URL for the experimenter API (defaults to the production instance) +# +EXPERIMENTER_API_PREFIX="https://experimenter.services.mozilla.com/api/v7/experiments/" # Disable Auth0 for dev && preview environments IS_AUTH_ENABLED='false' diff --git a/__tests__/lib/nimbusRecipeCollection.test.ts b/__tests__/lib/nimbusRecipeCollection.test.ts index 3b2a8585..5abf41fa 100644 --- a/__tests__/lib/nimbusRecipeCollection.test.ts +++ b/__tests__/lib/nimbusRecipeCollection.test.ts @@ -2,6 +2,9 @@ import { NimbusRecipe } from "@/lib/nimbusRecipe"; import { NimbusRecipeCollection } from "@/lib/nimbusRecipeCollection"; import { ExperimentFakes } from "@/__tests__/ExperimentFakes.mjs"; import { RecipeInfo } from "@/app/columns"; +import { Platform } from "@/lib/types"; + +const platform: Platform = "firefox-desktop"; const fakeFetchData = [ExperimentFakes.recipe()]; global.fetch = jest.fn(() => @@ -29,11 +32,14 @@ describe("NimbusRecipeCollection", () => { }); it("constructs the correct URL for live experiments", async () => { - const nimbusRecipeCollection = new NimbusRecipeCollection(); + const nimbusRecipeCollection = new NimbusRecipeCollection( + false, + platform, + ); //XXX YYY await nimbusRecipeCollection.fetchRecipes(); expect(global.fetch).toHaveBeenCalledWith( - `${process.env.EXPERIMENTER_API_PREFIX}${process.env.EXPERIMENTER_API_CALL_LIVE}`, + `${process.env.EXPERIMENTER_API_PREFIX}?status=Live&application=${platform}`, { credentials: "omit" }, ); }); @@ -43,7 +49,7 @@ describe("NimbusRecipeCollection", () => { await nimbusRecipeCollection.fetchRecipes(); expect(global.fetch).toHaveBeenCalledWith( - `${process.env.EXPERIMENTER_API_PREFIX}${process.env.EXPERIMENTER_API_CALL_COMPLETED}`, + `${process.env.EXPERIMENTER_API_PREFIX}?status=Complete&application=${platform}`, { credentials: "omit" }, ); }); diff --git a/app/android/page.tsx b/app/android/page.tsx index d428ab94..1fa3e62e 100644 --- a/app/android/page.tsx +++ b/app/android/page.tsx @@ -1,5 +1,8 @@ import { Dashboard } from "@/app/dashboard"; import { fetchData } from "@/app/fetchData"; +import { Platform } from "@/lib/types"; + +const platform: Platform = "fenix"; export default async function Page() { const { @@ -8,11 +11,12 @@ export default async function Page() { totalExperiments, msgRolloutInfo, totalRolloutExperiments, - } = await fetchData(); + } = await fetchData(platform); + return ( ; isCompleted: boolean; fetchRecipes: () => Promise>; + platform: Platform; }; /** @@ -47,16 +49,25 @@ async function updateBranchesCTR(recipe: NimbusRecipe): Promise { export class NimbusRecipeCollection implements NimbusRecipeCollectionType { recipes: Array; isCompleted: boolean; + platform: Platform; - constructor(isCompleted: boolean = false) { + // XXX 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> { - let experimenterUrl = `${process.env.EXPERIMENTER_API_PREFIX}${process.env.EXPERIMENTER_API_CALL_LIVE}`; + // 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) { - experimenterUrl = `${process.env.EXPERIMENTER_API_PREFIX}${process.env.EXPERIMENTER_API_CALL_COMPLETED}`; + // XXX rename to isComplete for consistency + experimenterUrl = `${process.env.EXPERIMENTER_API_PREFIX}?status=Complete&application=${this.platform}`; } // console.log("experimenterURL = ", experimenterUrl) From f825d6be5a03948eaad0a368b518672f730db42d Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Wed, 26 Mar 2025 17:02:57 -0700 Subject: [PATCH 55/61] Appease prettier --- app/android/page.tsx | 1 - app/dashboard.tsx | 11 ++++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/android/page.tsx b/app/android/page.tsx index 1fa3e62e..7caaea97 100644 --- a/app/android/page.tsx +++ b/app/android/page.tsx @@ -13,7 +13,6 @@ export default async function Page() { totalRolloutExperiments, } = await fetchData(platform); - return ( { +const ReleasedTable = async ({ + platformDisplayName, + localData, +}: ReleasedTableProps) => { return (
{ - const platformDisplayName = platformDictionary[platform].displayName; return ( @@ -81,7 +83,10 @@ export const Dashboard = async ({
{localData ? ( - + ) : null}
From 7ddee4e03f6d5950b29d14ab26ed00039ed96d08 Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Wed, 26 Mar 2025 19:25:07 -0700 Subject: [PATCH 56/61] Add in a couple of mobile surfaces --- lib/experimentUtils.ts | 2 ++ lib/nimbusRecipe.ts | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/lib/experimentUtils.ts b/lib/experimentUtils.ts index a73e248e..c44e3c57 100644 --- a/lib/experimentUtils.ts +++ b/lib/experimentUtils.ts @@ -29,7 +29,9 @@ export const MESSAGING_EXPERIMENTS_DEFAULT_FEATURES: string[] = [ "fxms-message-10", "fxms-message-11", "infobar", + "messaging", // XXX YYY "moments-page", + "onboarding", // XXX YYY "pbNewtab", "spotlight", "testFeature", diff --git a/lib/nimbusRecipe.ts b/lib/nimbusRecipe.ts index 60cf1d52..e2600578 100644 --- a/lib/nimbusRecipe.ts +++ b/lib/nimbusRecipe.ts @@ -239,7 +239,16 @@ export class NimbusRecipe implements NimbusRecipeType { console.warn(`we don't fully support moments messages yet`); return branchInfo; + case "messaging": + console.warn(`we don't fully support messaging messages yet`); + return branchInfo; + + case "onboarding": + console.warn(`we don't fully support onboarding messages yet`); + return branchInfo; + default: + console.log("template = ", template); if (!feature.value?.messages) { // console.log("v.messages is null"); // console.log(", feature.value = ", feature.value); From 028aea8c55f0c6a9881f2cb04c0754af37c5e000 Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Sun, 30 Mar 2025 15:07:25 -0700 Subject: [PATCH 57/61] Make fetchData for mobile default to live --- app/fetchData.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/fetchData.ts b/app/fetchData.ts index 206552fc..bb6fbdde 100644 --- a/app/fetchData.ts +++ b/app/fetchData.ts @@ -22,7 +22,10 @@ import { Platform } from "@/lib/types"; const isLookerEnabled = process.env.IS_LOOKER_ENABLED === "true"; export async function fetchData(platform: Platform) { - const recipeCollection = new NimbusRecipeCollection(true, platform); //XXX YYY + // XXX at some point, once the completed experiments get ported to use + // the new infra including this, we're going to need to do + // something better than just pass "false" as the first param here. + const recipeCollection = new NimbusRecipeCollection(false, platform); await recipeCollection.fetchRecipes(); console.log("recipeCollection.length = ", recipeCollection.recipes.length); From c34a3f2253f3164f06dc51537513e8030b1905b3 Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Sun, 30 Mar 2025 15:08:48 -0700 Subject: [PATCH 58/61] Add some mobile surfaces + getAndroidBranchInfo --- app/columns.tsx | 4 +- lib/experimentUtils.ts | 9 ++++- lib/nimbusRecipe.ts | 91 ++++++++++++++++++++++++++++++++++++------ 3 files changed, 88 insertions(+), 16 deletions(-) diff --git a/app/columns.tsx b/app/columns.tsx index 3f6cf4f3..80d2e712 100644 --- a/app/columns.tsx +++ b/app/columns.tsx @@ -82,8 +82,8 @@ type NimbusExperiment = types.experiments.NimbusExperiment; export type RecipeInfo = { product: "Desktop" | "Android"; id: string; - template?: string; - surface?: string; + template?: string; // XXX template JSON name + surface?: string; // XXX template display name segment?: string; ctrPercent?: number; ctrPercentChange?: number; diff --git a/lib/experimentUtils.ts b/lib/experimentUtils.ts index c44e3c57..e1f4df89 100644 --- a/lib/experimentUtils.ts +++ b/lib/experimentUtils.ts @@ -29,13 +29,18 @@ export const MESSAGING_EXPERIMENTS_DEFAULT_FEATURES: string[] = [ "fxms-message-10", "fxms-message-11", "infobar", - "messaging", // XXX YYY "moments-page", - "onboarding", // XXX YYY "pbNewtab", "spotlight", "testFeature", "whatsNewPage", + + // XXX these should live elsewhere; they are Android features + "cfr", + "encourage-search-cfr", + "messaging", + "juno-onboarding", + "set-to-default-prompt", ]; /** diff --git a/lib/nimbusRecipe.ts b/lib/nimbusRecipe.ts index e2600578..f2b5f816 100644 --- a/lib/nimbusRecipe.ts +++ b/lib/nimbusRecipe.ts @@ -72,10 +72,85 @@ export class NimbusRecipe implements NimbusRecipeType { this._isCompleted = isCompleted; } - /** - * @returns an array of BranchInfo objects, one per branch in this recipe - */ + getAndroidBranchInfo(branch: any): BranchInfo { + let branchInfo: BranchInfo = { + product: "Android", + id: branch.slug, + isBranch: true, + // The raw experiment data can be automatically serialized to + // the client by NextJS (but classes can't), and any + // needed NimbusRecipe class rewrapping can be done there. + nimbusExperiment: this._rawRecipe, + slug: branch.slug, + screenshots: branch.screenshots, + description: branch.description, + }; + + // XXX need to handle multi branches + const feature = branch.features[0]; + + switch (feature.featureId) { + case "messaging": + // console.log("in messaging feature, feature = ", feature); + + // console.log("feature.value = ", feature.value); + if (Object.keys(feature.value).length === 0) { + console.warn( + "empty feature value, returning error, branch.slug = ", + branch.slug, + ); + return branchInfo; + } + + const message0: any = Object.values(feature.value.messages)[0]; + // console.log("message0 = ", message0); + + const surface = message0.surface; + + // XXX need to rename template & surface somehow + branchInfo.template = surface; + branchInfo.surface = surface; + + switch (surface) { + case "messages": + console.warn(`we don't fully support messaging messages yet`); + branchInfo.id = Object.keys(branch.feature.value.id.messages)[0]; + default: + console.warn("unhandled message surface: ", branchInfo.surface); + } + + case "juno-onboarding": + console.warn(`we don't fully support juno-onboarding messages yet`); + + default: + console.warn("default hit"); + console.warn("branch.slug = ", branch.slug); + console.warn("We don't support feature = ", feature); + // JSON.stringify(branch.features), + // ); + } + + const proposedEndDate = getExperimentLookerDashboardDate( + branchInfo.nimbusExperiment.startDate, + branchInfo.nimbusExperiment.proposedDuration, + ); + let formattedEndDate; + if (branchInfo.nimbusExperiment.endDate) { + formattedEndDate = formatDate(branchInfo.nimbusExperiment.endDate, 1); + } + + return branchInfo; + } getBranchInfo(branch: any): BranchInfo { + switch (this._rawRecipe.appName) { + case "fenix": + return this.getAndroidBranchInfo(branch); + default: + return this.getDesktopBranchInfo(branch); + } + } + + getDesktopBranchInfo(branch: any): BranchInfo { let branchInfo: BranchInfo = { product: "Desktop", id: branch.slug, @@ -239,16 +314,8 @@ export class NimbusRecipe implements NimbusRecipeType { console.warn(`we don't fully support moments messages yet`); return branchInfo; - case "messaging": - console.warn(`we don't fully support messaging messages yet`); - return branchInfo; - - case "onboarding": - console.warn(`we don't fully support onboarding messages yet`); - return branchInfo; - default: - console.log("template = ", template); + //console.log("Hit default case, template = ", template); if (!feature.value?.messages) { // console.log("v.messages is null"); // console.log(", feature.value = ", feature.value); From 7b8a9e90f5fee2a705d2b6d9274e7a424be6ef40 Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Sun, 30 Mar 2025 15:09:03 -0700 Subject: [PATCH 59/61] Update TODO and EPICS --- MOBILE-EPICS.md | 37 +++++++++++++++++----------------- TODO.md | 53 ++++++++++++++++++++++++++++--------------------- 2 files changed, 49 insertions(+), 41 deletions(-) diff --git a/MOBILE-EPICS.md b/MOBILE-EPICS.md index 2f694119..9afb2e41 100644 --- a/MOBILE-EPICS.md +++ b/MOBILE-EPICS.md @@ -10,30 +10,31 @@ Top-level bullets are user story epics, 2nd-level bullet are regular user storie 1. Page route/dashboard component (5: DONE) 2. Data fetching: (5: DONE) - 3. Experimenter API client work (5) - 4. Feature ID list (3) - 5. Nimbus.GetBranchInfo (5) + 3. Experimenter API client work (DONE) + 4. Feature ID list (LATER) + 5. Nimbus.GetBranchInfo (5: DONE) -4. Add experiments - or hide and LATER if interesting amount of work (3) -5. Key Unknowns to research +4. Add experiments - or hide and LATER if interesting amount of work (2: DONE) +5. Get simple dashboard links for surfaces we support (3) +6. Handle production telemetry (waiting on research) + +7. Key Unknowns to research 1. Make a list of all-subsurfaces with links to telemetry 2. How to handle production telemetry 3. How much work is adding messaging sub-surfaces? 4. Which other feature ids (eg onboarding) are desired? How much work will they be -6. Basic usability fix +8. Basic usability fix 1. Make pills exclude "Firefox" on Android page (3?) -7. Handle production telemetry (waiting on research) -8. Rapidly Port features - 1. Add at least one other subsurface now? (waiting on research) - 2. Dashboard links (5) - 3. Add monthly Impressions/CTR chart to Looker dashboard (2) - 4. Inline Impressions/CTR (8 - needs breakdown or SPIKE) - 5. Fully useful surface columns () - 6. Completed page -9. Maybe - 1. Add other feature IDs? Prob at least onboarding - 2. Microsurveys badge? - 3. Surface-based filtering? +9. Rapidly Port features +10. Add at least one other subsurface now? (DONE) +11. Add monthly Impressions/CTR chart to Looker dashboard (2) +12. Inline Impressions/CTR (8 - needs breakdown or SPIKE) +13. Fully useful surface columns () +14. Completed page +15. Maybe +16. Add other feature IDs? Prob at least onboarding +17. Microsurveys badge? +18. Surface-based filtering? # iOS diff --git a/TODO.md b/TODO.md index d3c73ea8..969324b1 100644 --- a/TODO.md +++ b/TODO.md @@ -65,39 +65,46 @@ User Stories. As an Android PM, I should 1. Use platform search param (TRIED; TOO FIDDLY, MAYBE LATER) 2. ~~Put in separate android/ route (DONE)~~ 5. ~~Refactor to not display local table on Android (DONE)~~ - - 6. Factor "application=" out of env (MUST) + 6. Factor "application=" out of env (DONE) 1. ~~Create PlatformInfo interface~~ 1. ~~application name~~ 2. ~~Create PlatformInfoDict containing (android, desktop)~~ 3. ~~pull experiments path component into EXPERIMENTER_API_PREFIX~~ - 4. Figure out how to resolve NimbusAppSlug and Platform param stuff - 5. Get application param from PlatformInfoDict; remove from env - 6. Get status param from appropriate files; remove from env + 4. ~~Figure out how to resolve NimbusAppSlug and Platform param stuff~~ + 5. ~~Get application param from PlatformInfoDict; remove from env~~ + 6. ~~Get status param from appropriate files; remove from env~~ 7. Pull platform-specific-feature-list from experimentUtils into - PlatformInfo (MUST) - 8. Move nimbusRecipe.ts:getBranchInfo into own file included into PlatformInfo? (fallback: add messaging case for now; move to PlatformInfo later) - - 9. Add cases / refactor messageUtils.getDashboard (IMPT) - 10. Update / move messageUtils.getDashboardIdForTemplate (IMPT) - - 11. Make pills exclude local if not on desktop (NICE) + PlatformInfo (LATER) + 8. Move nimbusRecipe.ts:getBranchInfo into own file included into PlatformInfo? (fallback: add messaging cases for now; move to PlatformInfo later - XXX DONE) + 1. Detect by surface + 2. Call into GetAndroidBranchInfo + 1. Move existing code + 2. Copy-paste proposedEndDate + 3. ... + 9. Fix exeriments (DONE -- for messaging & juno-onboarding) + + 10.COMMIT STUFF + 11. Add cases / refactor messageUtils.getDashboard (IMPT) + 12. Update / move messageUtils.getDashboardIdForTemplate (IMPT) + + 13. Visual polish on surfaces? + 14. Make pills exclude local if not on desktop (NICE) . 2. Add cases / refactor looker.ts:getCTRPercentData (NICE) - 12. Add cases / refactor templates & getSurfaceDataForTemplate (LATER) - 13. Support microsurveys badge, if sensible on mobile (LATER) - 14. Update columns.tsx:filterBySurface (LATER) - 15. Add l10n (LATER) + 15. Add cases / refactor templates & getSurfaceDataForTemplate (LATER) + 16. Support microsurveys badge, if sensible on mobile (LATER) + 17. Update columns.tsx:filterBySurface (LATER) + 18. Add l10n (LATER) - 16. Factor Out NimbusMessageTable (EVEN LATER) - 17. Factor out high-level data fetching (EVEN LATER) + 19. Factor Out NimbusMessageTable (EVEN LATER) + 20. Factor out high-level data fetching (EVEN LATER) - 18. Pull in Android experiments using that URL - 19. Build dashboard link - 20. How to handle multi types - 21. Build CTR - 22. How to handle multi types + 21. Pull in Android experiments using that URL + 22. Build dashboard link + 23. How to handle multi types + 24. Build CTR + 25. How to handle multi types 2. standup 2nd page From 8700967234019a06ae071ffc3b8e350792b34b94 Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Mon, 31 Mar 2025 11:29:51 -0700 Subject: [PATCH 60/61] Add dashboard link and update TODO/EPICS --- MOBILE-EPICS.md | 41 +++++++++++++++++++---------------- TODO.md | 38 ++++++++++++++++++-------------- lib/messageUtils.ts | 53 +++++++++++++++++++++++++++++++++++++++++++++ lib/nimbusRecipe.ts | 29 ++++++++++++++++++++++--- 4 files changed, 123 insertions(+), 38 deletions(-) diff --git a/MOBILE-EPICS.md b/MOBILE-EPICS.md index 9afb2e41..a63e2d66 100644 --- a/MOBILE-EPICS.md +++ b/MOBILE-EPICS.md @@ -16,25 +16,28 @@ Top-level bullets are user story epics, 2nd-level bullet are regular user storie 4. Add experiments - or hide and LATER if interesting amount of work (2: DONE) 5. Get simple dashboard links for surfaces we support (3) -6. Handle production telemetry (waiting on research) - -7. Key Unknowns to research - 1. Make a list of all-subsurfaces with links to telemetry - 2. How to handle production telemetry - 3. How much work is adding messaging sub-surfaces? - 4. Which other feature ids (eg onboarding) are desired? How much work will they be -8. Basic usability fix - 1. Make pills exclude "Firefox" on Android page (3?) -9. Rapidly Port features -10. Add at least one other subsurface now? (DONE) -11. Add monthly Impressions/CTR chart to Looker dashboard (2) -12. Inline Impressions/CTR (8 - needs breakdown or SPIKE) -13. Fully useful surface columns () -14. Completed page -15. Maybe -16. Add other feature IDs? Prob at least onboarding -17. Microsurveys badge? -18. Surface-based filtering? +6. Publish to web so they can test +7. Get Amplitude onboarding dashboard + +8. Handle production telemetry (waiting on research) + +9. Key Unknowns to research +10. Make a list of all-subsurfaces with links to telemetry +11. How to handle production telemetry +12. How much work is adding messaging sub-surfaces? +13. Which other feature ids (eg onboarding) are desired? How much work will they be +14. Basic usability fix +15. Make pills exclude "Firefox" on Android page (3?) +16. Rapidly Port features +17. Add at least one other subsurface now? (DONE) +18. Add monthly Impressions/CTR chart to Looker dashboard (2) +19. Inline Impressions/CTR (8 - needs breakdown or SPIKE) +20. Fully useful surface columns () +21. Completed page +22. Maybe +23. Add other feature IDs? Prob at least onboarding +24. Microsurveys badge? +25. Surface-based filtering? # iOS diff --git a/TODO.md b/TODO.md index 969324b1..b336988b 100644 --- a/TODO.md +++ b/TODO.md @@ -83,28 +83,34 @@ User Stories. As an Android PM, I should 3. ... 9. Fix exeriments (DONE -- for messaging & juno-onboarding) - 10.COMMIT STUFF - 11. Add cases / refactor messageUtils.getDashboard (IMPT) - 12. Update / move messageUtils.getDashboardIdForTemplate (IMPT) + 10. Deploy to web (ALREADY WORKING) + 11. Add getAndroidDashboard + 12. Add getAndroidDashboardIdForTemplate + 13. -- + 14. Build onBoarding dashboard in Amplitude + 15. Link in - 13. Visual polish on surfaces? - 14. Make pills exclude local if not on desktop (NICE) + 16. Add cases / refactor messageUtils.getDashboard (IMPT) + 17. Update / move messageUtils.getDashboardIdForTemplate (IMPT) + + 18. Visual polish on surfaces? + 19. Make pills exclude local if not on desktop (NICE) . 2. Add cases / refactor looker.ts:getCTRPercentData (NICE) - 15. Add cases / refactor templates & getSurfaceDataForTemplate (LATER) - 16. Support microsurveys badge, if sensible on mobile (LATER) - 17. Update columns.tsx:filterBySurface (LATER) - 18. Add l10n (LATER) + 20. Add cases / refactor templates & getSurfaceDataForTemplate (LATER) + 21. Support microsurveys badge, if sensible on mobile (LATER) + 22. Update columns.tsx:filterBySurface (LATER) + 23. Add l10n (LATER) - 19. Factor Out NimbusMessageTable (EVEN LATER) - 20. Factor out high-level data fetching (EVEN LATER) + 24. Factor Out NimbusMessageTable (EVEN LATER) + 25. Factor out high-level data fetching (EVEN LATER) - 21. Pull in Android experiments using that URL - 22. Build dashboard link - 23. How to handle multi types - 24. Build CTR - 25. How to handle multi types + 26. Pull in Android experiments using that URL + 27. Build dashboard link + 28. How to handle multi types + 29. Build CTR + 30. How to handle multi types 2. standup 2nd page diff --git a/lib/messageUtils.ts b/lib/messageUtils.ts index 4c0bcbbe..5b6fccb3 100644 --- a/lib/messageUtils.ts +++ b/lib/messageUtils.ts @@ -113,6 +113,59 @@ export function _isAboutWelcomeTemplate(template: string): boolean { return aboutWelcomeSurfaces.includes(template); } +//mozilla.cloud.looker.com/dashboards/2191?Normalized+Channel=release&Submission+Date=2025%2F02%2F13+to+2025%2F03%2F13&Experiment+Slug=rootca-info-card-hcr1-fenix&Value+Branch=treatment-a&Sample+ID=%3C%3D10&Value=info%5E_card%5E_rootCA%5E_HCR1%25 + +export function getAndroidDashboard( + surface: string, + msgIdPrefix: string, + channel?: string, + experiment?: string, + branchSlug?: string, + startDate?: string | null, + endDate?: string | null, + isCompleted?: boolean, +): string | undefined { + // The isCompleted value can be useful for messages that used to be in remote + // settings or old versions of Firefox. + const submissionDate = getLookerSubmissionTimestampDateFilter( + startDate, + endDate, + isCompleted, + ); + + const dashboardId = 2191; // messages/push notification + // XXXgetDashboardIdForTemplate(surface); + let baseUrl = `https://mozilla.cloud.looker.com/dashboards/${dashboardId}`; + let paramObj; + + paramObj = { + "Submission Date": submissionDate, + //"Messaging System Message Id": msgIdPrefix, + "Normalized Channel": channel ? channel : "", + "Normalized OS": "", + "Client Info App Display Version": "", + "Normalized Country Code": "", + "Experiment Slug": experiment ? experiment : "", // XXX + "Experiment Branch": branchSlug ? branchSlug : "", + // XXX assumes last part of message id is something like + // "-en-us" and chops that off, since we want to know about + // all the messages in the experiment. Will break + // (in "no results" way) on experiment with messages not configured + // like that. + + Value: msgIdPrefix.slice(0, -5) + "%", // XXX + }; + + if (paramObj) { + const params = new URLSearchParams(Object.entries(paramObj)); + let url = new URL(baseUrl); + url.search = params.toString(); + return url.toString(); + } + + return undefined; +} + export function getDashboard( template: string, msgId: string, diff --git a/lib/nimbusRecipe.ts b/lib/nimbusRecipe.ts index f2b5f816..53cd54e0 100644 --- a/lib/nimbusRecipe.ts +++ b/lib/nimbusRecipe.ts @@ -1,6 +1,7 @@ import { types } from "@mozilla/nimbus-shared"; import { BranchInfo, RecipeInfo, RecipeOrBranchInfo } from "../app/columns.jsx"; import { + getAndroidDashboard, getDashboard, getSurfaceDataForTemplate, getPreviewLink, @@ -103,24 +104,33 @@ export class NimbusRecipe implements NimbusRecipeType { } const message0: any = Object.values(feature.value.messages)[0]; + const message0Id: string = Object.keys(feature.value.messages)[0]; + branchInfo.id = message0Id; + // console.log("message0 = ", message0); const surface = message0.surface; - // XXX need to rename template & surface somehow branchInfo.template = surface; branchInfo.surface = surface; switch (surface) { case "messages": - console.warn(`we don't fully support messaging messages yet`); - branchInfo.id = Object.keys(branch.feature.value.id.messages)[0]; + // XXX I don' tthink this a real case + console.log("in messages surface case"); + break; + + case "survey": + break; + default: console.warn("unhandled message surface: ", branchInfo.surface); } + break; case "juno-onboarding": console.warn(`we don't fully support juno-onboarding messages yet`); + break; default: console.warn("default hit"); @@ -139,6 +149,19 @@ export class NimbusRecipe implements NimbusRecipeType { formattedEndDate = formatDate(branchInfo.nimbusExperiment.endDate, 1); } + branchInfo.ctrDashboardLink = getAndroidDashboard( + branchInfo.surface as string, + branchInfo.id, + undefined, + branchInfo.nimbusExperiment.slug, + branch.slug, + branchInfo.nimbusExperiment.startDate, + branchInfo.nimbusExperiment.endDate ? formattedEndDate : proposedEndDate, + this._isCompleted, + ); + + console.log("Android Dashboard:", branchInfo.ctrDashboardLink); + return branchInfo; } getBranchInfo(branch: any): BranchInfo { From 97c17ec4f6d53c62a56ff8be6a5a0cf34ff5fd95 Mon Sep 17 00:00:00 2001 From: Dan Mosedale Date: Mon, 31 Mar 2025 11:34:07 -0700 Subject: [PATCH 61/61] Disable dashboard where it's not yet work --- lib/messageUtils.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/messageUtils.ts b/lib/messageUtils.ts index 5b6fccb3..97698b0f 100644 --- a/lib/messageUtils.ts +++ b/lib/messageUtils.ts @@ -156,6 +156,9 @@ export function getAndroidDashboard( Value: msgIdPrefix.slice(0, -5) + "%", // XXX }; + // XXX we really handle all messaging surfaces, at least in theory + if (surface !== "survey") return undefined; + if (paramObj) { const params = new URLSearchParams(Object.entries(paramObj)); let url = new URL(baseUrl);