feat(site): add parameters usage to insights (#8886)

This commit is contained in:
Bruno Quaresma
2023-08-08 13:09:31 -03:00
committed by GitHub
parent 5339a31532
commit 4a987e9917
7 changed files with 618 additions and 5 deletions
+4 -2
View File
@@ -8024,7 +8024,8 @@ const docTemplate = `{
"tailnet_pg_coordinator",
"single_tailnet",
"template_restart_requirement",
"deployment_health_page"
"deployment_health_page",
"template_parameters_insights"
],
"x-enum-varnames": [
"ExperimentMoons",
@@ -8032,7 +8033,8 @@ const docTemplate = `{
"ExperimentTailnetPGCoordinator",
"ExperimentSingleTailnet",
"ExperimentTemplateRestartRequirement",
"ExperimentDeploymentHealthPage"
"ExperimentDeploymentHealthPage",
"ExperimentTemplateParametersInsights"
]
},
"codersdk.Feature": {
+4 -2
View File
@@ -7185,7 +7185,8 @@
"tailnet_pg_coordinator",
"single_tailnet",
"template_restart_requirement",
"deployment_health_page"
"deployment_health_page",
"template_parameters_insights"
],
"x-enum-varnames": [
"ExperimentMoons",
@@ -7193,7 +7194,8 @@
"ExperimentTailnetPGCoordinator",
"ExperimentSingleTailnet",
"ExperimentTemplateRestartRequirement",
"ExperimentDeploymentHealthPage"
"ExperimentDeploymentHealthPage",
"ExperimentTemplateParametersInsights"
]
},
"codersdk.Feature": {
+4
View File
@@ -1881,6 +1881,9 @@ const (
// Deployment health page
ExperimentDeploymentHealthPage Experiment = "deployment_health_page"
// Template parameters insights
ExperimentTemplateParametersInsights Experiment = "template_parameters_insights"
// Add new experiments here!
// ExperimentExample Experiment = "example"
)
@@ -1891,6 +1894,7 @@ const (
// not be included here and will be essentially hidden.
var ExperimentsAll = Experiments{
ExperimentDeploymentHealthPage,
ExperimentTemplateParametersInsights,
}
// Experiments is a list of experiments that are enabled for the deployment.
+1
View File
@@ -2679,6 +2679,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
| `single_tailnet` |
| `template_restart_requirement` |
| `deployment_health_page` |
| `template_parameters_insights` |
## codersdk.Feature
+2
View File
@@ -1586,6 +1586,7 @@ export type Experiment =
| "moons"
| "single_tailnet"
| "tailnet_pg_coordinator"
| "template_parameters_insights"
| "template_restart_requirement"
| "workspace_actions"
export const Experiments: Experiment[] = [
@@ -1593,6 +1594,7 @@ export const Experiments: Experiment[] = [
"moons",
"single_tailnet",
"tailnet_pg_coordinator",
"template_parameters_insights",
"template_restart_requirement",
"workspace_actions",
]
@@ -83,7 +83,376 @@ export const Loaded: Story = {
seconds: 1020900,
},
],
parameters_usage: [],
parameters_usage: [
{
template_ids: ["7dd1d090-3e23-4ada-8894-3945affcad42"],
display_name: "",
name: "Compute instances",
type: "number",
description: "Let's set the expected number of instances.",
values: [
{
value: "3",
count: 2,
},
],
},
{
template_ids: ["7dd1d090-3e23-4ada-8894-3945affcad42"],
display_name: "",
name: "Docker Image",
type: "string",
description: "Docker image for the development container",
values: [
{
value: "ghcr.io/harrison-ai/coder-dev:base",
count: 2,
},
],
},
{
template_ids: ["7dd1d090-3e23-4ada-8894-3945affcad42"],
display_name: "Very random string",
name: "Optional random string",
type: "string",
description: "This string is optional",
values: [
{
value: "ksjdlkajs;djálskd'l ;a k;aosdk ;oaids ;li",
count: 1,
},
{
value: "some other any string here",
count: 1,
},
],
},
{
template_ids: ["7dd1d090-3e23-4ada-8894-3945affcad42"],
display_name: "",
name: "Region",
type: "string",
description: "These are options.",
options: [
{
name: "US Central",
description: "Select for central!",
value: "us-central1-a",
icon: "/icon/goland.svg",
},
{
name: "US East",
description: "Select for east!",
value: "us-east1-a",
icon: "/icon/folder.svg",
},
{
name: "US West",
description: "Select for west!",
value: "us-west2-a",
icon: "",
},
],
values: [
{
value: "us-central1-a",
count: 1,
},
{
value: "us-west2-a",
count: 1,
},
],
},
{
template_ids: ["7dd1d090-3e23-4ada-8894-3945affcad42"],
display_name: "",
name: "Security groups",
type: "list(string)",
description: "Select appropriate security groups.",
values: [
{
value:
'["Web Server Security Group","Database Security Group","Backend Security Group"]',
count: 2,
},
],
},
{
template_ids: ["7dd1d090-3e23-4ada-8894-3945affcad42"],
display_name: "Very random string",
name: "buggy-1",
type: "string",
description: "This string is buggy",
values: [
{
value: "",
count: 2,
},
],
},
{
template_ids: ["7dd1d090-3e23-4ada-8894-3945affcad42"],
display_name: "Force rebuild",
name: "force-rebuild",
type: "bool",
description: "Rebuild the project code",
values: [
{
value: "false",
count: 2,
},
],
},
{
template_ids: ["7dd1d090-3e23-4ada-8894-3945affcad42"],
display_name: "Location",
name: "location",
type: "string",
description: "What location should your workspace live in?",
options: [
{
name: "US (Virginia)",
description: "",
value: "eastus",
icon: "/emojis/1f1fa-1f1f8.png",
},
{
name: "US (Virginia) 2",
description: "",
value: "eastus2",
icon: "/emojis/1f1fa-1f1f8.png",
},
{
name: "US (Texas)",
description: "",
value: "southcentralus",
icon: "/emojis/1f1fa-1f1f8.png",
},
{
name: "US (Washington)",
description: "",
value: "westus2",
icon: "/emojis/1f1fa-1f1f8.png",
},
{
name: "US (Arizona)",
description: "",
value: "westus3",
icon: "/emojis/1f1fa-1f1f8.png",
},
{
name: "US (Iowa)",
description: "",
value: "centralus",
icon: "/emojis/1f1fa-1f1f8.png",
},
{
name: "Canada (Toronto)",
description: "",
value: "canadacentral",
icon: "/emojis/1f1e8-1f1e6.png",
},
{
name: "Brazil (Sao Paulo)",
description: "",
value: "brazilsouth",
icon: "/emojis/1f1e7-1f1f7.png",
},
{
name: "East Asia (Hong Kong)",
description: "",
value: "eastasia",
icon: "/emojis/1f1f0-1f1f7.png",
},
{
name: "Southeast Asia (Singapore)",
description: "",
value: "southeastasia",
icon: "/emojis/1f1f0-1f1f7.png",
},
{
name: "Australia (New South Wales)",
description: "",
value: "australiaeast",
icon: "/emojis/1f1e6-1f1fa.png",
},
{
name: "China (Hebei)",
description: "",
value: "chinanorth3",
icon: "/emojis/1f1e8-1f1f3.png",
},
{
name: "India (Pune)",
description: "",
value: "centralindia",
icon: "/emojis/1f1ee-1f1f3.png",
},
{
name: "Japan (Tokyo)",
description: "",
value: "japaneast",
icon: "/emojis/1f1ef-1f1f5.png",
},
{
name: "Korea (Seoul)",
description: "",
value: "koreacentral",
icon: "/emojis/1f1f0-1f1f7.png",
},
{
name: "Europe (Ireland)",
description: "",
value: "northeurope",
icon: "/emojis/1f1ea-1f1fa.png",
},
{
name: "Europe (Netherlands)",
description: "",
value: "westeurope",
icon: "/emojis/1f1ea-1f1fa.png",
},
{
name: "France (Paris)",
description: "",
value: "francecentral",
icon: "/emojis/1f1eb-1f1f7.png",
},
{
name: "Germany (Frankfurt)",
description: "",
value: "germanywestcentral",
icon: "/emojis/1f1e9-1f1ea.png",
},
{
name: "Norway (Oslo)",
description: "",
value: "norwayeast",
icon: "/emojis/1f1f3-1f1f4.png",
},
{
name: "Sweden (Gävle)",
description: "",
value: "swedencentral",
icon: "/emojis/1f1f8-1f1ea.png",
},
{
name: "Switzerland (Zurich)",
description: "",
value: "switzerlandnorth",
icon: "/emojis/1f1e8-1f1ed.png",
},
{
name: "Qatar (Doha)",
description: "",
value: "qatarcentral",
icon: "/emojis/1f1f6-1f1e6.png",
},
{
name: "UAE (Dubai)",
description: "",
value: "uaenorth",
icon: "/emojis/1f1e6-1f1ea.png",
},
{
name: "South Africa (Johannesburg)",
description: "",
value: "southafricanorth",
icon: "/emojis/1f1ff-1f1e6.png",
},
{
name: "UK (London)",
description: "",
value: "uksouth",
icon: "/emojis/1f1ec-1f1e7.png",
},
],
values: [
{
value: "brazilsouth",
count: 1,
},
{
value: "switzerlandnorth",
count: 1,
},
],
},
{
template_ids: ["7dd1d090-3e23-4ada-8894-3945affcad42"],
display_name: "",
name: "mtojek_region",
type: "string",
description: "What region should your workspace live in?",
options: [
{
name: "Los Angeles, CA",
description: "",
value: "Los Angeles, CA",
icon: "",
},
{
name: "Moncks Corner, SC",
description: "",
value: "Moncks Corner, SC",
icon: "",
},
{
name: "Eemshaven, NL",
description: "",
value: "Eemshaven, NL",
icon: "",
},
],
values: [
{
value: "Los Angeles, CA",
count: 2,
},
],
},
{
template_ids: ["7dd1d090-3e23-4ada-8894-3945affcad42"],
display_name: "My Project ID",
name: "project_id",
type: "string",
description: "This is the Project ID.",
values: [
{
value: "12345",
count: 2,
},
],
},
{
template_ids: ["7dd1d090-3e23-4ada-8894-3945affcad42"],
display_name: "Force devcontainer rebuild",
name: "rebuild_devcontainer",
type: "bool",
description: "",
values: [
{
value: "false",
count: 2,
},
],
},
{
template_ids: ["7dd1d090-3e23-4ada-8894-3945affcad42"],
display_name: "Git Repo URL",
name: "repo_url",
type: "string",
description:
"See sample projects (https://github.com/microsoft/vscode-dev-containers#sample-projects)",
values: [
{
value: "https://github.com/mtojek/coder",
count: 2,
},
],
},
],
},
interval_reports: [
{
@@ -21,10 +21,17 @@ import { Loader } from "components/Loader/Loader"
import {
DAUsResponse,
TemplateInsightsResponse,
TemplateParameterUsage,
TemplateParameterValue,
UserLatencyInsightsResponse,
} from "api/typesGenerated"
import { ComponentProps } from "react"
import { subDays, addHours, startOfHour } from "date-fns"
import { useDashboard } from "components/Dashboard/DashboardProvider"
import OpenInNewOutlined from "@mui/icons-material/OpenInNewOutlined"
import Link from "@mui/material/Link"
import CheckCircleOutlined from "@mui/icons-material/CheckCircleOutlined"
import CancelOutlined from "@mui/icons-material/CancelOutlined"
export default function TemplateInsightsPage() {
const now = new Date()
@@ -42,6 +49,10 @@ export default function TemplateInsightsPage() {
queryKey: ["templates", template.id, "user-latency"],
queryFn: () => getInsightsUserLatency(insightsFilter),
})
const dashboard = useDashboard()
const shouldDisplayParameters =
dashboard.experiments.includes("template_parameters_insights") ||
process.env.NODE_ENV === "development"
return (
<>
@@ -51,6 +62,7 @@ export default function TemplateInsightsPage() {
<TemplateInsightsPageView
templateInsights={templateInsights}
userLatency={userLatency}
shouldDisplayParameters={shouldDisplayParameters}
/>
</>
)
@@ -59,9 +71,11 @@ export default function TemplateInsightsPage() {
export const TemplateInsightsPageView = ({
templateInsights,
userLatency,
shouldDisplayParameters,
}: {
templateInsights: TemplateInsightsResponse | undefined
userLatency: UserLatencyInsightsResponse | undefined
shouldDisplayParameters: boolean
}) => {
return (
<Box
@@ -81,6 +95,12 @@ export const TemplateInsightsPageView = ({
sx={{ gridColumn: "span 3" }}
data={templateInsights?.report.apps_usage}
/>
{shouldDisplayParameters && (
<TemplateParametersUsagePanel
sx={{ gridColumn: "span 3" }}
data={templateInsights?.report.parameters_usage}
/>
)}
</Box>
)
}
@@ -261,6 +281,219 @@ const TemplateUsagePanel = ({
)
}
const TemplateParametersUsagePanel = ({
data,
...panelProps
}: PanelProps & {
data: TemplateInsightsResponse["report"]["parameters_usage"] | undefined
}) => {
return (
<Panel {...panelProps}>
<PanelHeader>
<PanelTitle>Parameters usage</PanelTitle>
<PanelSubtitle>Last 7 days</PanelSubtitle>
</PanelHeader>
<PanelContent>
{!data && <Loader sx={{ height: 200 }} />}
{data && data.length === 0 && <NoDataAvailable sx={{ height: 200 }} />}
{data &&
data.length > 0 &&
data.map((parameter, parameterIndex) => {
const label =
parameter.display_name !== ""
? parameter.display_name
: parameter.name
return (
<Box
key={parameter.name}
sx={{
display: "flex",
alignItems: "start",
p: 3,
marginX: -3,
borderTop: (theme) => `1px solid ${theme.palette.divider}`,
width: (theme) => `calc(100% + ${theme.spacing(6)})`,
"&:first-child": {
borderTop: 0,
},
}}
>
<Box sx={{ flex: 1 }}>
<Box sx={{ fontWeight: 500 }}>{label}</Box>
<Box
component="p"
sx={{
fontSize: 14,
color: (theme) => theme.palette.text.secondary,
maxWidth: 400,
margin: 0,
}}
>
{parameter.description}
</Box>
</Box>
<Box sx={{ flex: 1, fontSize: 14 }}>
{parameter.values
.sort((a, b) => b.count - a.count)
.map((usage, usageIndex) => (
<Box
key={`${parameterIndex}-${usageIndex}`}
sx={{
display: "flex",
alignItems: "baseline",
justifyContent: "space-between",
py: 0.5,
gap: 5,
}}
>
<ParameterUsageLabel
usage={usage}
parameter={parameter}
/>
<Box sx={{ textAlign: "right" }}>{usage.count}</Box>
</Box>
))}
</Box>
</Box>
)
})}
</PanelContent>
</Panel>
)
}
const ParameterUsageLabel = ({
usage,
parameter,
}: {
usage: TemplateParameterValue
parameter: TemplateParameterUsage
}) => {
if (usage.value.trim() === "") {
return (
<Box
component="span"
sx={{
color: (theme) => theme.palette.text.secondary,
}}
>
Not set
</Box>
)
}
if (parameter.options) {
const option = parameter.options.find((o) => o.value === usage.value)!
const icon = option.icon
const label = option.name
return (
<Box
sx={{
display: "flex",
alignItems: "center",
gap: 2,
}}
>
{icon && (
<Box sx={{ width: 16, height: 16, lineHeight: 1 }}>
<Box
component="img"
src={icon}
sx={{
objectFit: "contain",
width: "100%",
height: "100%",
}}
/>
</Box>
)}
{label}
</Box>
)
}
if (usage.value.startsWith("http")) {
return (
<Link
href={usage.value}
target="_blank"
rel="noreferrer"
sx={{
display: "flex",
alignItems: "center",
gap: 1,
color: (theme) => theme.palette.text.primary,
}}
>
<OpenInNewOutlined sx={{ width: 14, height: 14 }} />
{usage.value}
</Link>
)
}
if (parameter.type === "list(string)") {
const values = JSON.parse(usage.value) as string[]
return (
<Box sx={{ display: "flex", gap: 1, flexWrap: "wrap" }}>
{values.map((v, i) => {
return (
<Box
key={i}
sx={{
p: (theme) => theme.spacing(0.25, 1.5),
borderRadius: 999,
background: (theme) => theme.palette.divider,
whiteSpace: "nowrap",
}}
>
{v}
</Box>
)
})}
</Box>
)
}
if (parameter.type === "bool") {
return (
<Box
sx={{
display: "flex",
alignItems: "center",
gap: 1,
}}
>
{usage.value === "false" ? (
<>
<CancelOutlined
sx={{
width: 16,
height: 16,
color: (theme) => theme.palette.error.light,
}}
/>
False
</>
) : (
<>
<CheckCircleOutlined
sx={{
width: 16,
height: 16,
color: (theme) => theme.palette.success.light,
}}
/>
True
</>
)}
</Box>
)
}
return <Box>{usage.value}</Box>
}
const Panel = styled(Box)(({ theme }) => ({
borderRadius: theme.shape.borderRadius,
border: `1px solid ${theme.palette.divider}`,