Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions coderd/apidoc/docs.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions coderd/apidoc/swagger.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions codersdk/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -1871,6 +1871,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"
)
Expand All @@ -1881,6 +1884,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.
Expand Down
1 change: 1 addition & 0 deletions docs/api/schemas.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions site/src/api/typesGenerated.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,127 @@ export const Loaded: Story = {
seconds: 1020900,
},
],
parameters_usage: [],
parameters_usage: [
{
template_ids: ["0d286645-29aa-4eaf-9b52-cc5d2740c90b"],
display_name: "",
name: "Coder Repository Directory",
values: [
{
value: "${HOME}/src/coder/coder",
count: 1,
},
{
value: "~/coder",
count: 16,
},
{
value: "~/go/src/github.com/coder/coder",
count: 1,
},
{
value: "~/projects/coder/coder",
count: 1,
},
{
value: "~/repos/coder",
count: 1,
},
],
},
{
template_ids: ["0d286645-29aa-4eaf-9b52-cc5d2740c90b"],
display_name: "",
name: "Dotfiles URL",
values: [
{
value: " ",
count: 11,
},
{
value: " git@github.com:jsjoeio/dotfiles.git",
count: 1,
},
{
value: "git@github.com:Emyrk/dotfiles.git",
count: 1,
},
{
value: "git@github.com:ericpaulsen/dot-v2.git",
count: 1,
},
{
value: "git@github.com:johnstcn/dotfiles-coder.git",
count: 2,
},
{
value: "git@github.com:spikecurtis/dotfiles.git",
count: 1,
},
{
value: "https://github.com/bpmct/dot",
count: 1,
},
{
value: "https://github.com/spikecurtis/dotfiles.git",
count: 1,
},
{
value: "https://phorcys.net/dotfiles",
count: 1,
},
],
},
{
template_ids: ["0d286645-29aa-4eaf-9b52-cc5d2740c90b"],
display_name: "",
name: "Region",
options: [
{
name: "Pittsburgh",
description: "",
value: "us-pittsburgh",
icon: "/emojis/1f1fa-1f1f8.png",
},
{
name: "Helsinki",
description: "",
value: "eu-helsinki",
icon: "/emojis/1f1eb-1f1ee.png",
},
{
name: "Sydney",
description: "",
value: "ap-sydney",
icon: "/emojis/1f1e6-1f1fa.png",
},
{
name: "São Paulo",
description: "",
value: "sa-saopaulo",
icon: "/emojis/1f1e7-1f1f7.png",
},
],
values: [
{
value: "ap-sydney",
count: 1,
},
{
value: "eu-helsinki",
count: 5,
},
{
value: "sa-saopaulo",
count: 3,
},
{
value: "us-pittsburgh",
count: 11,
},
],
},
],
},
interval_reports: [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
} from "api/typesGenerated"
import { ComponentProps } from "react"
import subDays from "date-fns/subDays"
import { useDashboard } from "components/Dashboard/DashboardProvider"

export default function TemplateInsightsPage() {
const { template } = useTemplateLayoutContext()
Expand All @@ -41,6 +42,7 @@ export default function TemplateInsightsPage() {
queryKey: ["templates", template.id, "user-latency"],
queryFn: () => getInsightsUserLatency(insightsFilter),
})
const dashboard = useDashboard()

return (
<>
Expand All @@ -50,6 +52,9 @@ export default function TemplateInsightsPage() {
<TemplateInsightsPageView
templateInsights={templateInsights}
userLatency={userLatency}
shouldDisplayParameters={dashboard.experiments.includes(
"template_parameters_insights",
)}
/>
</>
)
Expand All @@ -58,9 +63,11 @@ export default function TemplateInsightsPage() {
export const TemplateInsightsPageView = ({
templateInsights,
userLatency,
shouldDisplayParameters,
}: {
templateInsights: TemplateInsightsResponse | undefined
userLatency: UserLatencyInsightsResponse | undefined
shouldDisplayParameters: boolean
}) => {
return (
<Box
Expand All @@ -80,6 +87,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>
)
}
Expand Down Expand Up @@ -260,6 +273,112 @@ 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) => {
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={{ fontWeight: 500, flex: 1 }}>{label}</Box>
<Box sx={{ flex: 1, fontSize: 14 }}>
{parameter.values
.sort((a, b) => b.count - a.count)
.map((value) => {
const icon = parameter.options
? parameter.options.find((o) => o.value === value.value)
?.icon
: undefined
const label =
value.value.trim() !== "" ? (
value.value
) : (
<Box
component="span"
sx={{
color: (theme) => theme.palette.text.secondary,
}}
>
Not set
</Box>
)
return (
<Box
key={value.value}
sx={{
display: "flex",
alignItems: "baseline",
justifyContent: "space-between",
py: 0.5,
}}
>
<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>
<Box sx={{ textAlign: "right" }}>{value.count}</Box>
</Box>
)
})}
</Box>
</Box>
)
})}
</PanelContent>
</Panel>
)
}

const Panel = styled(Box)(({ theme }) => ({
borderRadius: theme.shape.borderRadius,
border: `1px solid ${theme.palette.divider}`,
Expand Down