diff --git a/CHANGELOG.md b/CHANGELOG.md
index aae64430..6e9c4e2c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,9 @@
+## Tuesday, July 29th, 2025
+
+- Corrected the UX to change what we've been calling "CTR" to what it actually is: "UCTR". Meaning:
+ - Unique CTR (UCTR): Unique Users Who Clicked at Least Once / Unique Users Who Saw the Ad. Sometimes also referred to as User-level CTR.
+ - CTR: Clicks / Impressions
+
## Tuesday, May 6th, 2025
### Added
diff --git a/__tests__/app/message-table.test.tsx b/__tests__/app/message-table.test.tsx
index 756e278a..481964f8 100644
--- a/__tests__/app/message-table.test.tsx
+++ b/__tests__/app/message-table.test.tsx
@@ -185,7 +185,7 @@ describe("MessageTable", () => {
expect(previewButton).not.toBeInTheDocument();
});
- it("displays CTR percentages when Looker dashboard exists and CTR is defined", async () => {
+ it("displays UCTR percentages when Looker dashboard exists and UCTR is defined", async () => {
setMockPlatform("firefox-desktop");
const nimbusRecipeCollection = new NimbusRecipeCollection();
nimbusRecipeCollection.recipes = [
@@ -194,38 +194,38 @@ describe("MessageTable", () => {
const recipeInfos =
(await nimbusRecipeCollection.getExperimentAndBranchInfos()) as RecipeInfo[];
// Setting fake dashboard link in order to render in MessageTable
- recipeInfos[0].branches[0].ctrDashboardLink = "test link";
+ recipeInfos[0].branches[0].uctrDashboardLink = "test link";
render();
const toggleButton = screen.getByTestId("toggleAllRowsButton");
fireEvent.click(toggleButton);
- const ctrMetrics = screen.getByText("12.35% CTR", {
+ const uctrMetrics = screen.getByText("12.35% UCTR", {
exact: false,
});
- expect(recipeInfos[0].branches[0].ctrPercent).toBe(12.35);
- expect(recipeInfos[0].branches[0].ctrDashboardLink).toBeDefined();
- expect(ctrMetrics).toBeInTheDocument();
+ expect(recipeInfos[0].branches[0].uctrPercent).toBe(12.35);
+ expect(recipeInfos[0].branches[0].uctrDashboardLink).toBeDefined();
+ expect(uctrMetrics).toBeInTheDocument();
});
- it("displays 'Dashboard' when Looker dashboard exists but CTR is undefined", () => {
+ it("displays 'Dashboard' when Looker dashboard exists but UCTR is undefined", () => {
const rawRecipe = ExperimentFakes.recipe("test-recipe");
const nimbusRecipe = new NimbusRecipe(rawRecipe);
let recipeInfo = nimbusRecipe.getRecipeInfo();
// Setting fake dashboard link in order to render in MessageTable
- recipeInfo.branches[0].ctrDashboardLink = "test link";
+ recipeInfo.branches[0].uctrDashboardLink = "test link";
render();
const toggleButton = screen.getByTestId("toggleAllRowsButton");
fireEvent.click(toggleButton);
const dashboardLink = screen.getByText("Dashboard");
- const ctrMetrics = screen.queryByText("CTR");
+ const uctrMetrics = screen.queryByText("UCTR");
- expect(recipeInfo.branches[0].ctrPercent).not.toBeDefined();
- expect(recipeInfo.branches[0].ctrDashboardLink).toBeDefined();
+ expect(recipeInfo.branches[0].uctrPercent).not.toBeDefined();
+ expect(recipeInfo.branches[0].uctrDashboardLink).toBeDefined();
expect(dashboardLink).toBeInTheDocument();
- expect(ctrMetrics).not.toBeInTheDocument();
+ expect(uctrMetrics).not.toBeInTheDocument();
});
it("doesn't display any metric when Looker dashboard doesn't exist", () => {
@@ -238,14 +238,14 @@ describe("MessageTable", () => {
const toggleButton = screen.getByTestId("toggleAllRowsButton");
fireEvent.click(toggleButton);
- const ctrMetrics = screen.queryByText("CTR");
+ const uctrMetrics = screen.queryByText("UCTR");
const dashboardLink = screen.queryByText("Dashboard");
- expect(messageTableData[0].branches[0].ctrPercent).not.toBeDefined();
+ expect(messageTableData[0].branches[0].uctrPercent).not.toBeDefined();
expect(
- messageTableData[0].branches[0].ctrDashboardLink,
+ messageTableData[0].branches[0].uctrDashboardLink,
).not.toBeDefined();
- expect(ctrMetrics).not.toBeInTheDocument();
+ expect(uctrMetrics).not.toBeInTheDocument();
expect(dashboardLink).not.toBeInTheDocument();
});
@@ -340,7 +340,7 @@ describe("MessageTable", () => {
});
describe("MessageColumns", () => {
- it("displays CTR percentages when Looker dashboard exists and CTR is defined", async () => {
+ it("displays UCTR percentages when Looker dashboard exists and UCTR is defined", async () => {
const fakeMsgInfo: FxMSMessageInfo = {
product: "Desktop",
id: "test id",
@@ -348,23 +348,23 @@ describe("MessageTable", () => {
surface: "test surface",
segment: "test segment",
metrics: "test metrics",
- ctrPercent: 12.35,
- ctrPercentChange: 2,
- ctrDashboardLink: "test link",
+ uctrPercent: 12.35,
+ uctrPercentChange: 2,
+ uctrDashboardLink: "test link",
impressions: 12899,
};
render(
,
);
- const ctrMetrics = screen.getByText("12.35% CTR", {
+ const uctrMetrics = screen.getByText("12.35% UCTR", {
exact: false,
});
- expect(ctrMetrics).toBeInTheDocument();
+ expect(uctrMetrics).toBeInTheDocument();
});
- it("displays 'Dashboard' when Looker dashboard exists but CTR is undefined", () => {
+ it("displays 'Dashboard' when Looker dashboard exists but UCTR is undefined", () => {
const fakeMsgInfo: FxMSMessageInfo = {
product: "Desktop",
id: "test id",
@@ -372,16 +372,16 @@ describe("MessageTable", () => {
surface: "test surface",
segment: "test segment",
metrics: "test metrics",
- ctrDashboardLink: "test link",
+ uctrDashboardLink: "test link",
};
render(
,
);
- const ctrMetrics = screen.queryByText("CTR");
+ const uctrMetrics = screen.queryByText("UCTR");
const dashboardLink = screen.getByText("Dashboard");
- expect(ctrMetrics).not.toBeInTheDocument();
+ expect(uctrMetrics).not.toBeInTheDocument();
expect(dashboardLink).toBeInTheDocument();
});
@@ -398,10 +398,10 @@ describe("MessageTable", () => {
,
);
- const ctrMetrics = screen.queryByText("CTR");
+ const uctrMetrics = screen.queryByText("UCTR");
const dashboardLink = screen.queryByText("Dashboard");
- expect(ctrMetrics).not.toBeInTheDocument();
+ expect(uctrMetrics).not.toBeInTheDocument();
expect(dashboardLink).not.toBeInTheDocument();
});
@@ -434,7 +434,7 @@ describe("MessageTable", () => {
surface: "test surface",
segment: "test segment",
metrics: "test metrics",
- ctrPercent: 24.3,
+ uctrPercent: 24.3,
impressions: parseInt(impressions!) + 100,
};
const fxmsMsgInfo2: FxMSMessageInfo = {
@@ -444,7 +444,7 @@ describe("MessageTable", () => {
surface: "test surface",
segment: "test segment",
metrics: "test metrics",
- ctrPercent: 24.3,
+ uctrPercent: 24.3,
impressions: parseInt(impressions!) - 100,
};
const fxmsMsgInfo3: FxMSMessageInfo = {
@@ -454,7 +454,7 @@ describe("MessageTable", () => {
surface: "test surface",
segment: "test segment",
metrics: "test metrics",
- ctrPercent: 24.3,
+ uctrPercent: 24.3,
impressions: parseInt(impressions!),
};
render(
@@ -485,7 +485,7 @@ describe("MessageTable", () => {
surface: "Feature Callout (1st screen)",
segment: "test segment",
metrics: "test metrics",
- ctrPercent: 24.3,
+ uctrPercent: 24.3,
impressions: 1000,
};
const fxmsMsgInfo2: FxMSMessageInfo = {
@@ -495,7 +495,7 @@ describe("MessageTable", () => {
surface: "Default About:Welcome Message (1st screen)",
segment: "test segment",
metrics: "test metrics",
- ctrPercent: 24.3,
+ uctrPercent: 24.3,
impressions: 1000,
};
const fxmsMsgInfo3: FxMSMessageInfo = {
@@ -505,7 +505,7 @@ describe("MessageTable", () => {
surface: "Private Browsing New Tab",
segment: "test segment",
metrics: "test metrics",
- ctrPercent: 24.3,
+ uctrPercent: 24.3,
impressions: 1000,
};
@@ -542,7 +542,7 @@ describe("MessageTable", () => {
surface: "Feature Callout (1st screen)",
segment: "test segment",
metrics: "test metrics",
- ctrPercent: 24.3,
+ uctrPercent: 24.3,
impressions: 1000,
};
const fxmsMsgInfo2: FxMSMessageInfo = {
@@ -552,7 +552,7 @@ describe("MessageTable", () => {
surface: "InfoBar",
segment: "test segment",
metrics: "test metrics",
- ctrPercent: 24.3,
+ uctrPercent: 24.3,
impressions: 1000,
};
const fxmsMsgInfo3: FxMSMessageInfo = {
@@ -562,7 +562,7 @@ describe("MessageTable", () => {
surface: "Default About:Welcome Message (1st screen)",
segment: "test segment",
metrics: "test metrics",
- ctrPercent: 24.3,
+ uctrPercent: 24.3,
impressions: 1000,
};
@@ -612,7 +612,7 @@ describe("MessageTable", () => {
surface: "test surface",
segment: "test segment",
metrics: "test metrics",
- ctrDashboardLink: "test link",
+ uctrDashboardLink: "test link",
};
render(
,
diff --git a/__tests__/lib/looker.test.ts b/__tests__/lib/looker.test.ts
index f62064ae..76e4df93 100644
--- a/__tests__/lib/looker.test.ts
+++ b/__tests__/lib/looker.test.ts
@@ -38,69 +38,69 @@ describe("Looker", () => {
expect(queryResult).toEqual(fakeQueryResult);
});
- describe("getSafeCtrPercent", () => {
- it("should correctly format a CTR percentage to 2 decimal places", () => {
- expect(looker.getSafeCtrPercent(0.123456789)).toEqual(12.35);
- expect(looker.getSafeCtrPercent(0.1)).toEqual(10);
- expect(looker.getSafeCtrPercent(0.123)).toEqual(12.3);
- expect(looker.getSafeCtrPercent(0.1235)).toEqual(12.35);
+ describe("getSafeUctrPercent", () => {
+ it("should correctly format a UCTR percentage to 2 decimal places", () => {
+ expect(looker.getSafeUctrPercent(0.123456789)).toEqual(12.35);
+ expect(looker.getSafeUctrPercent(0.1)).toEqual(10);
+ expect(looker.getSafeUctrPercent(0.123)).toEqual(12.3);
+ expect(looker.getSafeUctrPercent(0.1235)).toEqual(12.35);
});
it("should handle zero value", () => {
- expect(looker.getSafeCtrPercent(0)).toEqual(0);
+ expect(looker.getSafeUctrPercent(0)).toEqual(0);
});
});
- describe("getCTRPercentData", () => {
- it("should return the CTR percent for a desktop message with standard template", async () => {
+ describe("getUCTRPercentData", () => {
+ it("should return the UCTR percent for a desktop message with standard template", async () => {
const template = "test_template";
const platform = "firefox-desktop";
const id = "test_query_0";
setMockPlatform(platform);
setMockTemplate(template);
- const ctrPercentData = await looker.getCTRPercentData(
+ const uctrPercentData = await looker.getUCTRPercentData(
id,
platform,
template,
);
- expect(ctrPercentData?.ctrPercent).toEqual(12.35);
- expect(ctrPercentData?.impressions).toEqual(12899);
+ expect(uctrPercentData?.uctrPercent).toEqual(12.35);
+ expect(uctrPercentData?.impressions).toEqual(12899);
});
- it("should return the CTR percent for a desktop message with infobar template", async () => {
+ it("should return the UCTR percent for a desktop message with infobar template", async () => {
const id = "test_query_0";
const platform = "firefox-desktop";
const template = "infobar";
setMockPlatform(platform);
setMockTemplate(template);
- const ctrPercentData = await looker.getCTRPercentData(
+ const uctrPercentData = await looker.getUCTRPercentData(
id,
platform,
template,
);
- expect(ctrPercentData?.ctrPercent).toEqual(12.35);
- expect(ctrPercentData?.impressions).toEqual(8765);
+ expect(uctrPercentData?.uctrPercent).toEqual(12.35);
+ expect(uctrPercentData?.impressions).toEqual(8765);
});
- it("should return the CTR percent for an android message with survey template and extrapolate impressions", async () => {
+ it("should return the UCTR percent for an android message with survey template and extrapolate impressions", async () => {
const id = "test_query_0";
const platform = "fenix";
const template = "survey";
setMockPlatform(platform);
setMockTemplate(template);
- const ctrPercentData = await looker.getCTRPercentData(
+ const uctrPercentData = await looker.getUCTRPercentData(
id,
platform,
template,
);
- expect(ctrPercentData?.ctrPercent).toEqual(12.35);
- expect(ctrPercentData?.impressions).toEqual(12890); // 1289 * 10 (extrapolated)
+ expect(uctrPercentData?.uctrPercent).toEqual(12.35);
+ expect(uctrPercentData?.impressions).toEqual(12890); // 1289 * 10 (extrapolated)
});
it("should return undefined for a standard android message (non-survey template)", async () => {
@@ -110,14 +110,14 @@ describe("Looker", () => {
setMockPlatform(platform);
setMockTemplate(template);
- const ctrPercentData = await looker.getCTRPercentData(
+ const uctrPercentData = await looker.getUCTRPercentData(
id,
platform,
template,
);
// For non-survey Android templates, we expect undefined
- expect(ctrPercentData).toBeUndefined();
+ expect(uctrPercentData).toBeUndefined();
});
});
diff --git a/__tests__/lib/nimbusRecipe.test.ts b/__tests__/lib/nimbusRecipe.test.ts
index 07fc7ac0..c37bda9a 100644
--- a/__tests__/lib/nimbusRecipe.test.ts
+++ b/__tests__/lib/nimbusRecipe.test.ts
@@ -145,8 +145,8 @@ describe("NimbusRecipe", () => {
product: "Desktop",
id: "test-recipe",
segment: "some segment",
- ctrPercent: 0.5,
- ctrPercentChange: 2,
+ uctrPercent: 0.5,
+ uctrPercentChange: 2,
metrics: "some metrics",
experimenterLink: `https://experimenter.services.mozilla.com/nimbus/test-recipe`,
userFacingName: rawRecipe.userFacingName,
@@ -215,7 +215,7 @@ describe("NimbusRecipe", () => {
// use deepEqual and check for the existence of object properties instead.
expect(branchInfo).toEqual({
product: "Desktop",
- ctrDashboardLink: dashboardLink,
+ uctrDashboardLink: dashboardLink,
id: "feature_value_id:treatment-a",
isBranch: true,
nimbusExperiment: AW_RECIPE,
@@ -267,7 +267,7 @@ describe("NimbusRecipe", () => {
expect(branchInfo).toEqual({
product: "Desktop",
- ctrDashboardLink: dashboardLink,
+ uctrDashboardLink: dashboardLink,
id: "feature_value_id:treatment-a",
isBranch: true,
nimbusExperiment: AW_RECIPE_NO_SCREENS,
@@ -319,7 +319,7 @@ describe("NimbusRecipe", () => {
template: "survey",
screenshots: ["screenshotURI"],
description: "control description",
- ctrDashboardLink: dashboardLink,
+ uctrDashboardLink: dashboardLink,
});
});
diff --git a/__tests__/lib/nimbusRecipeCollection.test.ts b/__tests__/lib/nimbusRecipeCollection.test.ts
index b99e206d..69a8cc26 100644
--- a/__tests__/lib/nimbusRecipeCollection.test.ts
+++ b/__tests__/lib/nimbusRecipeCollection.test.ts
@@ -70,7 +70,7 @@ describe("NimbusRecipeCollection", () => {
});
describe("getExperimentAndBranchInfos", () => {
- it("gets all the recipe infos with updated CTR percents", async () => {
+ it("gets all the recipe infos with updated UCTR percents", async () => {
setMockPlatform("firefox-desktop");
const nimbusRecipeCollection = new NimbusRecipeCollection();
nimbusRecipeCollection.recipes = [
@@ -80,8 +80,8 @@ describe("NimbusRecipeCollection", () => {
const recipeInfos =
(await nimbusRecipeCollection.getExperimentAndBranchInfos()) as RecipeInfo[];
- expect(recipeInfos[0].branches[0].ctrPercent).toBe(12.35);
- expect(recipeInfos[0].branches[1].ctrPercent).toBe(12.35);
+ expect(recipeInfos[0].branches[0].uctrPercent).toBe(12.35);
+ expect(recipeInfos[0].branches[1].uctrPercent).toBe(12.35);
});
});
});
diff --git a/app/columns.tsx b/app/columns.tsx
index 4a9bdd02..6f407a28 100644
--- a/app/columns.tsx
+++ b/app/columns.tsx
@@ -58,7 +58,7 @@ function OffsiteLink(href: string, linkText: any) {
}
// This type is used to define the shape of our data.
-// NOTE: ctrPercent is undefined by default until set using getCTRPercent. It is
+// NOTE: uctrPercent is undefined by default until set using getUCTRPercent. It is
// made optional to help determine what's displayed in the Metrics column.
export type FxMSMessageInfo = {
product: "Desktop" | "Android";
@@ -66,9 +66,9 @@ export type FxMSMessageInfo = {
template: string;
surface: string;
segment: string;
- ctrPercent?: number;
- ctrPercentChange?: number;
- ctrDashboardLink?: string;
+ uctrPercent?: number;
+ uctrPercentChange?: number;
+ uctrDashboardLink?: string;
previewLink?: string;
metrics: string;
impressions?: number;
@@ -85,9 +85,9 @@ export type RecipeInfo = {
template?: string; // XXX template JSON name
surface?: string; // XXX template display name
segment?: string;
- ctrPercent?: number;
- ctrPercentChange?: number;
- ctrDashboardLink?: string;
+ uctrPercent?: number;
+ uctrPercentChange?: number;
+ uctrDashboardLink?: string;
previewLink?: string;
metrics?: string;
experimenterLink?: string;
@@ -107,9 +107,9 @@ export type BranchInfo = {
slug: string;
surface?: string;
segment?: string;
- ctrPercent?: number;
- ctrPercentChange?: number;
- ctrDashboardLink?: string;
+ uctrPercent?: number;
+ uctrPercentChange?: number;
+ uctrDashboardLink?: string;
previewLink?: string;
metrics?: string;
experimenterLink?: string;
@@ -129,20 +129,20 @@ export type RecipeOrBranchInfo = RecipeInfo | BranchInfo;
/**
* @returns an OffsiteLink linking to the Looker dashboard link if it exists,
- * labelled with either the CTR percent or "Dashboard"
+ * labelled with either the UCTR percent or "Dashboard"
*/
-function showCTRMetrics(
- ctrDashboardLink?: string,
- ctrPercent?: number,
+function showUCTRMetrics(
+ uctrDashboardLink?: string,
+ uctrPercent?: number,
impressions?: number,
) {
- if (ctrDashboardLink && ctrPercent !== undefined && impressions) {
+ if (uctrDashboardLink && uctrPercent !== undefined && impressions) {
return (
{OffsiteLink(
- ctrDashboardLink,
+ uctrDashboardLink,
<>
- {ctrPercent + "% CTR"}
+ {uctrPercent + "% UCTR"}
{impressions.toLocaleString() +
" impression" +
(impressions > 1 ? "s" : "")}
@@ -150,8 +150,8 @@ function showCTRMetrics(
)}
);
- } else if (ctrDashboardLink) {
- return OffsiteLink(ctrDashboardLink, "Dashboard");
+ } else if (uctrDashboardLink) {
+ return OffsiteLink(uctrDashboardLink, "Dashboard");
}
}
@@ -280,9 +280,9 @@ export const fxmsMessageColumns: ColumnDef[] = [
- The CTR and impressions metrics in this table are the primary
+ The UCTR and impressions metrics in this table are the primary
button clickthrough rates calculated over the last 30 days.
- Clicking into the CTR value will direct you to the Looker
+ Clicking into the UCTR value will direct you to the Looker
dashboard displaying the data.
}
@@ -308,9 +308,9 @@ export const fxmsMessageColumns: ColumnDef[] = [
return <>>;
}
- const metrics = showCTRMetrics(
- props.row.original.ctrDashboardLink,
- props.row.original.ctrPercent,
+ const metrics = showUCTRMetrics(
+ props.row.original.uctrDashboardLink,
+ props.row.original.uctrPercent,
props.row.original.impressions,
);
if (metrics) {
@@ -481,9 +481,9 @@ export const experimentColumns: ColumnDef[] = [
- The CTR and impressions metrics in this table are the primary
+ The UCTR and impressions metrics in this table are the primary
button clickthrough rates calculated over the{" "}
- time that the experiment is live. Clicking into the CTR
+ time that the experiment is live. Clicking into the UCTR
value will direct you to the Looker dashboard displaying the data.
}
@@ -500,9 +500,9 @@ export const experimentColumns: ColumnDef[] = [
return <>>;
}
- const metrics = showCTRMetrics(
- props.row.original.ctrDashboardLink,
- props.row.original.ctrPercent,
+ const metrics = showUCTRMetrics(
+ props.row.original.uctrDashboardLink,
+ props.row.original.uctrPercent,
props.row.original.impressions,
);
if (metrics) {
@@ -661,8 +661,8 @@ export const completedExperimentColumns: ColumnDef[] = [
return <>>;
}
- if (props.row.original.ctrDashboardLink) {
- return OffsiteLink(props.row.original.ctrDashboardLink, "Dashboard");
+ if (props.row.original.uctrDashboardLink) {
+ return OffsiteLink(props.row.original.uctrDashboardLink, "Dashboard");
}
return <>>;
},
diff --git a/app/fetchData.ts b/app/fetchData.ts
index 939ae1b5..2c129f9c 100644
--- a/app/fetchData.ts
+++ b/app/fetchData.ts
@@ -13,7 +13,7 @@ import { NimbusRecipeCollection } from "@/lib/nimbusRecipeCollection";
import { FxMSMessageInfo } from "./columns";
import {
cleanLookerData,
- getCTRPercentData,
+ getUCTRPercentData,
mergeLookerData,
runLookQuery,
} from "@/lib/looker.ts";
@@ -150,7 +150,7 @@ export async function getASRouterLocalMessageInfoFromFile(): Promise<
/**
* Given a message JSON, this function fetches the message data as an
* FxMSMessageInfo object and populating it with surface data, preview links,
- * microsurvey tags, CTR data, and dashboard links when available.
+ * microsurvey tags, UCTR data, and dashboard links when available.
* @param messageDef the JSON for a single message collected from local data
* @returns the information in messageDef in FxMSMessageInfo type
*/
@@ -164,8 +164,8 @@ export async function getASRouterLocalColumnFromJSON(
surface: getSurfaceData(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
+ uctrPercent: undefined, // may be populated from Looker data
+ uctrPercentChange: undefined, // may be populated from Looker data
previewLink: getPreviewLink(maybeCreateWelcomePreview(messageDef)),
impressions: undefined, // may be populated from Looker data
hasMicrosurvey: messageHasMicrosurvey(messageDef.id),
@@ -176,19 +176,19 @@ export async function getASRouterLocalColumnFromJSON(
const platform = "firefox-desktop";
if (isLookerEnabled) {
- const ctrPercentData = await getCTRPercentData(
+ const uctrPercentData = await getUCTRPercentData(
fxmsMsgInfo.id,
platform,
fxmsMsgInfo.template,
channel,
);
- if (ctrPercentData) {
- fxmsMsgInfo.ctrPercent = ctrPercentData.ctrPercent;
- fxmsMsgInfo.impressions = ctrPercentData.impressions;
+ if (uctrPercentData) {
+ fxmsMsgInfo.uctrPercent = uctrPercentData.uctrPercent;
+ fxmsMsgInfo.impressions = uctrPercentData.impressions;
}
}
- fxmsMsgInfo.ctrDashboardLink = getDesktopDashboardLink(
+ fxmsMsgInfo.uctrDashboardLink = getDesktopDashboardLink(
fxmsMsgInfo.template,
fxmsMsgInfo.id,
channel,
diff --git a/lib/looker.ts b/lib/looker.ts
index 85cd30ec..0444dd20 100644
--- a/lib/looker.ts
+++ b/lib/looker.ts
@@ -4,20 +4,20 @@ import { getDashboardIdForSurface, getSurfaceData } from "./messageUtils";
import { getLookerSubmissionTimestampDateFilter } from "./lookerUtils";
import { Platform } from "./types";
-export type CTRData = {
- ctrPercent: number;
+export type UCTRData = {
+ uctrPercent: number;
impressions: number;
};
/**
- * Safely formats a CTR percentage value with consistent decimal precision
+ * Safely formats a UCTR percentage value with consistent decimal precision
*
- * @param value - The raw CTR rate (typically between 0 and 1)
- * @returns The formatted CTR percentage with 2 decimal places
+ * @param value - The raw UCTR rate (typically between 0 and 1)
+ * @returns The formatted UCTR percentage with 2 decimal places
*
* This function is used throughout the codebase to ensure consistent formatting
- * of CTR percentages. It's necessary because we need consistent decimal
- * precision for CTR percentages in the UI and tests.
+ * of UCTR percentages. It's necessary because we need consistent decimal
+ * precision for UCTR percentages in the UI and tests.
*
* Using Math.round with multiplier/divisor instead of toFixed() to avoid
* floating-point precision issues in JavaScript. This approach ensures
@@ -27,7 +27,7 @@ export type CTRData = {
* 2. Round to nearest integer to handle the 2-decimal precision we want
* 3. Divide by 100 to shift decimal point left by 2 places
*/
-export function getSafeCtrPercent(value: number): number {
+export function getSafeUctrPercent(value: number): number {
return Math.round(value * 10000) / 100;
}
@@ -45,7 +45,7 @@ export async function getDashboardElement0(
// clear, but the code is working, so I'm inclined to leave it alone for now.
SDK.search_dashboard_elements({
dashboard_id: dashboardId,
- title: "CTR and User Profiles Impressed",
+ title: "UCTR and User Profiles Impressed",
fields: "query",
}),
);
@@ -117,7 +117,7 @@ export async function runQueryForSurface(
/**
* @param id the events_count.message_id required for running the looker
- * query to retrieve CTR metrics
+ * query to retrieve UCTR metrics
* @param platform the message platform
* @param template the message template
* @param channel the normalized channel
@@ -125,10 +125,10 @@ export async function runQueryForSurface(
* @param branch the branch slug
* @param startDate the experiment start date
* @param endDate the experiment proposed end date
- * @returns a CTR percent value for a message if the Looker query results are
+ * @returns a UCTR percent value for a message if the Looker query results are
* defined
*/
-export async function getCTRPercentData(
+export async function getUCTRPercentData(
id: string,
platform: Platform,
template: string,
@@ -137,10 +137,10 @@ export async function getCTRPercentData(
branch?: string,
startDate?: string | null,
endDate?: string | null,
-): Promise {
+): Promise {
switch (platform) {
case "fenix":
- return getAndroidCTRPercentData(
+ return getAndroidUCTRPercentData(
id,
template,
channel,
@@ -150,7 +150,7 @@ export async function getCTRPercentData(
endDate,
);
default:
- return getDesktopCTRPercentData(
+ return getDesktopUCTRPercentData(
id,
template,
channel,
@@ -162,7 +162,7 @@ export async function getCTRPercentData(
}
}
-export async function getAndroidCTRPercentData(
+export async function getAndroidUCTRPercentData(
id: string,
template: string,
channel?: string,
@@ -170,7 +170,7 @@ export async function getAndroidCTRPercentData(
branch?: string,
startDate?: string | null,
endDate?: string | null,
-): Promise {
+): Promise {
// XXX the filters are currently defined to match the filters in getDashboard.
// It would be more ideal to consider a different approach when definining
// those filters to sync up the data in both places. Non-trivial changes to
@@ -200,7 +200,7 @@ export async function getAndroidCTRPercentData(
);
if (queryResult?.length > 0) {
- // CTR percents will have 2 decimal places since this is what is expected
+ // UCTR percents will have 2 decimal places since this is what is expected
// from Experimenter analyses.
const clientCount = queryResult[0]["events.client_count"];
const eventName = clientCount["events.event_name"];
@@ -208,10 +208,10 @@ export async function getAndroidCTRPercentData(
const primaryRate = queryResult[0].primary_rate;
- const ctrPercent = getSafeCtrPercent(primaryRate);
+ const uctrPercent = getSafeUctrPercent(primaryRate);
return {
- ctrPercent: ctrPercent,
+ uctrPercent: uctrPercent,
impressions: impressions * 10, // We need to extrapolate real numbers for the 10% sample
};
}
@@ -220,7 +220,7 @@ export async function getAndroidCTRPercentData(
return undefined;
}
-export async function getDesktopCTRPercentData(
+export async function getDesktopUCTRPercentData(
id: string,
template: string,
channel?: string,
@@ -228,7 +228,7 @@ export async function getDesktopCTRPercentData(
branch?: string,
startDate?: string | null,
endDate?: string | null,
-): Promise {
+): Promise {
// XXX the filters are currently defined to match the filters in getDashboard.
// It would be more ideal to consider a different approach when definining
// those filters to sync up the data in both places. Non-trivial changes to
@@ -263,7 +263,7 @@ export async function getDesktopCTRPercentData(
}
if (queryResult?.length > 0) {
- // CTR percents will have 2 decimal places since this is what is expected
+ // UCTR percents will have 2 decimal places since this is what is expected
// from Experimenter analyses.
let impressions;
if (template === "infobar") {
@@ -279,10 +279,10 @@ export async function getDesktopCTRPercentData(
const primaryRate = queryResult[0].primary_rate;
- const ctrPercent = getSafeCtrPercent(primaryRate);
+ const uctrPercent = getSafeUctrPercent(primaryRate);
return {
- ctrPercent: ctrPercent,
+ uctrPercent: uctrPercent,
impressions: impressions,
};
}
diff --git a/lib/nimbusRecipe.ts b/lib/nimbusRecipe.ts
index dec52a40..6e6d1a42 100644
--- a/lib/nimbusRecipe.ts
+++ b/lib/nimbusRecipe.ts
@@ -171,7 +171,7 @@ export class NimbusRecipe implements NimbusRecipeType {
formattedEndDate = formatDate(branchInfo.nimbusExperiment.endDate, 1);
}
- branchInfo.ctrDashboardLink = getAndroidDashboardLink(
+ branchInfo.uctrDashboardLink = getAndroidDashboardLink(
branchInfo.template as string,
branchInfo.id,
undefined,
@@ -182,7 +182,7 @@ export class NimbusRecipe implements NimbusRecipeType {
this._isCompleted,
);
- console.log("Android Dashboard: ", branchInfo.ctrDashboardLink);
+ console.log("Android Dashboard: ", branchInfo.uctrDashboardLink);
return branchInfo;
}
@@ -482,7 +482,7 @@ export class NimbusRecipe implements NimbusRecipeType {
if (branchInfo.nimbusExperiment.endDate) {
formattedEndDate = formatDate(branchInfo.nimbusExperiment.endDate, 1);
}
- branchInfo.ctrDashboardLink = getDesktopDashboardLink(
+ branchInfo.uctrDashboardLink = getDesktopDashboardLink(
branch.template,
branchInfo.id,
undefined,
@@ -537,8 +537,8 @@ export class NimbusRecipe implements NimbusRecipeType {
product: "Desktop",
id: this._rawRecipe.slug,
segment: "some segment",
- ctrPercent: 0.5, // get me from BigQuery
- ctrPercentChange: 2, // get me from BigQuery
+ uctrPercent: 0.5, // get me from BigQuery
+ uctrPercentChange: 2, // get me from BigQuery
metrics: "some metrics",
experimenterLink: `https://experimenter.services.mozilla.com/nimbus/${this._rawRecipe.slug}`,
userFacingName: this._rawRecipe.userFacingName,
diff --git a/lib/nimbusRecipeCollection.ts b/lib/nimbusRecipeCollection.ts
index de713886..9dfdcd3a 100644
--- a/lib/nimbusRecipeCollection.ts
+++ b/lib/nimbusRecipeCollection.ts
@@ -1,6 +1,6 @@
import { NimbusRecipe } from "../lib/nimbusRecipe";
import { BranchInfo, RecipeInfo, RecipeOrBranchInfo } from "@/app/columns";
-import { getCTRPercentData } from "./looker";
+import { getUCTRPercentData } from "./looker";
import { getExperimentLookerDashboardDate } from "./lookerUtils";
import { Platform } from "./types";
@@ -15,9 +15,9 @@ type NimbusRecipeCollectionType = {
};
/**
- * @returns an array of BranchInfo with updated CTR percents for the recipe
+ * @returns an array of BranchInfo with updated UCTR percents for the recipe
*/
-async function updateBranchesCTR(recipe: NimbusRecipe): Promise {
+async function updateBranchesUCTR(recipe: NimbusRecipe): Promise {
return await Promise.all(
recipe
.getBranchInfos()
@@ -31,7 +31,7 @@ async function updateBranchesCTR(recipe: NimbusRecipe): Promise {
);
// We are making all branch ids upper case to make up for
// Looker being case sensitive
- const ctrPercentData = await getCTRPercentData(
+ const uctrPercentData = await getUCTRPercentData(
branchInfo.id,
branchInfo.nimbusExperiment.appName,
branchInfo.template!,
@@ -41,9 +41,9 @@ async function updateBranchesCTR(recipe: NimbusRecipe): Promise {
branchInfo.nimbusExperiment.startDate,
proposedEndDate,
);
- if (ctrPercentData) {
- branchInfo.ctrPercent = ctrPercentData.ctrPercent;
- branchInfo.impressions = ctrPercentData.impressions;
+ if (uctrPercentData) {
+ branchInfo.uctrPercent = uctrPercentData.uctrPercent;
+ branchInfo.impressions = uctrPercentData.impressions;
}
return branchInfo;
}),
@@ -92,15 +92,15 @@ export class NimbusRecipeCollection implements NimbusRecipeCollectionType {
/**
* @returns a list of RecipeInfo of recipes in this collection with updated
- * ctrPercent properties
+ * uctrPercent properties
*/
async getExperimentAndBranchInfos(): Promise {
return await Promise.all(
this.recipes.map(async (recipe: NimbusRecipe): Promise => {
let updatedRecipe = recipe.getRecipeInfo();
- // Update all branches with CTR data for the recipe
- updatedRecipe.branches = await updateBranchesCTR(recipe);
+ // Update all branches with UCTR data for the recipe
+ updatedRecipe.branches = await updateBranchesUCTR(recipe);
return updatedRecipe;
}),