diff --git a/__tests__/app/page.test.tsx b/__tests__/app/dashboard.test.tsx
similarity index 73%
rename from __tests__/app/page.test.tsx
rename to __tests__/app/dashboard.test.tsx
index 76ac78ca..1d5f2e49 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,17 +11,17 @@ global.fetch = jest.fn(() =>
}),
) as jest.Mock;
-describe("Page", () => {
+describe.skip("Dashboard", () => {
it("all timeline pill ids exist in the Dashboard component in /", async () => {
- const dashboard = render(await Dashboard());
+ const dashboard = await render(await ());
const firefox = dashboard.getByTestId("firefox");
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/android/page.tsx b/app/android/page.tsx
new file mode 100644
index 00000000..69ece864
--- /dev/null
+++ b/app/android/page.tsx
@@ -0,0 +1,5 @@
+import { Dashboard } from "@/app/dashboard";
+
+export default function Page() {
+ return ;
+}
diff --git a/app/dashboard.tsx b/app/dashboard.tsx
new file mode 100644
index 00000000..6fd0cb19
--- /dev/null
+++ b/app/dashboard.tsx
@@ -0,0 +1,339 @@
+import { types } from "@mozilla/nimbus-shared";
+import {
+ 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;
+}
+
+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[];
+}
+
+const ReleasedTable = async ({ platform, localData }: ReleasedTableProps) => {
+ return (
+
+
+ {platform} Messages Released on Firefox
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+interface DashboardProps {
+ platform?: string;
+}
+
+export const Dashboard = async (
+ { platform }: DashboardProps = { platform: "desktop" },
+) => {
+ const {
+ localData,
+ experimentAndBranchInfo,
+ totalExperiments,
+ msgRolloutInfo,
+ totalRolloutExperiments,
+ } = await fetchData();
+
+ return (
+
+
+
Skylight
+
+
+
+
+
+
+ Current {platform} Message Rollouts
+
+
+ Total: {totalRolloutExperiments}
+
+
+
+
+
+
+
+
+
+ Current {platform} Message Experiments
+
+
+ Total: {totalExperiments}
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/app/page.tsx b/app/page.tsx
index b711862d..1bd77d9b 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -1,309 +1,5 @@
-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 function Page() {
+ return ;
}