From fba94922b1244351cfb38797622cf5a4e756a037 Mon Sep 17 00:00:00 2001 From: Andrew Aquino Date: Thu, 28 Aug 2025 18:08:41 +0000 Subject: [PATCH 01/54] refactor: replace Popover with Tooltip in AgentLatency --- .../components/HelpTooltip/HelpTooltip.tsx | 49 ++++++----- site/src/modules/resources/AgentLatency.tsx | 82 ++++++++++--------- 2 files changed, 66 insertions(+), 65 deletions(-) diff --git a/site/src/components/HelpTooltip/HelpTooltip.tsx b/site/src/components/HelpTooltip/HelpTooltip.tsx index 2bcaef1eb6847..b05b9934903ed 100644 --- a/site/src/components/HelpTooltip/HelpTooltip.tsx +++ b/site/src/components/HelpTooltip/HelpTooltip.tsx @@ -3,18 +3,16 @@ import { css, type Interpolation, type Theme, - useTheme, } from "@emotion/react"; import Link from "@mui/material/Link"; -import { - Popover, - PopoverContent, - type PopoverContentProps, - type PopoverProps, - PopoverTrigger, - usePopover, -} from "components/deprecated/Popover/Popover"; +import { TooltipContentProps, TooltipProps } from "@radix-ui/react-tooltip"; +import { usePopover } from "components/deprecated/Popover/Popover"; import { Stack } from "components/Stack/Stack"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "components/Tooltip/Tooltip"; import { CircleHelpIcon, ExternalLinkIcon } from "lucide-react"; import { type FC, @@ -23,6 +21,7 @@ import { type PropsWithChildren, type ReactNode, } from "react"; +import { cn } from "utils/cn"; type Icon = typeof CircleHelpIcon; @@ -30,24 +29,23 @@ type Size = "small" | "medium"; export const HelpTooltipIcon = CircleHelpIcon; -export const HelpTooltip: FC = (props) => { - return ; +export const HelpTooltip: FC = (props) => { + return ; }; -export const HelpTooltipContent: FC = (props) => { - const theme = useTheme(); - +export const HelpTooltipContent: FC = ({ + className, + ...props +}) => { return ( - ); }; @@ -76,7 +74,7 @@ export const HelpTooltipTrigger = forwardRef< }); return ( - + - + ); }); @@ -155,6 +153,7 @@ export const HelpTooltipAction: FC = ({ onClick, ariaLabel, }) => { + // TODO dismiss tooltip const popover = usePopover(); return ( diff --git a/site/src/modules/resources/AgentLatency.tsx b/site/src/modules/resources/AgentLatency.tsx index 4be2a4cf52a07..0f5f436b62341 100644 --- a/site/src/modules/resources/AgentLatency.tsx +++ b/site/src/modules/resources/AgentLatency.tsx @@ -1,6 +1,5 @@ import { type Theme, useTheme } from "@emotion/react"; import type { DERPRegion, WorkspaceAgent } from "api/typesGenerated"; -import { PopoverTrigger } from "components/deprecated/Popover/Popover"; import { HelpTooltip, HelpTooltipContent, @@ -8,6 +7,7 @@ import { HelpTooltipTitle, } from "components/HelpTooltip/HelpTooltip"; import { Stack } from "components/Stack/Stack"; +import { TooltipProvider, TooltipTrigger } from "components/Tooltip/Tooltip"; import type { FC } from "react"; import { getLatencyColor } from "utils/latency"; @@ -43,45 +43,47 @@ export const AgentLatency: FC = ({ agent }) => { } return ( - - - - {Math.round(latency.latency_ms)}ms - - - - Latency - - This is the latency overhead on non peer to peer connections. The - first row is the preferred relay. - - - - {Object.entries(agent.latency) - .sort(([, a], [, b]) => a.latency_ms - b.latency_ms) - .map(([regionName, region]) => ( - + + + + {Math.round(latency.latency_ms)}ms + + + + Latency + + This is the latency overhead on non peer to peer connections. The + first row is the preferred relay. + + + + {Object.entries(agent.latency) + .sort(([, a], [, b]) => a.latency_ms - b.latency_ms) + .map(([regionName, region]) => ( + - {regionName} - {Math.round(region.latency_ms)}ms - - ))} - - - - + > + {regionName} + {Math.round(region.latency_ms)}ms + + ))} + + + + + ); }; From e230f38027cf201b67ab530fa62974df6296d7a0 Mon Sep 17 00:00:00 2001 From: Andrew Aquino Date: Thu, 28 Aug 2025 18:23:48 +0000 Subject: [PATCH 02/54] refactor: replace PopoverTrigger with TooltipTrigger in AgentStatus --- site/src/modules/resources/AgentStatus.tsx | 56 +++++++++++----------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/site/src/modules/resources/AgentStatus.tsx b/site/src/modules/resources/AgentStatus.tsx index 8f6b923e70d68..543c31313d043 100644 --- a/site/src/modules/resources/AgentStatus.tsx +++ b/site/src/modules/resources/AgentStatus.tsx @@ -6,13 +6,13 @@ import type { WorkspaceAgentDevcontainer, } from "api/typesGenerated"; import { ChooseOne, Cond } from "components/Conditionals/ChooseOne"; -import { PopoverTrigger } from "components/deprecated/Popover/Popover"; import { HelpTooltip, HelpTooltipContent, HelpTooltipText, HelpTooltipTitle, } from "components/HelpTooltip/HelpTooltip"; +import { TooltipProvider, TooltipTrigger } from "components/Tooltip/Tooltip"; import { TriangleAlertIcon } from "lucide-react"; import type { FC } from "react"; @@ -62,9 +62,9 @@ interface DevcontainerStatusProps { const StartTimeoutLifecycle: FC = ({ agent }) => { return ( - + - + Agent is taking too long to start @@ -87,9 +87,9 @@ const StartTimeoutLifecycle: FC = ({ agent }) => { const StartErrorLifecycle: FC = ({ agent }) => { return ( - + - + Error starting the agent @@ -123,9 +123,9 @@ const ShuttingDownLifecycle: FC = () => { const ShutdownTimeoutLifecycle: FC = ({ agent }) => { return ( - + - + Agent is taking too long to stop @@ -147,9 +147,9 @@ const ShutdownTimeoutLifecycle: FC = ({ agent }) => { const ShutdownErrorLifecycle: FC = ({ agent }) => { return ( - + - + Error stopping the agent @@ -243,9 +243,9 @@ const ConnectingStatus: FC = () => { const TimeoutStatus: FC = ({ agent }) => { return ( - + - + Agent is taking too long to connect @@ -266,20 +266,22 @@ const TimeoutStatus: FC = ({ agent }) => { export const AgentStatus: FC = ({ agent }) => { return ( - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + ); }; @@ -308,9 +310,9 @@ const SubAgentStatus: FC = ({ agent }) => { const DevcontainerStartError: FC = ({ agent }) => { return ( - + - + Error starting the devcontainer agent From 75c9fdfc4cd0457e80368f3b6ebdcd45779bba2c Mon Sep 17 00:00:00 2001 From: Andrew Aquino Date: Thu, 28 Aug 2025 18:47:33 +0000 Subject: [PATCH 03/54] refactor: replace PopoverTrigger with TooltipTrigger in AgentOutdatedTooltip --- .../components/HelpTooltip/HelpTooltip.tsx | 12 +-- .../resources/AgentOutdatedTooltip.tsx | 83 ++++++++++--------- 2 files changed, 48 insertions(+), 47 deletions(-) diff --git a/site/src/components/HelpTooltip/HelpTooltip.tsx b/site/src/components/HelpTooltip/HelpTooltip.tsx index b05b9934903ed..8c788661bff62 100644 --- a/site/src/components/HelpTooltip/HelpTooltip.tsx +++ b/site/src/components/HelpTooltip/HelpTooltip.tsx @@ -6,7 +6,6 @@ import { } from "@emotion/react"; import Link from "@mui/material/Link"; import { TooltipContentProps, TooltipProps } from "@radix-ui/react-tooltip"; -import { usePopover } from "components/deprecated/Popover/Popover"; import { Stack } from "components/Stack/Stack"; import { Tooltip, @@ -43,7 +42,7 @@ export const HelpTooltipContent: FC = ({ align="start" {...props} className={cn( - "w-[320px] p-5 bg-surface-secondary border-surface-quaternary", + "w-[320px] p-5 bg-surface-secondary border-surface-quaternary text-sm", className, )} /> @@ -153,19 +152,12 @@ export const HelpTooltipAction: FC = ({ onClick, ariaLabel, }) => { - // TODO dismiss tooltip - const popover = usePopover(); - return ( - + }> { From 9887b0946fb9451a1425dc052de3dc8ccbbd373c Mon Sep 17 00:00:00 2001 From: Andrew Aquino Date: Tue, 2 Sep 2025 23:10:36 +0000 Subject: [PATCH 25/54] refactor: move default collisionPadding from HelpTooltip to Popover --- site/src/components/HelpTooltip/HelpTooltip.tsx | 1 - site/src/components/Popover/Popover.tsx | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/components/HelpTooltip/HelpTooltip.tsx b/site/src/components/HelpTooltip/HelpTooltip.tsx index 4c41a56b0e331..93abe7c056b56 100644 --- a/site/src/components/HelpTooltip/HelpTooltip.tsx +++ b/site/src/components/HelpTooltip/HelpTooltip.tsx @@ -46,7 +46,6 @@ export const HelpTooltipContent: FC = ({ Date: Wed, 3 Sep 2025 00:08:24 +0000 Subject: [PATCH 26/54] refactor: replace deprecated Popover in SelectMenu --- site/src/components/SelectMenu/SelectMenu.tsx | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/site/src/components/SelectMenu/SelectMenu.tsx b/site/src/components/SelectMenu/SelectMenu.tsx index ece4128769705..5ecba4cb7d286 100644 --- a/site/src/components/SelectMenu/SelectMenu.tsx +++ b/site/src/components/SelectMenu/SelectMenu.tsx @@ -1,11 +1,15 @@ import MenuItem, { type MenuItemProps } from "@mui/material/MenuItem"; import MenuList, { type MenuListProps } from "@mui/material/MenuList"; +import type { + PopoverContentProps, + PopoverTriggerProps, +} from "@radix-ui/react-popover"; import { Button, type ButtonProps } from "components/Button/Button"; import { Popover, PopoverContent, PopoverTrigger, -} from "components/deprecated/Popover/Popover"; +} from "components/Popover/Popover"; import { SearchField, type SearchFieldProps, @@ -26,9 +30,21 @@ const SIDE_PADDING = 16; export const SelectMenu = Popover; -export const SelectMenuTrigger = PopoverTrigger; +export const SelectMenuTrigger: FC = (props) => { + return ; +}; -export const SelectMenuContent = PopoverContent; +export const SelectMenuContent: FC = (props) => { + return ( + + ); +}; type SelectMenuButtonProps = ButtonProps & { startIcon?: React.ReactNode; From bda70b2faf6920a07a6e2a1921d1517f81b3088d Mon Sep 17 00:00:00 2001 From: Andrew Aquino Date: Wed, 3 Sep 2025 00:10:32 +0000 Subject: [PATCH 27/54] style: prettier --- .../dashboard/Navbar/DeploymentDropdown.tsx | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/site/src/modules/dashboard/Navbar/DeploymentDropdown.tsx b/site/src/modules/dashboard/Navbar/DeploymentDropdown.tsx index b78e3a6954a58..6de9435c3fd03 100644 --- a/site/src/modules/dashboard/Navbar/DeploymentDropdown.tsx +++ b/site/src/modules/dashboard/Navbar/DeploymentDropdown.tsx @@ -139,17 +139,17 @@ const DeploymentDropdownContent: FC = ({ const styles = { menuItem: (theme) => css` - text-decoration: none; - color: inherit; - gap: 8px; - padding: 8px 20px; - font-size: 14px; + text-decoration: none; + color: inherit; + gap: 8px; + padding: 8px 20px; + font-size: 14px; - &:hover { - background-color: ${theme.palette.action.hover}; - transition: background-color 0.3s ease; - } - `, + &:hover { + background-color: ${theme.palette.action.hover}; + transition: background-color 0.3s ease; + } + `, menuItemIcon: (theme) => ({ color: theme.palette.text.secondary, width: 20, From 6a37c4948ba941ec13d63d8755958ed1bce58bb7 Mon Sep 17 00:00:00 2001 From: Andrew Aquino Date: Wed, 3 Sep 2025 00:27:08 +0000 Subject: [PATCH 28/54] refactor: replace deprecated Popover in DeploymentDropdown --- .../dashboard/Navbar/DeploymentDropdown.tsx | 101 ++++++++---------- 1 file changed, 43 insertions(+), 58 deletions(-) diff --git a/site/src/modules/dashboard/Navbar/DeploymentDropdown.tsx b/site/src/modules/dashboard/Navbar/DeploymentDropdown.tsx index 6de9435c3fd03..b38201f3a8c3d 100644 --- a/site/src/modules/dashboard/Navbar/DeploymentDropdown.tsx +++ b/site/src/modules/dashboard/Navbar/DeploymentDropdown.tsx @@ -1,12 +1,12 @@ -import { css, type Interpolation, type Theme, useTheme } from "@emotion/react"; +import { css, type Interpolation, type Theme } from "@emotion/react"; import MenuItem from "@mui/material/MenuItem"; +import { PopoverClose } from "@radix-ui/react-popover"; import { Button } from "components/Button/Button"; import { Popover, PopoverContent, PopoverTrigger, - usePopover, -} from "components/deprecated/Popover/Popover"; +} from "components/Popover/Popover"; import { ChevronDownIcon } from "lucide-react"; import { linkToAuditing } from "modules/navigation"; import type { FC } from "react"; @@ -27,8 +27,6 @@ export const DeploymentDropdown: FC = ({ canViewConnectionLog, canViewHealth, }) => { - const theme = useTheme(); - if ( !canViewAuditLog && !canViewConnectionLog && @@ -41,7 +39,7 @@ export const DeploymentDropdown: FC = ({ return ( - + - + = ({ ); }; -const classNames = { - paper: (css, theme) => css` - padding: 0; - width: 404px; - color: ${theme.palette.text.secondary}; - margin-top: 4px; - `, -} satisfies Record; - const styles = { portCount: (theme) => ({ fontSize: 12, From a6cf0dab9945fd8f2ea2003f4e592602414b06c7 Mon Sep 17 00:00:00 2001 From: Andrew Aquino Date: Wed, 3 Sep 2025 00:55:18 +0000 Subject: [PATCH 34/54] refactor: replace deprecated Popover in EditRolesButton --- .../UserTable/EditRolesButton.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/site/src/pages/OrganizationSettingsPage/UserTable/EditRolesButton.tsx b/site/src/pages/OrganizationSettingsPage/UserTable/EditRolesButton.tsx index 4983e671aa5a6..757729f25ab38 100644 --- a/site/src/pages/OrganizationSettingsPage/UserTable/EditRolesButton.tsx +++ b/site/src/pages/OrganizationSettingsPage/UserTable/EditRolesButton.tsx @@ -7,7 +7,7 @@ import { Popover, PopoverContent, PopoverTrigger, -} from "components/deprecated/Popover/Popover"; +} from "components/Popover/Popover"; import { HelpTooltip, HelpTooltipContent, @@ -130,7 +130,7 @@ const EnabledEditRolesButton: FC = ({ return ( - + Date: Wed, 3 Sep 2025 21:44:44 +0000 Subject: [PATCH 37/54] refactor: replace deprecated Popover in BuildParametersPopover --- .../BuildParametersPopover.tsx | 46 ++++++------- .../WorkspaceActions/RetryButton.stories.tsx | 66 ++++++++++++++++++- site/src/testHelpers/entities.ts | 21 ++++++ 3 files changed, 106 insertions(+), 27 deletions(-) diff --git a/site/src/pages/WorkspacePage/WorkspaceActions/BuildParametersPopover.tsx b/site/src/pages/WorkspacePage/WorkspaceActions/BuildParametersPopover.tsx index 7aef1dc7c7357..d4e5dc7773fcd 100644 --- a/site/src/pages/WorkspacePage/WorkspaceActions/BuildParametersPopover.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceActions/BuildParametersPopover.tsx @@ -7,12 +7,6 @@ import type { WorkspaceBuildParameter, } from "api/typesGenerated"; import { Button } from "components/Button/Button"; -import { - Popover, - PopoverContent, - PopoverTrigger, - usePopover, -} from "components/deprecated/Popover/Popover"; import { FormFields } from "components/Form/Form"; import { TopbarButton } from "components/FullPageLayout/Topbar"; import { @@ -21,13 +15,18 @@ import { HelpTooltipText, HelpTooltipTitle, } from "components/HelpTooltip/HelpTooltip"; +import { Link } from "components/Link/Link"; import { Loader } from "components/Loader/Loader"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "components/Popover/Popover"; import { RichParameterInput } from "components/RichParameterInput/RichParameterInput"; import { useFormik } from "formik"; import { ChevronDownIcon } from "lucide-react"; -import type { FC } from "react"; +import { useState, type FC } from "react"; import { useQuery } from "react-query"; -import { useNavigate } from "react-router"; import { docs } from "utils/docs"; import { getFormHelpers } from "utils/formUtils"; import { @@ -48,6 +47,7 @@ export const BuildParametersPopover: FC = ({ label, onSubmit, }) => { + const [isOpen, setIsOpen] = useState(false); const { data: parameters } = useQuery({ queryKey: ["workspace", workspace.id, "parameters"], queryFn: () => API.getWorkspaceParameters(workspace), @@ -57,8 +57,8 @@ export const BuildParametersPopover: FC = ({ : undefined; return ( - - + + = ({ {label} - + @@ -88,6 +86,7 @@ interface BuildParametersPopoverContentProps { ephemeralParameters?: TemplateVersionParameter[]; buildParameters?: WorkspaceBuildParameter[]; onSubmit: (buildParameters: WorkspaceBuildParameter[]) => void; + setIsOpen: React.Dispatch>; } const BuildParametersPopoverContent: FC = ({ @@ -95,23 +94,15 @@ const BuildParametersPopoverContent: FC = ({ ephemeralParameters, buildParameters, onSubmit, + setIsOpen, }) => { const theme = useTheme(); - const popover = usePopover(); - const navigate = useNavigate(); if ( !workspace.template_use_classic_parameter_flow && ephemeralParameters && ephemeralParameters.length > 0 ) { - const handleGoToParameters = () => { - popover.setOpen(false); - navigate( - `/@${workspace.owner_name}/${workspace.name}/settings/parameters`, - ); - }; - return (

@@ -137,9 +128,12 @@ const BuildParametersPopoverContent: FC = ({

- + ); } @@ -165,7 +159,7 @@ const BuildParametersPopoverContent: FC = ({
{ onSubmit(buildParameters); - popover.setOpen(false); + setIsOpen(false); }} ephemeralParameters={ephemeralParameters} buildParameters={buildParameters.map( diff --git a/site/src/pages/WorkspacePage/WorkspaceActions/RetryButton.stories.tsx b/site/src/pages/WorkspacePage/WorkspaceActions/RetryButton.stories.tsx index 12ff75dc64616..b99b8cba4b3fc 100644 --- a/site/src/pages/WorkspacePage/WorkspaceActions/RetryButton.stories.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceActions/RetryButton.stories.tsx @@ -1,4 +1,8 @@ -import { MockWorkspace } from "testHelpers/entities"; +import { + MockNonClassicParameterFlowWorkspace, + MockTemplateVersionParameter6, + MockWorkspace, +} from "testHelpers/entities"; import type { Meta, StoryObj } from "@storybook/react-vite"; import { expect, userEvent, waitFor, within } from "storybook/test"; import { RetryButton } from "./RetryButton"; @@ -52,3 +56,63 @@ export const WithOpenBuildParameters: Story = { }); }, }; + +export const WithOpenEphemeralBuildParameters: Story = { + args: { + enableBuildParameters: true, + workspace: MockWorkspace, + }, + parameters: { + queries: [ + { + key: ["workspace", MockWorkspace.id, "parameters"], + data: { + templateVersionRichParameters: [MockTemplateVersionParameter6], + buildParameters: [], + }, + }, + ], + }, + play: async ({ canvasElement, step }) => { + const screen = within(canvasElement); + + await step("open popover", async () => { + await userEvent.click(screen.getByTestId("build-parameters-button")); + await waitFor(() => + expect(screen.getByText("Build Options")).toBeInTheDocument(), + ); + }); + }, +}; + +export const WithOpenEphemeralBuildParametersNotClassic: Story = { + args: { + enableBuildParameters: true, + workspace: MockNonClassicParameterFlowWorkspace, + }, + parameters: { + queries: [ + { + key: [ + "workspace", + MockNonClassicParameterFlowWorkspace.id, + "parameters", + ], + data: { + templateVersionRichParameters: [MockTemplateVersionParameter6], + buildParameters: [], + }, + }, + ], + }, + play: async ({ canvasElement, step }) => { + const screen = within(canvasElement); + + await step("open popover", async () => { + await userEvent.click(screen.getByTestId("build-parameters-button")); + await waitFor(() => + expect(screen.getByText("Build Options")).toBeInTheDocument(), + ); + }); + }, +}; diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index fb7ab29659835..4a803fef8a917 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -1598,6 +1598,12 @@ export const MockPendingWorkspace: TypesGen.Workspace = { }, }; +export const MockNonClassicParameterFlowWorkspace: TypesGen.Workspace = { + ...MockWorkspace, + id: "test-non-classic-parameter-flow-workspace", + template_use_classic_parameter_flow: false, +}; + // just over one page of workspaces export const MockWorkspacesResponse: TypesGen.WorkspacesResponse = { workspaces: range(1, 27).map((id: number) => ({ @@ -1695,6 +1701,21 @@ const MockTemplateVersionParameter5: TypesGen.TemplateVersionParameter = { ephemeral: false, }; +export const MockTemplateVersionParameter6: TypesGen.TemplateVersionParameter = + { + name: "ephemeral_parameter", + type: "string", + form_type: "input", + description: "This is ephemeral parameter", + description_plaintext: "Markdown: This is ephemeral parameter", + default_value: "abc", + mutable: true, + icon: "/icon/folder.svg", + options: [], + required: true, + ephemeral: true, + }; + export const MockTemplateVersionVariable1: TypesGen.TemplateVersionVariable = { name: "first_variable", description: "This is first variable.", From 244792ab446a30076da4ae9a45564e3fdfcf8592 Mon Sep 17 00:00:00 2001 From: Andrew Aquino Date: Thu, 4 Sep 2025 00:55:10 +0000 Subject: [PATCH 38/54] refactor: extract TemplateInsightsControls component --- .../TemplateInsightsControls.stories.tsx | 32 ++++++++++ .../TemplateInsightsPage.tsx | 64 +++++++++++++------ 2 files changed, 77 insertions(+), 19 deletions(-) create mode 100644 site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsControls.stories.tsx diff --git a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsControls.stories.tsx b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsControls.stories.tsx new file mode 100644 index 0000000000000..4eb311a8d9c47 --- /dev/null +++ b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsControls.stories.tsx @@ -0,0 +1,32 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import type { ComponentProps } from "react"; +import { TemplateInsightsControls } from "./TemplateInsightsPage"; + +const meta: Meta = { + title: "pages/TemplatePage/TemplateInsightsControls", + component: TemplateInsightsControls, +}; + +export default meta; +type Story = StoryObj; + +const defaultArgs: Partial> = { + dateRange: { startDate: new Date(), endDate: new Date() }, + setDateRange: () => {}, + searchParams: new URLSearchParams(), + setSearchParams: () => {}, +}; + +export const Day: Story = { + args: { + ...defaultArgs, + interval: "day", + }, +}; + +export const Week: Story = { + args: { + ...defaultArgs, + interval: "week", + }, +}; diff --git a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx index 0c12d96625156..3a74da6b4fb07 100644 --- a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx +++ b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx @@ -47,7 +47,7 @@ import { } from "react"; import { Helmet } from "react-helmet-async"; import { useQuery } from "react-query"; -import { useSearchParams } from "react-router"; +import { type SetURLSearchParams, useSearchParams } from "react-router"; import { getLatencyColor } from "utils/latency"; import { addTime, @@ -105,24 +105,13 @@ export default function TemplateInsightsPage() { - { - // When going from daily to week we need to set a safe week range - if (interval === "week") { - setDateRange(lastWeeks(DEFAULT_NUMBER_OF_WEEKS)); - } - searchParams.set("interval", interval); - setSearchParams(searchParams); - }} - /> - {interval === "day" ? ( - - ) : ( - - )} - + } templateInsights={templateInsights} userLatency={userLatency} @@ -134,6 +123,43 @@ export default function TemplateInsightsPage() { ); } +interface TemplateInsightsControlsProps { + interval: "day" | "week"; + dateRange: DateRangeValue; + setDateRange: (value: DateRangeValue) => void; + searchParams: URLSearchParams; + setSearchParams: SetURLSearchParams; +} + +export const TemplateInsightsControls: FC = ({ + interval, + dateRange, + setDateRange, + searchParams, + setSearchParams, +}) => { + return ( + <> + { + // When going from daily to week we need to set a safe week range + if (interval === "week") { + setDateRange(lastWeeks(DEFAULT_NUMBER_OF_WEEKS)); + } + searchParams.set("interval", interval); + setSearchParams(searchParams); + }} + /> + {interval === "day" ? ( + + ) : ( + + )} + + ); +}; + const getDefaultInterval = (template: Template) => { const now = new Date(); const templateCreateDate = new Date(template.created_at); From 07fd1e2c1a4cdd4da41385111702dcca716bd1e2 Mon Sep 17 00:00:00 2001 From: Andrew Aquino Date: Thu, 4 Sep 2025 01:08:47 +0000 Subject: [PATCH 39/54] test: use set past dateRange --- .../TemplateInsightsControls.stories.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsControls.stories.tsx b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsControls.stories.tsx index 4eb311a8d9c47..2baf6c6c9b985 100644 --- a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsControls.stories.tsx +++ b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsControls.stories.tsx @@ -11,7 +11,10 @@ export default meta; type Story = StoryObj; const defaultArgs: Partial> = { - dateRange: { startDate: new Date(), endDate: new Date() }, + dateRange: { + startDate: new Date("2025-08-05"), + endDate: new Date("2025-08-07"), + }, setDateRange: () => {}, searchParams: new URLSearchParams(), setSearchParams: () => {}, From 8bd2cd03d24c4c9d5ee901b0ad589d49772aa711 Mon Sep 17 00:00:00 2001 From: Andrew Aquino Date: Thu, 4 Sep 2025 01:14:39 +0000 Subject: [PATCH 40/54] test: add play functions to TemplateInsightsControls.stories --- .../TemplateInsightsControls.stories.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsControls.stories.tsx b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsControls.stories.tsx index 2baf6c6c9b985..1e1e0e8198f6e 100644 --- a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsControls.stories.tsx +++ b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsControls.stories.tsx @@ -1,5 +1,7 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; +import { within } from "@testing-library/react"; import type { ComponentProps } from "react"; +import { userEvent } from "storybook/test"; import { TemplateInsightsControls } from "./TemplateInsightsPage"; const meta: Meta = { @@ -25,6 +27,11 @@ export const Day: Story = { ...defaultArgs, interval: "day", }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const datePicker = canvas.getAllByRole("button")[1]; + await userEvent.click(datePicker); + }, }; export const Week: Story = { @@ -32,4 +39,9 @@ export const Week: Story = { ...defaultArgs, interval: "week", }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const dropdown = canvas.getAllByRole("button")[1]; + await userEvent.click(dropdown); + }, }; From bdacfb1767ccb56679a8a44478ba4a7bb0fe215f Mon Sep 17 00:00:00 2001 From: Andrew Aquino Date: Thu, 4 Sep 2025 22:08:18 +0000 Subject: [PATCH 41/54] refactor: replace deprecated Popover in DateRange --- .../pages/TemplatePage/TemplateInsightsPage/DateRange.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/site/src/pages/TemplatePage/TemplateInsightsPage/DateRange.tsx b/site/src/pages/TemplatePage/TemplateInsightsPage/DateRange.tsx index 3d9fb8120efbf..6c53a7e9b4b37 100644 --- a/site/src/pages/TemplatePage/TemplateInsightsPage/DateRange.tsx +++ b/site/src/pages/TemplatePage/TemplateInsightsPage/DateRange.tsx @@ -6,7 +6,7 @@ import { Popover, PopoverContent, PopoverTrigger, -} from "components/deprecated/Popover/Popover"; +} from "components/Popover/Popover"; import dayjs from "dayjs"; import { MoveRightIcon } from "lucide-react"; import { type ComponentProps, type FC, useRef, useState } from "react"; @@ -45,7 +45,7 @@ export const DateRange: FC = ({ value, onChange }) => { return ( - + - - - - - + + + + - + + + {userGroups.map((group) => { + const groupName = group.display_name || group.name; + return ( + - {groupName || N/A} - - - ); - })} - - - - + + + + {groupName || N/A} + + + ); + })} + + + + + )} ); From b2370afd128688da4dd52fa58fb6be96a80ea5f6 Mon Sep 17 00:00:00 2001 From: Andrew Aquino Date: Wed, 10 Sep 2025 00:59:52 +0000 Subject: [PATCH 52/54] refactor: replace deprecated Popover in Notifications --- .../WorkspaceNotifications/Notifications.tsx | 75 ++++++++++--------- 1 file changed, 41 insertions(+), 34 deletions(-) diff --git a/site/src/pages/WorkspacePage/WorkspaceNotifications/Notifications.tsx b/site/src/pages/WorkspacePage/WorkspaceNotifications/Notifications.tsx index bc72396932e77..150cb2d00b5ec 100644 --- a/site/src/pages/WorkspacePage/WorkspaceNotifications/Notifications.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceNotifications/Notifications.tsx @@ -1,14 +1,14 @@ -import { type Interpolation, type Theme, useTheme } from "@emotion/react"; +import { useTheme, type Interpolation, type Theme } from "@emotion/react"; import type { AlertProps } from "components/Alert/Alert"; import { Button, type ButtonProps } from "components/Button/Button"; -import { - Popover, - PopoverContent, - PopoverTrigger, - usePopover, -} from "components/deprecated/Popover/Popover"; import { Pill } from "components/Pill/Pill"; -import type { FC, ReactNode } from "react"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "components/Tooltip/Tooltip"; +import { useState, type FC, type ReactNode } from "react"; import type { ThemeRole } from "theme/roles"; export type NotificationItem = { @@ -22,6 +22,7 @@ type NotificationsProps = { items: NotificationItem[]; severity: ThemeRole; icon: ReactNode; + isTooltipOpen: boolean; }; export const Notifications: FC = ({ @@ -29,32 +30,39 @@ export const Notifications: FC = ({ severity, icon, }) => { + const [isOpen, setIsOpen] = useState(false); const theme = useTheme(); return ( - - -
- -
-
- + + +
+ +
+
+ - {items.map((n) => ( - - ))} -
-
+ }} + > + {items.map((n) => ( + + ))} + + + ); }; @@ -62,15 +70,14 @@ const NotificationPill: FC = ({ items, severity, icon, + isTooltipOpen, }) => { - const popover = usePopover(); - return ( ({ "& svg": { color: theme.roles[severity].outline }, - borderColor: popover.open ? theme.roles[severity].outline : undefined, + borderColor: isTooltipOpen ? theme.roles[severity].outline : undefined, })} > {items.length} @@ -99,7 +106,7 @@ export const NotificationActionButton: FC = (props) => { }; const styles = { - // Adds some spacing from the popover content + // Adds some spacing from the Tooltip content pillContainer: { padding: "8px 0", }, From f75a147ddab25d3d118aaf63237ef5a705a5d644 Mon Sep 17 00:00:00 2001 From: Andrew Aquino Date: Wed, 10 Sep 2025 01:03:07 +0000 Subject: [PATCH 53/54] refactor: factor out type import --- .../pages/OrganizationSettingsPage/IdpSyncPage/IdpPillList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpPillList.tsx b/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpPillList.tsx index 1a47e1887a4aa..330cd6bfdc9ac 100644 --- a/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpPillList.tsx +++ b/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpPillList.tsx @@ -1,4 +1,4 @@ -import { type Interpolation, type Theme } from "@emotion/react"; +import type { Interpolation, Theme } from "@emotion/react"; import Stack from "@mui/material/Stack"; import { Pill } from "components/Pill/Pill"; import { From ef1bfef981c609399e66c57178f30661ffdb8b84 Mon Sep 17 00:00:00 2001 From: Andrew Aquino Date: Wed, 10 Sep 2025 01:06:11 +0000 Subject: [PATCH 54/54] chore: delete deprecated Popover --- .../deprecated/Popover/Popover.stories.tsx | 57 ---- .../components/deprecated/Popover/Popover.tsx | 243 ------------------ 2 files changed, 300 deletions(-) delete mode 100644 site/src/components/deprecated/Popover/Popover.stories.tsx delete mode 100644 site/src/components/deprecated/Popover/Popover.tsx diff --git a/site/src/components/deprecated/Popover/Popover.stories.tsx b/site/src/components/deprecated/Popover/Popover.stories.tsx deleted file mode 100644 index 8661044c5187a..0000000000000 --- a/site/src/components/deprecated/Popover/Popover.stories.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import Button from "@mui/material/Button"; -import type { Meta, StoryObj } from "@storybook/react-vite"; -import { expect, screen, userEvent, waitFor, within } from "storybook/test"; -import { Popover, PopoverContent, PopoverTrigger } from "./Popover"; - -const meta: Meta = { - title: "components/PopoverDeprecated", - component: Popover, -}; - -export default meta; -type Story = StoryObj; - -const content = ` -According to all known laws of aviation, there is no way a bee should be able to fly. -Its wings are too small to get its fat little body off the ground. The bee, of course, -flies anyway because bees don't care what humans think is impossible. -`; - -export const Example: Story = { - args: { - children: ( - <> - - - - {content} - - ), - }, - play: async ({ canvasElement, step }) => { - const canvas = within(canvasElement); - - await step("click to open", async () => { - await userEvent.click(canvas.getByRole("button")); - await waitFor(() => - expect( - screen.getByText(/according to all known laws/i), - ).toBeInTheDocument(), - ); - }); - }, -}; - -export const Horizontal: Story = { - args: { - children: ( - <> - - - - {content} - - ), - }, - play: Example.play, -}; diff --git a/site/src/components/deprecated/Popover/Popover.tsx b/site/src/components/deprecated/Popover/Popover.tsx deleted file mode 100644 index cd30a40f1a784..0000000000000 --- a/site/src/components/deprecated/Popover/Popover.tsx +++ /dev/null @@ -1,243 +0,0 @@ -import MuiPopover, { - type PopoverProps as MuiPopoverProps, - // biome-ignore lint/style/noRestrictedImports: This is the base component that our custom popover is based on -} from "@mui/material/Popover"; -import { - cloneElement, - createContext, - type FC, - type HTMLAttributes, - type PointerEvent, - type PointerEventHandler, - type ReactElement, - type ReactNode, - type RefObject, - useContext, - useEffect, - useId, - useRef, - useState, -} from "react"; - -type TriggerMode = "hover" | "click"; - -type TriggerRef = RefObject; - -// Have to append ReactNode type to satisfy React's cloneElement function. It -// has absolutely no bearing on what happens at runtime -type TriggerElement = ReactNode & - ReactElement<{ - ref: TriggerRef; - onClick?: () => void; - }>; - -type PopoverContextValue = { - id: string; - open: boolean; - setOpen: (open: boolean) => void; - triggerRef: TriggerRef; - mode: TriggerMode; -}; - -const PopoverContext = createContext( - undefined, -); - -type BasePopoverProps = { - children: ReactNode; - mode?: TriggerMode; -}; - -// By separating controlled and uncontrolled props, we achieve more accurate -// type inference. -type UncontrolledPopoverProps = BasePopoverProps & { - open?: undefined; - onOpenChange?: undefined; -}; - -type ControlledPopoverProps = BasePopoverProps & { - open: boolean; - onOpenChange: (open: boolean) => void; -}; - -type PopoverProps = UncontrolledPopoverProps | ControlledPopoverProps; - -/** @deprecated prefer `components.Popover` */ -export const Popover: FC = (props) => { - const hookId = useId(); - const [uncontrolledOpen, setUncontrolledOpen] = useState(false); - const triggerRef: TriggerRef = useRef(null); - - // Helps makes sure that popovers close properly when the user switches to - // a different tab. This won't help with controlled instances of the - // component, but this is basically the most we can do from here - useEffect(() => { - const closeOnTabSwitch = () => setUncontrolledOpen(false); - window.addEventListener("blur", closeOnTabSwitch); - return () => window.removeEventListener("blur", closeOnTabSwitch); - }, []); - - const value: PopoverContextValue = { - triggerRef, - id: `${hookId}-popover`, - mode: props.mode ?? "click", - open: props.open ?? uncontrolledOpen, - setOpen: props.onOpenChange ?? setUncontrolledOpen, - }; - - return ( - - {props.children} - - ); -}; - -export const usePopover = () => { - const context = useContext(PopoverContext); - if (!context) { - throw new Error( - "Popover compound components cannot be rendered outside the Popover component", - ); - } - return context; -}; - -type PopoverTriggerRenderProps = Readonly<{ - isOpen: boolean; -}>; - -type PopoverTriggerProps = Readonly< - Omit, "children"> & { - children: - | TriggerElement - | ((props: PopoverTriggerRenderProps) => TriggerElement); - } ->; - -/** @deprecated prefer `components.Popover.PopoverTrigger` */ -export const PopoverTrigger: FC = (props) => { - const popover = usePopover(); - const { children, onClick, onPointerEnter, onPointerLeave, ...elementProps } = - props; - - const clickProps = { - onClick: (event: PointerEvent) => { - popover.setOpen(true); - onClick?.(event); - }, - }; - - const hoverProps = { - onPointerEnter: (event: PointerEvent) => { - popover.setOpen(true); - onPointerEnter?.(event); - }, - onPointerLeave: (event: PointerEvent) => { - popover.setOpen(false); - onPointerLeave?.(event); - }, - }; - - const evaluatedChildren = - typeof children === "function" - ? children({ isOpen: popover.open }) - : children; - - return cloneElement(evaluatedChildren, { - ...elementProps, - ...(popover.mode === "click" ? clickProps : hoverProps), - "aria-haspopup": true, - "aria-owns": popover.id, - "aria-expanded": popover.open, - ref: popover.triggerRef, - }); -}; - -type Horizontal = "left" | "right"; - -type PopoverContentProps = Omit< - MuiPopoverProps, - "open" | "onClose" | "anchorEl" -> & { - horizontal?: Horizontal; -}; - -/** @deprecated prefer `components.Popover.PopoverContent` */ -export const PopoverContent: FC = ({ - horizontal = "left", - onPointerEnter, - onPointerLeave, - ...popoverProps -}) => { - const popover = usePopover(); - const hoverMode = popover.mode === "hover"; - - return ( - popover.setOpen(false)} - anchorEl={popover.triggerRef.current} - /> - ); -}; - -const modeProps = ( - popover: PopoverContextValue, - externalOnPointerEnter: PointerEventHandler | undefined, - externalOnPointerLeave: PointerEventHandler | undefined, -) => { - if (popover.mode === "hover") { - return { - onPointerEnter: (event: PointerEvent) => { - popover.setOpen(true); - externalOnPointerEnter?.(event); - }, - onPointerLeave: (event: PointerEvent) => { - popover.setOpen(false); - externalOnPointerLeave?.(event); - }, - }; - } - - return {}; -}; - -const horizontalProps = (horizontal: Horizontal) => { - if (horizontal === "right") { - return { - anchorOrigin: { - vertical: "bottom", - horizontal: "right", - }, - transformOrigin: { - vertical: "top", - horizontal: "right", - }, - } as const; - } - - return { - anchorOrigin: { - vertical: "bottom", - horizontal: "left", - }, - } as const; -};