diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go
index 61a6989ba27fd..9faf9de1611cc 100644
--- a/coderd/apidoc/docs.go
+++ b/coderd/apidoc/docs.go
@@ -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": {
diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json
index 982ed816264c1..635457901db36 100644
--- a/coderd/apidoc/swagger.json
+++ b/coderd/apidoc/swagger.json
@@ -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": {
diff --git a/codersdk/deployment.go b/codersdk/deployment.go
index e714d9c1c34b5..fdc5b92af28de 100644
--- a/codersdk/deployment.go
+++ b/codersdk/deployment.go
@@ -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.
diff --git a/docs/api/schemas.md b/docs/api/schemas.md
index 2f09bfb6728ae..4ab6eceeef96d 100644
--- a/docs/api/schemas.md
+++ b/docs/api/schemas.md
@@ -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
diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts
index 7b9b79d69b3db..c01f4d94385ba 100644
--- a/site/src/api/typesGenerated.ts
+++ b/site/src/api/typesGenerated.ts
@@ -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",
]
diff --git a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.stories.tsx b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.stories.tsx
index 49678eef08375..4fb65c0b2b5f1 100644
--- a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.stories.tsx
+++ b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.stories.tsx
@@ -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: [
{
diff --git a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx
index d6161124d3540..77bf66a0759e9 100644
--- a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx
+++ b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx
@@ -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() {
>
)
@@ -59,9 +71,11 @@ export default function TemplateInsightsPage() {
export const TemplateInsightsPageView = ({
templateInsights,
userLatency,
+ shouldDisplayParameters,
}: {
templateInsights: TemplateInsightsResponse | undefined
userLatency: UserLatencyInsightsResponse | undefined
+ shouldDisplayParameters: boolean
}) => {
return (
+ {shouldDisplayParameters && (
+
+ )}
)
}
@@ -261,6 +281,219 @@ const TemplateUsagePanel = ({
)
}
+const TemplateParametersUsagePanel = ({
+ data,
+ ...panelProps
+}: PanelProps & {
+ data: TemplateInsightsResponse["report"]["parameters_usage"] | undefined
+}) => {
+ return (
+
+
+ Parameters usage
+ Last 7 days
+
+
+ {!data && }
+ {data && data.length === 0 && }
+ {data &&
+ data.length > 0 &&
+ data.map((parameter, parameterIndex) => {
+ const label =
+ parameter.display_name !== ""
+ ? parameter.display_name
+ : parameter.name
+ return (
+ `1px solid ${theme.palette.divider}`,
+ width: (theme) => `calc(100% + ${theme.spacing(6)})`,
+ "&:first-child": {
+ borderTop: 0,
+ },
+ }}
+ >
+
+ {label}
+ theme.palette.text.secondary,
+ maxWidth: 400,
+ margin: 0,
+ }}
+ >
+ {parameter.description}
+
+
+
+ {parameter.values
+ .sort((a, b) => b.count - a.count)
+ .map((usage, usageIndex) => (
+
+
+ {usage.count}
+
+ ))}
+
+
+ )
+ })}
+
+
+ )
+}
+
+const ParameterUsageLabel = ({
+ usage,
+ parameter,
+}: {
+ usage: TemplateParameterValue
+ parameter: TemplateParameterUsage
+}) => {
+ if (usage.value.trim() === "") {
+ return (
+ theme.palette.text.secondary,
+ }}
+ >
+ Not set
+
+ )
+ }
+
+ if (parameter.options) {
+ const option = parameter.options.find((o) => o.value === usage.value)!
+ const icon = option.icon
+ const label = option.name
+
+ return (
+
+ {icon && (
+
+
+
+ )}
+ {label}
+
+ )
+ }
+
+ if (usage.value.startsWith("http")) {
+ return (
+ theme.palette.text.primary,
+ }}
+ >
+
+ {usage.value}
+
+ )
+ }
+
+ if (parameter.type === "list(string)") {
+ const values = JSON.parse(usage.value) as string[]
+ return (
+
+ {values.map((v, i) => {
+ return (
+ theme.spacing(0.25, 1.5),
+ borderRadius: 999,
+ background: (theme) => theme.palette.divider,
+ whiteSpace: "nowrap",
+ }}
+ >
+ {v}
+
+ )
+ })}
+
+ )
+ }
+
+ if (parameter.type === "bool") {
+ return (
+
+ {usage.value === "false" ? (
+ <>
+ theme.palette.error.light,
+ }}
+ />
+ False
+ >
+ ) : (
+ <>
+ theme.palette.success.light,
+ }}
+ />
+ True
+ >
+ )}
+
+ )
+ }
+
+ return {usage.value}
+}
+
const Panel = styled(Box)(({ theme }) => ({
borderRadius: theme.shape.borderRadius,
border: `1px solid ${theme.palette.divider}`,