Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix #4342: feature(visualization) - Incorporate metric metadata from Jetstream. #4599

Merged
merged 1 commit into from Feb 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions app/experimenter/nimbus-ui/package.json
Expand Up @@ -46,6 +46,7 @@
"react-dom": "^16.13.1",
"react-helmet": "^6.1.0",
"react-hook-form": "^6.12.2",
"react-markdown": "^5.0.3",
"react-scripts": "3.4.0",
"react-scrollspy": "^3.4.3",
"react-select": "^3.1.1",
Expand Down
Expand Up @@ -51,6 +51,7 @@ const Subject = ({
daily: [],
weekly: {},
overall: mockAnalysis().overall,
metadata: mockAnalysis().metadata,
other_metrics: mockAnalysis().other_metrics,
}
: undefined,
Expand Down Expand Up @@ -134,7 +135,7 @@ describe("AppLayoutSidebarLocked", () => {
"Overview",
"Results Summary",
"Default Metrics",
"Feature D",
"Feature D Friendly Name",
].forEach((item) => {
expect(screen.getByText(item)).toBeInTheDocument();
});
Expand Down
Expand Up @@ -8,7 +8,7 @@ import Scrollspy from "react-scrollspy";
import { ReactComponent as ChevronLeft } from "../../images/chevron-left.svg";
import { ReactComponent as Clipboard } from "../../images/clipboard.svg";
import { StatusCheck } from "../../lib/experiment";
import { AnalysisData } from "../../lib/visualization/types";
import { AnalysisData, MetadataPoint } from "../../lib/visualization/types";
import { analysisAvailable } from "../../lib/visualization/utils";
import {
getExperiment_experimentBySlug_primaryProbeSets,
Expand Down Expand Up @@ -56,6 +56,19 @@ const probesetToMapping = (
return newMap;
};

const otherMetricsToFriendlyName = (
otherMetrics: { [metric: string]: string },
metricsMetaData: { [metric: string]: MetadataPoint },
) => {
const newMap: { [key: string]: string } = {};
Object.keys(otherMetrics).map(
(metric) =>
(newMap[metric] =
metricsMetaData[metric]!.friendly_name || otherMetrics[metric]),
);
return newMap;
};

type AppLayoutSidebarLockedProps = {
testid?: string;
children: React.ReactNode;
Expand Down Expand Up @@ -84,6 +97,11 @@ export const AppLayoutSidebarLocked = ({
const { slug } = useParams();
const primaryMetrics = probesetToMapping(primaryProbeSets || []);
const secondaryMetrics = probesetToMapping(secondaryProbeSets || []);
const otherMetrics = otherMetricsToFriendlyName(
analysis?.other_metrics || {},
analysis?.metadata?.metrics || {},
);

const sidebarKeys = [
"monitoring",
"overview",
Expand All @@ -94,7 +112,7 @@ export const AppLayoutSidebarLocked = ({
.concat("secondary-metrics")
.concat(Object.keys(secondaryMetrics || []))
.concat("default-metrics")
.concat(Object.keys(analysis?.other_metrics || []));
.concat(Object.keys(otherMetrics || []));

return (
<Container fluid className="h-100vh" data-testid={testid}>
Expand Down Expand Up @@ -160,11 +178,8 @@ export const AppLayoutSidebarLocked = ({
getSidebarItems(primaryMetrics, "Primary Metrics")}
{Object.keys(secondaryMetrics).length &&
getSidebarItems(secondaryMetrics, "Secondary Metrics")}
{analysis?.other_metrics &&
getSidebarItems(
analysis?.other_metrics,
"Default Metrics",
)}
{otherMetrics &&
getSidebarItems(otherMetrics, "Default Metrics")}
</Scrollspy>
</>
) : (
Expand Down
Expand Up @@ -26,7 +26,7 @@ storiesOf("pages/Results/TableMetricSecondary", module)
<TableMetricSecondary
results={mockAnalysis()}
probeSetSlug={experiment.secondaryProbeSets![0]!.slug}
probeSetName={experiment.secondaryProbeSets![0]!.name}
probeSetDefaultName={experiment.secondaryProbeSets![0]!.name}
isDefault={false}
/>
);
Expand All @@ -37,7 +37,7 @@ storiesOf("pages/Results/TableMetricSecondary", module)
<TableMetricSecondary
results={mockAnalysis()}
probeSetSlug={experiment.secondaryProbeSets![0]!.slug}
probeSetName={experiment.secondaryProbeSets![0]!.name}
probeSetDefaultName={experiment.secondaryProbeSets![0]!.name}
isDefault={false}
/>
);
Expand All @@ -57,7 +57,7 @@ storiesOf("pages/Results/TableMetricSecondary", module)
<TableMetricSecondary
results={mockAnalysis()}
probeSetSlug={experiment.secondaryProbeSets![0]!.slug}
probeSetName={experiment.secondaryProbeSets![0]!.name}
probeSetDefaultName={experiment.secondaryProbeSets![0]!.name}
isDefault={false}
/>
);
Expand Down
Expand Up @@ -19,7 +19,7 @@ describe("TableMetricSecondary", () => {
<TableMetricSecondary
results={mockAnalysis()}
probeSetSlug={experiment.secondaryProbeSets![0]!.slug}
probeSetName={experiment.secondaryProbeSets![0]!.name}
probeSetDefaultName={experiment.secondaryProbeSets![0]!.name}
isDefault={false}
/>
</RouterSlugProvider>,
Expand All @@ -37,7 +37,7 @@ describe("TableMetricSecondary", () => {
<TableMetricSecondary
results={mockAnalysis()}
probeSetSlug={experiment.secondaryProbeSets![0]!.slug}
probeSetName={experiment.secondaryProbeSets![0]!.name}
probeSetDefaultName={experiment.secondaryProbeSets![0]!.name}
isDefault={false}
/>
</RouterSlugProvider>,
Expand All @@ -58,7 +58,7 @@ describe("TableMetricSecondary", () => {
<TableMetricSecondary
results={mockAnalysis()}
probeSetSlug={experiment.secondaryProbeSets![0]!.slug}
probeSetName={experiment.secondaryProbeSets![0]!.name}
probeSetDefaultName={experiment.secondaryProbeSets![0]!.name}
isDefault={false}
/>
</RouterSlugProvider>,
Expand All @@ -75,7 +75,7 @@ describe("TableMetricSecondary", () => {
<TableMetricSecondary
results={mockAnalysis()}
probeSetSlug={experiment.secondaryProbeSets![0]!.slug}
probeSetName={experiment.secondaryProbeSets![0]!.name}
probeSetDefaultName={experiment.secondaryProbeSets![0]!.name}
isDefault={false}
/>
</RouterSlugProvider>,
Expand All @@ -96,12 +96,28 @@ describe("TableMetricSecondary", () => {
<TableMetricSecondary
results={mockAnalysis()}
probeSetSlug={experiment.secondaryProbeSets![0]!.slug}
probeSetName={experiment.secondaryProbeSets![0]!.name}
probeSetDefaultName={experiment.secondaryProbeSets![0]!.name}
isDefault={false}
/>
</RouterSlugProvider>,
);

expect(screen.queryAllByText("0.02 to 0.08")).toHaveLength(2);
});

it("uses the friendly name from the metadata", () => {
const { mock, experiment } = mockExperimentQuery("demo-slug");
render(
<RouterSlugProvider mocks={[mock]}>
<TableMetricSecondary
results={mockAnalysis()}
probeSetSlug="feature_d"
probeSetDefaultName={experiment.secondaryProbeSets![0]!.name}
isDefault={false}
/>
</RouterSlugProvider>,
);

expect(screen.queryByText("Feature D Friendly Name")).toBeInTheDocument();
});
});
Expand Up @@ -3,6 +3,9 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import React from "react";
import ReactMarkdown from "react-markdown";
import ReactTooltip from "react-tooltip";
import { ReactComponent as Info } from "../../../images/info.svg";
import {
DISPLAY_TYPE,
METRIC_TYPE,
Expand All @@ -23,7 +26,7 @@ type SecondaryMetricStatistic = {
type TableMetricSecondaryProps = {
results: AnalysisData;
probeSetSlug: string;
probeSetName: string;
probeSetDefaultName: string;
isDefault?: boolean;
};

Expand All @@ -44,10 +47,11 @@ const TableMetricSecondary = ({
daily: [],
weekly: {},
overall: {},
metadata: { metrics: {}, probesets: {} },
show_analysis: false,
},
probeSetSlug,
probeSetName,
probeSetDefaultName,
isDefault = true,
}: TableMetricSecondaryProps) => {
const secondaryMetricStatistics = getStatistics(probeSetSlug);
Expand All @@ -56,11 +60,39 @@ const TableMetricSecondary = ({
: METRIC_TYPE.USER_SELECTED_SECONDARY;

const overallResults = results?.overall!;
const probeSetName =
results.metadata?.metrics[probeSetSlug]?.friendly_name ||
probeSetDefaultName;
const probeSetDescription =
results.metadata?.metrics[probeSetSlug]?.description || undefined;

return (
<div data-testid="table-metric-secondary" className="mb-5">
<h2 className="h5 mb-3" id={probeSetSlug}>
<div>{probeSetName}</div>
<div>
<div className="d-inline-block">
{probeSetName}{" "}
{probeSetDescription && (
<>
<Info
data-tip
data-for={probeSetSlug}
className="align-baseline"
/>
<ReactTooltip
id={probeSetSlug}
multiline
clickable
className="w-25"
delayHide={200}
effect="solid"
>
<ReactMarkdown source={probeSetDescription!} />
</ReactTooltip>
</>
)}
</div>
</div>
<div
className={`badge ${secondaryType.badge}`}
data-tip={secondaryType.tooltip}
Expand Down
Expand Up @@ -84,17 +84,17 @@ const PageResults: React.FunctionComponent<RouteComponentProps> = () => (
key={probeSet!.slug}
results={analysis}
probeSetSlug={probeSet!.slug}
probeSetName={probeSet!.name}
probeSetDefaultName={probeSet!.name}
isDefault={false}
/>
))}
{analysis?.other_metrics &&
Object.keys(analysis.other_metrics).map((metric) => (
Object.keys(analysis.other_metrics).map((metric: string) => (
<TableMetricSecondary
key={metric}
results={analysis}
probeSetSlug={metric}
probeSetName={analysis!.other_metrics![metric]}
probeSetDefaultName={analysis!.other_metrics![metric]}
/>
))}
</div>
Expand Down
32 changes: 26 additions & 6 deletions app/experimenter/nimbus-ui/src/lib/visualization/mocks.tsx
Expand Up @@ -7,6 +7,28 @@ export const MOCK_UNAVAILABLE_ANALYSIS = {
daily: null,
weekly: null,
overall: null,
metadata: {
metrics: {},
probesets: {},
},
};

export const MOCK_METADATA = {
metrics: {
feature_b: {
bigger_is_better: true,
description:
"This is a metric description. It's made by a data scientist at the creation time of the metric [I'm a link](https://www.example.com)",
friendly_name: "Feature B",
},
feature_d: {
bigger_is_better: true,
description:
"This is a metric description. It's made by a data scientist at the creation time of the metric [I'm a link](https://www.example.com)",
friendly_name: "Feature D Friendly Name",
},
},
probesets: {},
};

export const CONTROL_NEUTRAL = {
Expand Down Expand Up @@ -204,9 +226,8 @@ export const weeklyMockAnalysis = (modifications = {}) =>
export const mockAnalysis = (modifications = {}) =>
Object.assign(
{
other_metrics: {
feature_d: "Feature D",
},
other_metrics: { feature_d: "Feature D" },
metadata: MOCK_METADATA,
show_analysis: true,
daily: [],
weekly: weeklyMockAnalysis(),
Expand Down Expand Up @@ -907,9 +928,8 @@ export const mockAnalysis = (modifications = {}) =>
export const mockIncompleteAnalysis = (modifications = {}) =>
Object.assign(
{
other_metrics: {
feature_d: "Feature D",
},
other_metrics: { feature_d: "Feature D" },
metadata: MOCK_METADATA,
show_analysis: true,
daily: [],
weekly: {},
Expand Down
12 changes: 12 additions & 0 deletions app/experimenter/nimbus-ui/src/lib/visualization/types.ts
Expand Up @@ -7,12 +7,24 @@ export interface AnalysisData {
weekly: { [branch: string]: BranchDescription } | null;
overall: { [branch: string]: BranchDescription } | null;
show_analysis: boolean;
metadata?: Metadata;
other_metrics?: { [metric: string]: string };
}

export type AnalysisDataOverall = Exclude<AnalysisData["overall"], null>;
export type AnalysisDataWeekly = Exclude<AnalysisData["weekly"], null>;

export interface Metadata {
metrics: { [metric: string]: MetadataPoint };
probesets: { [probeset: string]: MetadataPoint };
}

export interface MetadataPoint {
description: string;
friendly_name: string;
bigger_is_better: boolean;
}

export interface AnalysisPoint {
metric: string;
statistic: string;
Expand Down
4 changes: 4 additions & 0 deletions app/experimenter/nimbus-ui/src/styles/index.scss
Expand Up @@ -86,3 +86,7 @@ $sizes: (
width: 18px;
height: 18px;
}

.__react_component_tooltip > p:last-child {
margin-bottom: 0;
}
1 change: 0 additions & 1 deletion app/experimenter/settings.py
Expand Up @@ -418,6 +418,5 @@
DEFAULT_FILE_STORAGE = "storages.backends.gcloud.GoogleCloudStorage"
GS_PROJECT_ID = "experiments-analysis"
GS_BUCKET_NAME = "mozanalysis"
GS_LOCATION = "statistics"

NIMBUS_SCHEMA_VERSION = pkg_resources.get_distribution("mozilla-nimbus-shared").version