From a27af594196ac98648295bfeab0d8744ffa0072b Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Mon, 2 Oct 2023 14:55:48 +0000 Subject: [PATCH 01/18] Add weekly control --- site/src/api/api.ts | 7 +- .../TemplateInsightsPage/IntervalMenu.tsx | 80 +++++++++++++++++++ .../TemplateInsightsPage.tsx | 31 +++++-- 3 files changed, 105 insertions(+), 13 deletions(-) create mode 100644 site/src/pages/TemplatePage/TemplateInsightsPage/IntervalMenu.tsx diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 9828ab78848ba..4bc533c9b00f7 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -1483,12 +1483,9 @@ export const getInsightsUserLatency = async ( }; export const getInsightsTemplate = async ( - filters: InsightsFilter, + filters: InsightsFilter & { interval: "day" | "week" }, ): Promise => { - const params = new URLSearchParams({ - ...filters, - interval: "day", - }); + const params = new URLSearchParams(filters); const response = await axios.get(`/api/v2/insights/templates?${params}`); return response.data; }; diff --git a/site/src/pages/TemplatePage/TemplateInsightsPage/IntervalMenu.tsx b/site/src/pages/TemplatePage/TemplateInsightsPage/IntervalMenu.tsx new file mode 100644 index 0000000000000..5e709c58d280d --- /dev/null +++ b/site/src/pages/TemplatePage/TemplateInsightsPage/IntervalMenu.tsx @@ -0,0 +1,80 @@ +import CheckOutlined from "@mui/icons-material/CheckOutlined"; +import ExpandMoreOutlined from "@mui/icons-material/ExpandMoreOutlined"; +import Box from "@mui/material/Box"; +import Button from "@mui/material/Button"; +import Menu from "@mui/material/Menu"; +import MenuItem from "@mui/material/MenuItem"; +import { useState, MouseEvent } from "react"; + +export const insightsIntervals = { + day: { + label: "Daily", + }, + week: { + label: "Weekly", + }, +} as const; + +export type InsightsInterval = keyof typeof insightsIntervals; + +export const IntervalMenu = ({ + value, + onChange, +}: { + value: InsightsInterval; + onChange: (value: InsightsInterval) => void; +}) => { + const [anchorEl, setAnchorEl] = useState(null); + const open = Boolean(anchorEl); + const handleClick = (event: MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + const handleClose = () => { + setAnchorEl(null); + }; + + return ( +
+ + + {Object.keys(insightsIntervals).map((interval) => { + const { label } = insightsIntervals[interval as InsightsInterval]; + return ( + { + onChange(interval as InsightsInterval); + handleClose(); + }} + > + {label} + + {value === interval && ( + + )} + + + ); + })} + +
+ ); +}; diff --git a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx index e6ae5e642e903..14b95594f02d5 100644 --- a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx +++ b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx @@ -36,15 +36,17 @@ import CancelOutlined from "@mui/icons-material/CancelOutlined"; import { getDateRangeFilter } from "./utils"; import Tooltip from "@mui/material/Tooltip"; import LinkOutlined from "@mui/icons-material/LinkOutlined"; +import { InsightsInterval, IntervalMenu } from "./IntervalMenu"; export default function TemplateInsightsPage() { const now = new Date(); + const [interval, setInterval] = useState("day"); const [dateRangeValue, setDateRangeValue] = useState({ startDate: subDays(now, 6), endDate: now, }); const { template } = useTemplateLayoutContext(); - const insightsFilter = { + const commonFilters = { template_ids: template.id, ...getDateRangeFilter({ startDate: dateRangeValue.startDate, @@ -53,13 +55,14 @@ export default function TemplateInsightsPage() { isToday, }), }; + const insightsFilter = { ...commonFilters, interval }; const { data: templateInsights } = useQuery({ queryKey: ["templates", template.id, "usage", insightsFilter], queryFn: () => getInsightsTemplate(insightsFilter), }); const { data: userLatency } = useQuery({ - queryKey: ["templates", template.id, "user-latency", insightsFilter], - queryFn: () => getInsightsUserLatency(insightsFilter), + queryKey: ["templates", template.id, "user-latency", commonFilters], + queryFn: () => getInsightsUserLatency(commonFilters), }); return ( @@ -68,8 +71,11 @@ export default function TemplateInsightsPage() { {getTemplatePageTitle("Insights", template)} + controls={ + <> + + + } templateInsights={templateInsights} userLatency={userLatency} @@ -81,15 +87,24 @@ export default function TemplateInsightsPage() { export const TemplateInsightsPageView = ({ templateInsights, userLatency, - dateRange, + controls, }: { templateInsights: TemplateInsightsResponse | undefined; userLatency: UserLatencyInsightsResponse | undefined; - dateRange: ReactNode; + controls: ReactNode; }) => { return ( <> - {dateRange} + ({ + marginBottom: theme.spacing(4), + display: "flex", + alignItems: "center", + gap: theme.spacing(1), + })} + > + {controls} + Date: Mon, 2 Oct 2023 15:06:47 +0000 Subject: [PATCH 02/18] Interval menu position --- .../TemplateInsightsPage/IntervalMenu.tsx | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/site/src/pages/TemplatePage/TemplateInsightsPage/IntervalMenu.tsx b/site/src/pages/TemplatePage/TemplateInsightsPage/IntervalMenu.tsx index 5e709c58d280d..80d413ebbb39a 100644 --- a/site/src/pages/TemplatePage/TemplateInsightsPage/IntervalMenu.tsx +++ b/site/src/pages/TemplatePage/TemplateInsightsPage/IntervalMenu.tsx @@ -4,7 +4,7 @@ import Box from "@mui/material/Box"; import Button from "@mui/material/Button"; import Menu from "@mui/material/Menu"; import MenuItem from "@mui/material/MenuItem"; -import { useState, MouseEvent } from "react"; +import { useState, useRef } from "react"; export const insightsIntervals = { day: { @@ -24,35 +24,42 @@ export const IntervalMenu = ({ value: InsightsInterval; onChange: (value: InsightsInterval) => void; }) => { - const [anchorEl, setAnchorEl] = useState(null); - const open = Boolean(anchorEl); - const handleClick = (event: MouseEvent) => { - setAnchorEl(event.currentTarget); - }; + const anchorRef = useRef(null); + const [open, setOpen] = useState(false); + const handleClose = () => { - setAnchorEl(null); + setOpen(false); }; return (
{Object.keys(insightsIntervals).map((interval) => { const { label } = insightsIntervals[interval as InsightsInterval]; From 41cc6386232a39816c98dcc875fe261da10c3184 Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Mon, 2 Oct 2023 17:13:53 +0000 Subject: [PATCH 03/18] Add weekly controls --- .../TemplateInsightsPage.tsx | 64 ++++++++++++--- .../WeeklyPresetsMenu.tsx | 79 +++++++++++++++++++ .../TemplateInsightsPage/utils.ts | 17 ++-- 3 files changed, 142 insertions(+), 18 deletions(-) create mode 100644 site/src/pages/TemplatePage/TemplateInsightsPage/WeeklyPresetsMenu.tsx diff --git a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx index 14b95594f02d5..f57409afc072c 100644 --- a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx +++ b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx @@ -26,7 +26,14 @@ import { UserLatencyInsightsResponse, } from "api/typesGenerated"; import { ComponentProps, ReactNode, useState } from "react"; -import { subDays, isToday } from "date-fns"; +import { + subDays, + subWeeks, + startOfWeek, + endOfWeek, + isSunday, + endOfDay, +} from "date-fns"; import "react-date-range/dist/styles.css"; import "react-date-range/dist/theme/default.css"; import { DateRange, DateRangeValue } from "./DateRange"; @@ -37,23 +44,30 @@ import { getDateRangeFilter } from "./utils"; import Tooltip from "@mui/material/Tooltip"; import LinkOutlined from "@mui/icons-material/LinkOutlined"; import { InsightsInterval, IntervalMenu } from "./IntervalMenu"; +import { WeeklyPreset, WeeklyPresetsMenu } from "./WeeklyPresetsMenu"; export default function TemplateInsightsPage() { + const { template } = useTemplateLayoutContext(); const now = new Date(); - const [interval, setInterval] = useState("day"); - const [dateRangeValue, setDateRangeValue] = useState({ + + const defaultWeeklyPreset = 4; + const defaultDateRangeValue = { startDate: subDays(now, 6), endDate: now, - }); - const { template } = useTemplateLayoutContext(); + }; + + const [interval, setInterval] = useState("day"); + const [weeklyPreset, setWeeklyPreset] = + useState(defaultWeeklyPreset); + const [dateRangeValue, setDateRangeValue] = useState( + defaultDateRangeValue, + ); + + const dateRange = + interval === "day" ? dateRangeValue : getWeeklyRange(weeklyPreset); const commonFilters = { template_ids: template.id, - ...getDateRangeFilter({ - startDate: dateRangeValue.startDate, - endDate: dateRangeValue.endDate, - now, - isToday, - }), + ...getDateRangeFilter(dateRange), }; const insightsFilter = { ...commonFilters, interval }; const { data: templateInsights } = useQuery({ @@ -73,8 +87,25 @@ export default function TemplateInsightsPage() { - - + { + setInterval(interval); + if (interval === "week") { + setWeeklyPreset(defaultWeeklyPreset); + } else { + setDateRangeValue(defaultDateRangeValue); + } + }} + /> + {interval === "day" ? ( + + ) : ( + + )} } templateInsights={templateInsights} @@ -598,6 +629,13 @@ const TextValue = ({ children }: { children: ReactNode }) => { ); }; +const getWeeklyRange = (numberOfWeeks: WeeklyPreset) => { + const now = new Date(); + const startDate = startOfWeek(subWeeks(now, numberOfWeeks)); + const endDate = isSunday(now) ? endOfDay(now) : endOfWeek(subWeeks(now, 1)); + return { startDate, endDate }; +}; + function mapToDAUsResponse( data: TemplateInsightsResponse["interval_reports"], ): DAUsResponse { diff --git a/site/src/pages/TemplatePage/TemplateInsightsPage/WeeklyPresetsMenu.tsx b/site/src/pages/TemplatePage/TemplateInsightsPage/WeeklyPresetsMenu.tsx new file mode 100644 index 0000000000000..9bb8c29c5b847 --- /dev/null +++ b/site/src/pages/TemplatePage/TemplateInsightsPage/WeeklyPresetsMenu.tsx @@ -0,0 +1,79 @@ +import CheckOutlined from "@mui/icons-material/CheckOutlined"; +import ExpandMoreOutlined from "@mui/icons-material/ExpandMoreOutlined"; +import Box from "@mui/material/Box"; +import Button from "@mui/material/Button"; +import Menu from "@mui/material/Menu"; +import MenuItem from "@mui/material/MenuItem"; +import { useState, useRef } from "react"; + +export const weeklyPresets = [4, 12, 24, 48] as const; + +export type WeeklyPreset = (typeof weeklyPresets)[number]; + +export const WeeklyPresetsMenu = ({ + value, + onChange, +}: { + value: WeeklyPreset; + onChange: (value: WeeklyPreset) => void; +}) => { + const anchorRef = useRef(null); + const [open, setOpen] = useState(false); + + const handleClose = () => { + setOpen(false); + }; + + return ( +
+ + + {weeklyPresets.map((numberOfWeeks) => { + return ( + { + onChange(numberOfWeeks); + handleClose(); + }} + > + {numberOfWeeks} weeks ago + + {value === numberOfWeeks && ( + + )} + + + ); + })} + +
+ ); +}; diff --git a/site/src/pages/TemplatePage/TemplateInsightsPage/utils.ts b/site/src/pages/TemplatePage/TemplateInsightsPage/utils.ts index 13c3991c78ad2..868eb5c61c637 100644 --- a/site/src/pages/TemplatePage/TemplateInsightsPage/utils.ts +++ b/site/src/pages/TemplatePage/TemplateInsightsPage/utils.ts @@ -1,15 +1,22 @@ -import { addDays, addHours, format, startOfDay, startOfHour } from "date-fns"; +import { + addDays, + addHours, + format, + startOfDay, + startOfHour, + isToday as isTodayDefault, +} from "date-fns"; export function getDateRangeFilter({ startDate, endDate, - now, - isToday, + now = new Date(), + isToday = isTodayDefault, }: { startDate: Date; endDate: Date; - now: Date; - isToday: (date: Date) => boolean; + now?: Date; + isToday?: (date: Date) => boolean; }) { return { start_time: toISOLocal(startOfDay(startDate)), From 86fab98073e22e07bca78dc77767ea651a9bdf6b Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Mon, 2 Oct 2023 17:16:19 +0000 Subject: [PATCH 04/18] Fix title and description --- site/src/components/DAUChart/DAUChart.tsx | 10 ++++------ .../GeneralSettingsPage/GeneralSettingsPageView.tsx | 4 ++-- .../TemplateInsightsPage/TemplateInsightsPage.tsx | 8 ++++---- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/site/src/components/DAUChart/DAUChart.tsx b/site/src/components/DAUChart/DAUChart.tsx index 16b013a78c54c..fa3b87a0c2dd0 100644 --- a/site/src/components/DAUChart/DAUChart.tsx +++ b/site/src/components/DAUChart/DAUChart.tsx @@ -115,17 +115,15 @@ export const DAUChart: FC = ({ daus }) => { ); }; -export const DAUTitle = () => { +export const ActiveUsersTitle = () => { return ( - Daily Active Users + Active Users - - How do we calculate daily active users? - + How do we calculate active users? When a connection is initiated to a user's workspace they are - considered a daily active user. e.g. apps, web terminal, SSH + considered an active user. e.g. apps, web terminal, SSH diff --git a/site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx b/site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx index 378d2d431b938..06989ea8e59c8 100644 --- a/site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx +++ b/site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx @@ -1,7 +1,7 @@ import Box from "@mui/material/Box"; import { ClibaseOption, DAUsResponse } from "api/typesGenerated"; import { ErrorAlert } from "components/Alert/ErrorAlert"; -import { DAUChart, DAUTitle } from "components/DAUChart/DAUChart"; +import { DAUChart, ActiveUsersTitle } from "components/DAUChart/DAUChart"; import { Header } from "components/DeploySettingsLayout/Header"; import OptionsTable from "components/DeploySettingsLayout/OptionsTable"; import { Stack } from "components/Stack/Stack"; @@ -32,7 +32,7 @@ export const GeneralSettingsPageView = ({ )} {deploymentDAUs && ( - }> + }> diff --git a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx index f57409afc072c..d5a998bcfc082 100644 --- a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx +++ b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx @@ -4,7 +4,7 @@ import { styled, useTheme } from "@mui/material/styles"; import { BoxProps } from "@mui/system"; import { useQuery } from "@tanstack/react-query"; import { getInsightsTemplate, getInsightsUserLatency } from "api/api"; -import { DAUChart, DAUTitle } from "components/DAUChart/DAUChart"; +import { ActiveUsersTitle, DAUChart } from "components/DAUChart/DAUChart"; import { useTemplateLayoutContext } from "pages/TemplatePage/TemplateLayout"; import { HelpTooltip, @@ -144,7 +144,7 @@ export const TemplateInsightsPageView = ({ gap: (theme) => theme.spacing(3), }} > - @@ -162,7 +162,7 @@ export const TemplateInsightsPageView = ({ ); }; -const DailyUsersPanel = ({ +const ActiveUsersPanel = ({ data, ...panelProps }: PanelProps & { @@ -172,7 +172,7 @@ const DailyUsersPanel = ({ - + From 78e6db05c1bb4c93e6235c96948726c9d82cabc2 Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Mon, 2 Oct 2023 17:24:32 +0000 Subject: [PATCH 05/18] Modify DAUChart to be agnostic to interval --- .../ActiveUserChart.tsx} | 15 +++++---- .../GeneralSettingsPageView.tsx | 7 +++-- .../TemplateInsightsPage.tsx | 31 +++++++++---------- 3 files changed, 26 insertions(+), 27 deletions(-) rename site/src/components/{DAUChart/DAUChart.tsx => ActiveUserChart/ActiveUserChart.tsx} (88%) diff --git a/site/src/components/DAUChart/DAUChart.tsx b/site/src/components/ActiveUserChart/ActiveUserChart.tsx similarity index 88% rename from site/src/components/DAUChart/DAUChart.tsx rename to site/src/components/ActiveUserChart/ActiveUserChart.tsx index fa3b87a0c2dd0..000713fa2a4a8 100644 --- a/site/src/components/DAUChart/DAUChart.tsx +++ b/site/src/components/ActiveUserChart/ActiveUserChart.tsx @@ -1,7 +1,6 @@ import Box from "@mui/material/Box"; import { Theme } from "@mui/material/styles"; import useTheme from "@mui/styles/useTheme"; -import * as TypesGen from "api/typesGenerated"; import { CategoryScale, Chart as ChartJS, @@ -38,18 +37,18 @@ ChartJS.register( Legend, ); -export interface DAUChartProps { - daus: TypesGen.DAUsResponse; +export interface ActiveUserChartProps { + data: { date: string; amount: number }[]; } -export const DAUChart: FC = ({ daus }) => { +export const ActiveUserChart: FC = ({ data }) => { const theme: Theme = useTheme(); - const labels = daus.entries.map((val) => { + const labels = data.map((val) => { return dayjs(val.date).format("YYYY-MM-DD"); }); - const data = daus.entries.map((val) => { + const chartData = data.map((val) => { return val.amount; }); @@ -82,7 +81,7 @@ export const DAUChart: FC = ({ daus }) => { x: { ticks: { - stepSize: daus.entries.length > 10 ? 2 : undefined, + stepSize: data.length > 10 ? 2 : undefined, }, type: "time", time: { @@ -101,7 +100,7 @@ export const DAUChart: FC = ({ daus }) => { datasets: [ { label: "Daily Active Users", - data: data, + data: chartData, pointBackgroundColor: theme.palette.info.light, pointBorderColor: theme.palette.info.light, borderColor: theme.palette.info.light, diff --git a/site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx b/site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx index 06989ea8e59c8..9929150cd6f16 100644 --- a/site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx +++ b/site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx @@ -1,7 +1,10 @@ import Box from "@mui/material/Box"; import { ClibaseOption, DAUsResponse } from "api/typesGenerated"; import { ErrorAlert } from "components/Alert/ErrorAlert"; -import { DAUChart, ActiveUsersTitle } from "components/DAUChart/DAUChart"; +import { + ActiveUserChart, + ActiveUsersTitle, +} from "components/ActiveUserChart/ActiveUserChart"; import { Header } from "components/DeploySettingsLayout/Header"; import OptionsTable from "components/DeploySettingsLayout/OptionsTable"; import { Stack } from "components/Stack/Stack"; @@ -33,7 +36,7 @@ export const GeneralSettingsPageView = ({ {deploymentDAUs && ( }> - + )} diff --git a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx index d5a998bcfc082..6840fb67506bb 100644 --- a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx +++ b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx @@ -4,7 +4,10 @@ import { styled, useTheme } from "@mui/material/styles"; import { BoxProps } from "@mui/system"; import { useQuery } from "@tanstack/react-query"; import { getInsightsTemplate, getInsightsUserLatency } from "api/api"; -import { ActiveUsersTitle, DAUChart } from "components/DAUChart/DAUChart"; +import { + ActiveUsersTitle, + ActiveUserChart, +} from "components/ActiveUserChart/ActiveUserChart"; import { useTemplateLayoutContext } from "pages/TemplatePage/TemplateLayout"; import { HelpTooltip, @@ -19,7 +22,6 @@ import { Helmet } from "react-helmet-async"; import { getTemplatePageTitle } from "../utils"; import { Loader } from "components/Loader/Loader"; import { - DAUsResponse, TemplateInsightsResponse, TemplateParameterUsage, TemplateParameterValue, @@ -178,7 +180,16 @@ const ActiveUsersPanel = ({ {!data && } {data && data.length === 0 && } - {data && data.length > 0 && } + {data && data.length > 0 && ( + { + return { + amount: d.active_users, + date: d.start_time, + }; + })} + /> + )} ); @@ -636,20 +647,6 @@ const getWeeklyRange = (numberOfWeeks: WeeklyPreset) => { return { startDate, endDate }; }; -function mapToDAUsResponse( - data: TemplateInsightsResponse["interval_reports"], -): DAUsResponse { - return { - tz_hour_offset: 0, - entries: data.map((d) => { - return { - amount: d.active_users, - date: d.start_time, - }; - }), - }; -} - function formatTime(seconds: number): string { if (seconds < 60) { return seconds + " seconds"; From 6b4e763f3a912d4ff6cd6ec1618e8f2510c628dd Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Mon, 2 Oct 2023 17:38:06 +0000 Subject: [PATCH 06/18] Update chart to support weekly data --- site/src/components/ActiveUserChart/ActiveUserChart.tsx | 8 ++++++-- .../GeneralSettingsPage/GeneralSettingsPageView.tsx | 2 +- .../TemplateInsightsPage/TemplateInsightsPage.tsx | 7 +++++++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/site/src/components/ActiveUserChart/ActiveUserChart.tsx b/site/src/components/ActiveUserChart/ActiveUserChart.tsx index 000713fa2a4a8..eafaf3eb3afd9 100644 --- a/site/src/components/ActiveUserChart/ActiveUserChart.tsx +++ b/site/src/components/ActiveUserChart/ActiveUserChart.tsx @@ -39,9 +39,13 @@ ChartJS.register( export interface ActiveUserChartProps { data: { date: string; amount: number }[]; + interval: "day" | "week"; } -export const ActiveUserChart: FC = ({ data }) => { +export const ActiveUserChart: FC = ({ + data, + interval, +}) => { const theme: Theme = useTheme(); const labels = data.map((val) => { @@ -85,7 +89,7 @@ export const ActiveUserChart: FC = ({ data }) => { }, type: "time", time: { - unit: "day", + unit: interval, }, }, }, diff --git a/site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx b/site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx index 9929150cd6f16..03c4e44a93195 100644 --- a/site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx +++ b/site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx @@ -36,7 +36,7 @@ export const GeneralSettingsPageView = ({ {deploymentDAUs && ( }> - + )} diff --git a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx index 6840fb67506bb..7fd67dd9302b3 100644 --- a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx +++ b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx @@ -112,6 +112,7 @@ export default function TemplateInsightsPage() { } templateInsights={templateInsights} userLatency={userLatency} + interval={interval} /> ); @@ -121,10 +122,12 @@ export const TemplateInsightsPageView = ({ templateInsights, userLatency, controls, + interval, }: { templateInsights: TemplateInsightsResponse | undefined; userLatency: UserLatencyInsightsResponse | undefined; controls: ReactNode; + interval: InsightsInterval; }) => { return ( <> @@ -148,6 +151,7 @@ export const TemplateInsightsPageView = ({ > @@ -166,9 +170,11 @@ export const TemplateInsightsPageView = ({ const ActiveUsersPanel = ({ data, + interval, ...panelProps }: PanelProps & { data: TemplateInsightsResponse["interval_reports"] | undefined; + interval: InsightsInterval; }) => { return ( @@ -182,6 +188,7 @@ const ActiveUsersPanel = ({ {data && data.length === 0 && } {data && data.length > 0 && ( { return { amount: d.active_users, From 56b9484d84a10c81d99c2ae10655f97393cd3380 Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Mon, 2 Oct 2023 17:41:40 +0000 Subject: [PATCH 07/18] Make default interval as week --- .../TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx index 7fd67dd9302b3..715bf32e62f43 100644 --- a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx +++ b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx @@ -58,7 +58,7 @@ export default function TemplateInsightsPage() { endDate: now, }; - const [interval, setInterval] = useState("day"); + const [interval, setInterval] = useState("week"); const [weeklyPreset, setWeeklyPreset] = useState(defaultWeeklyPreset); const [dateRangeValue, setDateRangeValue] = useState( From f1ea3613d9eef5a3452b5da366fc75e05ec6fe7a Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Mon, 2 Oct 2023 17:48:51 +0000 Subject: [PATCH 08/18] If template is created less than 5 weeks, show daily --- .../TemplateInsightsPage/TemplateInsightsPage.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx index 715bf32e62f43..14230422903a7 100644 --- a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx +++ b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx @@ -35,6 +35,7 @@ import { endOfWeek, isSunday, endOfDay, + addWeeks, } from "date-fns"; import "react-date-range/dist/styles.css"; import "react-date-range/dist/theme/default.css"; @@ -58,7 +59,11 @@ export default function TemplateInsightsPage() { endDate: now, }; - const [interval, setInterval] = useState("week"); + const [interval, setInterval] = useState(() => { + const templateCreateDate = new Date(template.created_at); + const hasFiveWeeksOrMore = addWeeks(templateCreateDate, 5) < now; + return hasFiveWeeksOrMore ? "week" : "day"; + }); const [weeklyPreset, setWeeklyPreset] = useState(defaultWeeklyPreset); const [dateRangeValue, setDateRangeValue] = useState( From 4cfba45dfab5bb857eb17e57ae0ed938e941190f Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Tue, 3 Oct 2023 12:12:54 +0000 Subject: [PATCH 09/18] Update copy --- .../TemplatePage/TemplateInsightsPage/WeeklyPresetsMenu.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site/src/pages/TemplatePage/TemplateInsightsPage/WeeklyPresetsMenu.tsx b/site/src/pages/TemplatePage/TemplateInsightsPage/WeeklyPresetsMenu.tsx index 9bb8c29c5b847..33f56785d4db9 100644 --- a/site/src/pages/TemplatePage/TemplateInsightsPage/WeeklyPresetsMenu.tsx +++ b/site/src/pages/TemplatePage/TemplateInsightsPage/WeeklyPresetsMenu.tsx @@ -35,7 +35,7 @@ export const WeeklyPresetsMenu = ({ onClick={() => setOpen(true)} endIcon={} > - {value} weeks ago + Last {value} weeks - {numberOfWeeks} weeks ago + Last {numberOfWeeks} weeks {value === numberOfWeeks && ( From 53c282d032df26d0eeb1765d5bd8a920bfefb0b8 Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Tue, 3 Oct 2023 12:20:24 +0000 Subject: [PATCH 10/18] Move queries to queries folder --- site/src/api/api.ts | 16 +++++++++++----- site/src/api/queries/insights.ts | 15 +++++++++++++++ .../TemplateInsightsPage.tsx | 12 +++--------- 3 files changed, 29 insertions(+), 14 deletions(-) create mode 100644 site/src/api/queries/insights.ts diff --git a/site/src/api/api.ts b/site/src/api/api.ts index e6a539dc60520..cb8067d1fb7e0 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -1471,25 +1471,31 @@ export const getWorkspaceParameters = async (workspace: TypesGen.Workspace) => { }; }; -type InsightsFilter = { +export type InsightsParams = { start_time: string; end_time: string; template_ids: string; }; export const getInsightsUserLatency = async ( - filters: InsightsFilter, + filters: InsightsParams, ): Promise => { const params = new URLSearchParams(filters); const response = await axios.get(`/api/v2/insights/user-latency?${params}`); return response.data; }; +export type InsightsTemplateParams = InsightsParams & { + interval: "day" | "week"; +}; + export const getInsightsTemplate = async ( - filters: InsightsFilter & { interval: "day" | "week" }, + params: InsightsTemplateParams, ): Promise => { - const params = new URLSearchParams(filters); - const response = await axios.get(`/api/v2/insights/templates?${params}`); + const searchParams = new URLSearchParams(params); + const response = await axios.get( + `/api/v2/insights/templates?${searchParams}`, + ); return response.data; }; diff --git a/site/src/api/queries/insights.ts b/site/src/api/queries/insights.ts new file mode 100644 index 0000000000000..f3aef1ba58ca0 --- /dev/null +++ b/site/src/api/queries/insights.ts @@ -0,0 +1,15 @@ +import * as API from "api/api"; + +export const insightsTemplate = (params: API.InsightsTemplateParams) => { + return { + queryKey: ["insights", "templates", params.template_ids, params], + queryFn: () => API.getInsightsTemplate(params), + }; +}; + +export const insightsUserLatency = (params: API.InsightsParams) => { + return { + queryKey: ["insights", "userLatency", params.template_ids, params], + queryFn: () => API.getInsightsUserLatency(params), + }; +}; diff --git a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx index 14230422903a7..d7bb3f903c400 100644 --- a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx +++ b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx @@ -3,7 +3,6 @@ import Box from "@mui/material/Box"; import { styled, useTheme } from "@mui/material/styles"; import { BoxProps } from "@mui/system"; import { useQuery } from "@tanstack/react-query"; -import { getInsightsTemplate, getInsightsUserLatency } from "api/api"; import { ActiveUsersTitle, ActiveUserChart, @@ -48,6 +47,7 @@ import Tooltip from "@mui/material/Tooltip"; import LinkOutlined from "@mui/icons-material/LinkOutlined"; import { InsightsInterval, IntervalMenu } from "./IntervalMenu"; import { WeeklyPreset, WeeklyPresetsMenu } from "./WeeklyPresetsMenu"; +import { insightsTemplate, insightsUserLatency } from "api/queries/insights"; export default function TemplateInsightsPage() { const { template } = useTemplateLayoutContext(); @@ -77,14 +77,8 @@ export default function TemplateInsightsPage() { ...getDateRangeFilter(dateRange), }; const insightsFilter = { ...commonFilters, interval }; - const { data: templateInsights } = useQuery({ - queryKey: ["templates", template.id, "usage", insightsFilter], - queryFn: () => getInsightsTemplate(insightsFilter), - }); - const { data: userLatency } = useQuery({ - queryKey: ["templates", template.id, "user-latency", commonFilters], - queryFn: () => getInsightsUserLatency(commonFilters), - }); + const { data: templateInsights } = useQuery(insightsTemplate(insightsFilter)); + const { data: userLatency } = useQuery(insightsUserLatency(commonFilters)); return ( <> From cf79978f1379952f1b4a8587a95bf9076e298724 Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Tue, 3 Oct 2023 12:30:31 +0000 Subject: [PATCH 11/18] Move weekly and interval to search params --- .../TemplateInsightsPage.tsx | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx index d7bb3f903c400..fa086d0bc6030 100644 --- a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx +++ b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx @@ -21,6 +21,7 @@ import { Helmet } from "react-helmet-async"; import { getTemplatePageTitle } from "../utils"; import { Loader } from "components/Loader/Loader"; import { + Template, TemplateInsightsResponse, TemplateParameterUsage, TemplateParameterValue, @@ -48,24 +49,31 @@ import LinkOutlined from "@mui/icons-material/LinkOutlined"; import { InsightsInterval, IntervalMenu } from "./IntervalMenu"; import { WeeklyPreset, WeeklyPresetsMenu } from "./WeeklyPresetsMenu"; import { insightsTemplate, insightsUserLatency } from "api/queries/insights"; +import { useSearchParams } from "react-router-dom"; export default function TemplateInsightsPage() { const { template } = useTemplateLayoutContext(); const now = new Date(); + const [searchParams, setSearchParams] = useSearchParams(); const defaultWeeklyPreset = 4; const defaultDateRangeValue = { startDate: subDays(now, 6), endDate: now, }; + const defaultInterval = getDefaultInterval(template); + + const interval = + (searchParams.get("interval") as InsightsInterval) || defaultInterval; + + const weeklyPreset = + (Number(searchParams.get("weeklyPreset")) as WeeklyPreset) || + defaultWeeklyPreset; + const setWeeklyPreset = (newWeeklyPreset: WeeklyPreset) => { + searchParams.set("weeklyPreset", newWeeklyPreset.toString()); + setSearchParams(searchParams); + }; - const [interval, setInterval] = useState(() => { - const templateCreateDate = new Date(template.created_at); - const hasFiveWeeksOrMore = addWeeks(templateCreateDate, 5) < now; - return hasFiveWeeksOrMore ? "week" : "day"; - }); - const [weeklyPreset, setWeeklyPreset] = - useState(defaultWeeklyPreset); const [dateRangeValue, setDateRangeValue] = useState( defaultDateRangeValue, ); @@ -91,12 +99,14 @@ export default function TemplateInsightsPage() { { - setInterval(interval); if (interval === "week") { setWeeklyPreset(defaultWeeklyPreset); } else { setDateRangeValue(defaultDateRangeValue); } + + searchParams.set("interval", interval); + setSearchParams(searchParams); }} /> {interval === "day" ? ( @@ -117,6 +127,13 @@ export default function TemplateInsightsPage() { ); } +const getDefaultInterval = (template: Template) => { + const now = new Date(); + const templateCreateDate = new Date(template.created_at); + const hasFiveWeeksOrMore = addWeeks(templateCreateDate, 5) < now; + return hasFiveWeeksOrMore ? "week" : "day"; +}; + export const TemplateInsightsPageView = ({ templateInsights, userLatency, From 6d33f90857f933851b7e2f0ca722bcb83add5fdc Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Tue, 3 Oct 2023 12:35:00 +0000 Subject: [PATCH 12/18] Move date range to search params --- .../TemplateInsightsPage.tsx | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx index fa086d0bc6030..80f4541c6b431 100644 --- a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx +++ b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx @@ -27,7 +27,7 @@ import { TemplateParameterValue, UserLatencyInsightsResponse, } from "api/typesGenerated"; -import { ComponentProps, ReactNode, useState } from "react"; +import { ComponentProps, ReactNode } from "react"; import { subDays, subWeeks, @@ -57,7 +57,7 @@ export default function TemplateInsightsPage() { const [searchParams, setSearchParams] = useSearchParams(); const defaultWeeklyPreset = 4; - const defaultDateRangeValue = { + const defaultDailyRange = { startDate: subDays(now, 6), endDate: now, }; @@ -74,12 +74,21 @@ export default function TemplateInsightsPage() { setSearchParams(searchParams); }; - const [dateRangeValue, setDateRangeValue] = useState( - defaultDateRangeValue, - ); + const dailyRange = + searchParams.has("startDate") && searchParams.has("endDate") + ? { + startDate: new Date(searchParams.get("startDate")!), + endDate: new Date(searchParams.get("endDate")!), + } + : defaultDailyRange; + const setDailyRange = (newDailyRange: DateRangeValue) => { + searchParams.set("startDate", newDailyRange.startDate.toISOString()); + searchParams.set("endDate", newDailyRange.endDate.toISOString()); + setSearchParams(searchParams); + }; const dateRange = - interval === "day" ? dateRangeValue : getWeeklyRange(weeklyPreset); + interval === "day" ? dailyRange : getWeeklyRange(weeklyPreset); const commonFilters = { template_ids: template.id, ...getDateRangeFilter(dateRange), @@ -102,7 +111,7 @@ export default function TemplateInsightsPage() { if (interval === "week") { setWeeklyPreset(defaultWeeklyPreset); } else { - setDateRangeValue(defaultDateRangeValue); + setDailyRange(defaultDailyRange); } searchParams.set("interval", interval); @@ -110,7 +119,7 @@ export default function TemplateInsightsPage() { }} /> {interval === "day" ? ( - + ) : ( Date: Tue, 3 Oct 2023 14:27:21 -0300 Subject: [PATCH 13/18] Update site/src/pages/TemplatePage/TemplateInsightsPage/IntervalMenu.tsx Co-authored-by: Kayla Washburn --- .../pages/TemplatePage/TemplateInsightsPage/IntervalMenu.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/site/src/pages/TemplatePage/TemplateInsightsPage/IntervalMenu.tsx b/site/src/pages/TemplatePage/TemplateInsightsPage/IntervalMenu.tsx index 80d413ebbb39a..797e1b4995f10 100644 --- a/site/src/pages/TemplatePage/TemplateInsightsPage/IntervalMenu.tsx +++ b/site/src/pages/TemplatePage/TemplateInsightsPage/IntervalMenu.tsx @@ -61,8 +61,7 @@ export const IntervalMenu = ({ horizontal: "left", }} > - {Object.keys(insightsIntervals).map((interval) => { - const { label } = insightsIntervals[interval as InsightsInterval]; + {Object.entries(insightsIntervals).map(([interval, { label }]) => { return ( Date: Tue, 3 Oct 2023 14:27:35 -0300 Subject: [PATCH 14/18] Update site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx Co-authored-by: Kayla Washburn --- .../TemplateInsightsPage/TemplateInsightsPage.tsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx index c28b6b0bd5aed..eb1da1d0107d0 100644 --- a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx +++ b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx @@ -215,12 +215,10 @@ const ActiveUsersPanel = ({ {data && data.length > 0 && ( { - return { - amount: d.active_users, - date: d.start_time, - }; - })} + data={data.map((d) => ({ + amount: d.active_users, + date: d.start_time, + }))} /> )} From 44f640bd2050bc8d66ff7dcf062734334dc4a0a2 Mon Sep 17 00:00:00 2001 From: Bruno Quaresma Date: Tue, 3 Oct 2023 14:28:12 -0300 Subject: [PATCH 15/18] Update site/src/pages/TemplatePage/TemplateInsightsPage/WeeklyPresetsMenu.tsx Co-authored-by: Kayla Washburn --- .../WeeklyPresetsMenu.tsx | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/site/src/pages/TemplatePage/TemplateInsightsPage/WeeklyPresetsMenu.tsx b/site/src/pages/TemplatePage/TemplateInsightsPage/WeeklyPresetsMenu.tsx index 33f56785d4db9..66492d67d0d90 100644 --- a/site/src/pages/TemplatePage/TemplateInsightsPage/WeeklyPresetsMenu.tsx +++ b/site/src/pages/TemplatePage/TemplateInsightsPage/WeeklyPresetsMenu.tsx @@ -54,25 +54,23 @@ export const WeeklyPresetsMenu = ({ horizontal: "left", }} > - {weeklyPresets.map((numberOfWeeks) => { - return ( - { - onChange(numberOfWeeks); - handleClose(); - }} - > - Last {numberOfWeeks} weeks - - {value === numberOfWeeks && ( - - )} - - - ); - })} + {weeklyPresets.map((numberOfWeeks) => ( + { + onChange(numberOfWeeks); + handleClose(); + }} + > + Last {numberOfWeeks} weeks + + {value === numberOfWeeks && ( + + )} + + + ))}
); From 579b093fb6cde0520931349304bc881179d62dbb Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Tue, 3 Oct 2023 17:29:41 +0000 Subject: [PATCH 16/18] Apply Kayla improvement --- site/src/components/ActiveUserChart/ActiveUserChart.tsx | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/site/src/components/ActiveUserChart/ActiveUserChart.tsx b/site/src/components/ActiveUserChart/ActiveUserChart.tsx index eafaf3eb3afd9..35b812c6cf478 100644 --- a/site/src/components/ActiveUserChart/ActiveUserChart.tsx +++ b/site/src/components/ActiveUserChart/ActiveUserChart.tsx @@ -48,13 +48,8 @@ export const ActiveUserChart: FC = ({ }) => { const theme: Theme = useTheme(); - const labels = data.map((val) => { - return dayjs(val.date).format("YYYY-MM-DD"); - }); - - const chartData = data.map((val) => { - return val.amount; - }); + const labels = data.map((val) => dayjs(val.date).format("YYYY-MM-DD")); + const chartData = data.map((val) => val.amount); defaults.font.family = theme.typography.fontFamily as string; defaults.color = theme.palette.text.secondary; From 0fe704e205d7c08c605185a1cb0f840b84de686a Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Tue, 3 Oct 2023 17:32:21 +0000 Subject: [PATCH 17/18] Improve destructuring --- .../TemplateInsightsPage/utils.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/site/src/pages/TemplatePage/TemplateInsightsPage/utils.ts b/site/src/pages/TemplatePage/TemplateInsightsPage/utils.ts index 868eb5c61c637..6f6cbc58353dd 100644 --- a/site/src/pages/TemplatePage/TemplateInsightsPage/utils.ts +++ b/site/src/pages/TemplatePage/TemplateInsightsPage/utils.ts @@ -7,17 +7,22 @@ import { isToday as isTodayDefault, } from "date-fns"; -export function getDateRangeFilter({ - startDate, - endDate, - now = new Date(), - isToday = isTodayDefault, -}: { +type GetDateRangeFilterOptions = { startDate: Date; endDate: Date; + // Testing purposes now?: Date; isToday?: (date: Date) => boolean; -}) { +}; + +export function getDateRangeFilter(props: GetDateRangeFilterOptions) { + const { + startDate, + endDate, + now = new Date(), + isToday = isTodayDefault, + } = props; + return { start_time: toISOLocal(startOfDay(startDate)), end_time: toISOLocal( From 932672a0d305f525bf22bdef7de7411151cc80c6 Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Tue, 3 Oct 2023 18:05:14 +0000 Subject: [PATCH 18/18] Centralize date range value --- .../TemplateInsightsPage.tsx | 93 ++++++++----------- .../{WeeklyPresetsMenu.tsx => WeekPicker.tsx} | 55 ++++++----- .../TemplateInsightsPage/utils.ts | 12 +++ 3 files changed, 81 insertions(+), 79 deletions(-) rename site/src/pages/TemplatePage/TemplateInsightsPage/{WeeklyPresetsMenu.tsx => WeekPicker.tsx} (53%) diff --git a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx index eb1da1d0107d0..25226c9bc89fd 100644 --- a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx +++ b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx @@ -29,67 +29,38 @@ import { UserLatencyInsightsResponse, } from "api/typesGenerated"; import { ComponentProps, ReactNode } from "react"; -import { - subDays, - subWeeks, - startOfWeek, - endOfWeek, - isSunday, - endOfDay, - addWeeks, -} from "date-fns"; +import { subDays, addWeeks } from "date-fns"; import "react-date-range/dist/styles.css"; import "react-date-range/dist/theme/default.css"; -import { DateRange, DateRangeValue } from "./DateRange"; +import { DateRange as DailyPicker, DateRangeValue } from "./DateRange"; import Link from "@mui/material/Link"; import CheckCircleOutlined from "@mui/icons-material/CheckCircleOutlined"; import CancelOutlined from "@mui/icons-material/CancelOutlined"; -import { getDateRangeFilter } from "./utils"; +import { getDateRangeFilter, lastWeeks } from "./utils"; import Tooltip from "@mui/material/Tooltip"; import LinkOutlined from "@mui/icons-material/LinkOutlined"; import { InsightsInterval, IntervalMenu } from "./IntervalMenu"; -import { WeeklyPreset, WeeklyPresetsMenu } from "./WeeklyPresetsMenu"; +import { WeekPicker, numberOfWeeksOptions } from "./WeekPicker"; import { insightsTemplate, insightsUserLatency } from "api/queries/insights"; import { useSearchParams } from "react-router-dom"; +const DEFAULT_NUMBER_OF_WEEKS = numberOfWeeksOptions[0]; + export default function TemplateInsightsPage() { const { template } = useTemplateLayoutContext(); - const now = new Date(); const [searchParams, setSearchParams] = useSearchParams(); - const defaultWeeklyPreset = 4; - const defaultDailyRange = { - startDate: subDays(now, 6), - endDate: now, - }; const defaultInterval = getDefaultInterval(template); - const interval = (searchParams.get("interval") as InsightsInterval) || defaultInterval; - const weeklyPreset = - (Number(searchParams.get("weeklyPreset")) as WeeklyPreset) || - defaultWeeklyPreset; - const setWeeklyPreset = (newWeeklyPreset: WeeklyPreset) => { - searchParams.set("weeklyPreset", newWeeklyPreset.toString()); + const dateRange = getDateRange(searchParams, interval); + const setDateRange = (newDateRange: DateRangeValue) => { + searchParams.set("startDate", newDateRange.startDate.toISOString()); + searchParams.set("endDate", newDateRange.endDate.toISOString()); setSearchParams(searchParams); }; - const dailyRange = - searchParams.has("startDate") && searchParams.has("endDate") - ? { - startDate: new Date(searchParams.get("startDate")!), - endDate: new Date(searchParams.get("endDate")!), - } - : defaultDailyRange; - const setDailyRange = (newDailyRange: DateRangeValue) => { - searchParams.set("startDate", newDailyRange.startDate.toISOString()); - searchParams.set("endDate", newDailyRange.endDate.toISOString()); - setSearchParams(searchParams); - }; - - const dateRange = - interval === "day" ? dailyRange : getWeeklyRange(weeklyPreset); const commonFilters = { template_ids: template.id, ...getDateRangeFilter(dateRange), @@ -109,23 +80,18 @@ export default function TemplateInsightsPage() { { + // When going from daily to week we need to set a safe week range if (interval === "week") { - setWeeklyPreset(defaultWeeklyPreset); - } else { - setDailyRange(defaultDailyRange); + setDateRange(lastWeeks(DEFAULT_NUMBER_OF_WEEKS)); } - searchParams.set("interval", interval); setSearchParams(searchParams); }} /> {interval === "day" ? ( - + ) : ( - + )} } @@ -144,6 +110,30 @@ const getDefaultInterval = (template: Template) => { return hasFiveWeeksOrMore ? "week" : "day"; }; +const getDateRange = ( + searchParams: URLSearchParams, + interval: InsightsInterval, +) => { + const startDate = searchParams.get("startDate"); + const endDate = searchParams.get("endDate"); + + if (startDate && endDate) { + return { + startDate: new Date(startDate), + endDate: new Date(endDate), + }; + } + + if (interval === "day") { + return { + startDate: subDays(new Date(), 6), + endDate: new Date(), + }; + } + + return lastWeeks(DEFAULT_NUMBER_OF_WEEKS); +}; + export const TemplateInsightsPageView = ({ templateInsights, userLatency, @@ -671,13 +661,6 @@ const TextValue = ({ children }: { children: ReactNode }) => { ); }; -const getWeeklyRange = (numberOfWeeks: WeeklyPreset) => { - const now = new Date(); - const startDate = startOfWeek(subWeeks(now, numberOfWeeks)); - const endDate = isSunday(now) ? endOfDay(now) : endOfWeek(subWeeks(now, 1)); - return { startDate, endDate }; -}; - function formatTime(seconds: number): string { if (seconds < 60) { return seconds + " seconds"; diff --git a/site/src/pages/TemplatePage/TemplateInsightsPage/WeeklyPresetsMenu.tsx b/site/src/pages/TemplatePage/TemplateInsightsPage/WeekPicker.tsx similarity index 53% rename from site/src/pages/TemplatePage/TemplateInsightsPage/WeeklyPresetsMenu.tsx rename to site/src/pages/TemplatePage/TemplateInsightsPage/WeekPicker.tsx index 66492d67d0d90..2ef0dae1f8cb1 100644 --- a/site/src/pages/TemplatePage/TemplateInsightsPage/WeeklyPresetsMenu.tsx +++ b/site/src/pages/TemplatePage/TemplateInsightsPage/WeekPicker.tsx @@ -5,20 +5,23 @@ import Button from "@mui/material/Button"; import Menu from "@mui/material/Menu"; import MenuItem from "@mui/material/MenuItem"; import { useState, useRef } from "react"; +import { DateRangeValue } from "./DateRange"; +import { differenceInWeeks } from "date-fns"; +import { lastWeeks } from "./utils"; -export const weeklyPresets = [4, 12, 24, 48] as const; +export const numberOfWeeksOptions = [4, 12, 24, 48] as const; -export type WeeklyPreset = (typeof weeklyPresets)[number]; - -export const WeeklyPresetsMenu = ({ +export const WeekPicker = ({ value, onChange, }: { - value: WeeklyPreset; - onChange: (value: WeeklyPreset) => void; + value: DateRangeValue; + onChange: (value: DateRangeValue) => void; }) => { const anchorRef = useRef(null); const [open, setOpen] = useState(false); + // Why +1? If you get the week 1 and week 2 the diff is 1, but there are 2 weeks + const numberOfWeeks = differenceInWeeks(value.endDate, value.startDate) + 1; const handleClose = () => { setOpen(false); @@ -35,7 +38,7 @@ export const WeeklyPresetsMenu = ({ onClick={() => setOpen(true)} endIcon={} > - Last {value} weeks + Last {numberOfWeeks} weeks - {weeklyPresets.map((numberOfWeeks) => ( - { - onChange(numberOfWeeks); - handleClose(); - }} - > - Last {numberOfWeeks} weeks - - {value === numberOfWeeks && ( - - )} - - - ))} + {numberOfWeeksOptions.map((option) => { + const optionRange = lastWeeks(option); + + return ( + { + onChange(optionRange); + handleClose(); + }} + > + Last {option} weeks + + {numberOfWeeks === option && ( + + )} + + + ); + })} ); diff --git a/site/src/pages/TemplatePage/TemplateInsightsPage/utils.ts b/site/src/pages/TemplatePage/TemplateInsightsPage/utils.ts index 6f6cbc58353dd..f447bff1a331e 100644 --- a/site/src/pages/TemplatePage/TemplateInsightsPage/utils.ts +++ b/site/src/pages/TemplatePage/TemplateInsightsPage/utils.ts @@ -5,6 +5,11 @@ import { startOfDay, startOfHour, isToday as isTodayDefault, + startOfWeek, + endOfDay, + endOfWeek, + isSunday, + subWeeks, } from "date-fns"; type GetDateRangeFilterOptions = { @@ -36,3 +41,10 @@ export function getDateRangeFilter(props: GetDateRangeFilterOptions) { function toISOLocal(d: Date) { return format(d, "yyyy-MM-dd'T'HH:mm:ssxxx"); } + +export const lastWeeks = (numberOfWeeks: number) => { + const now = new Date(); + const startDate = startOfWeek(subWeeks(now, numberOfWeeks)); + const endDate = isSunday(now) ? endOfDay(now) : endOfWeek(subWeeks(now, 1)); + return { startDate, endDate }; +};