From bdb5cb3102a059cd1d1f5ee22f949275071fad79 Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Thu, 21 Jul 2022 15:43:34 +0000 Subject: [PATCH 1/5] feat: give update button primary focus when applicable resolves #3024 --- .../WorkspaceActions/ActionCtas.tsx | 42 +++++-------------- .../WorkspaceActions.test.tsx | 6 +-- .../WorkspaceActions/WorkspaceActions.tsx | 37 +++++++++++----- .../components/WorkspaceActions/constants.ts | 8 ++-- 4 files changed, 44 insertions(+), 49 deletions(-) diff --git a/site/src/components/WorkspaceActions/ActionCtas.tsx b/site/src/components/WorkspaceActions/ActionCtas.tsx index b99807d1050ae..1cf762ad42096 100644 --- a/site/src/components/WorkspaceActions/ActionCtas.tsx +++ b/site/src/components/WorkspaceActions/ActionCtas.tsx @@ -6,8 +6,6 @@ import DeleteOutlineIcon from "@material-ui/icons/DeleteOutline" import HighlightOffIcon from "@material-ui/icons/HighlightOff" import PlayCircleOutlineIcon from "@material-ui/icons/PlayCircleOutline" import { FC } from "react" -import { Workspace } from "../../api/typesGenerated" -import { WorkspaceStatus } from "../../util/workspace" import { WorkspaceActionButton } from "../WorkspaceActionButton/WorkspaceActionButton" export const Language = { @@ -22,6 +20,16 @@ interface WorkspaceAction { handleAction: () => void } +export const UpdateButton: FC = ({ handleAction }) => { + const styles = useStyles() + + return ( + + ) +} + export const StartButton: FC = ({ handleAction }) => { const styles = useStyles() @@ -61,36 +69,6 @@ export const DeleteButton: FC = ({ handleAction }) => { ) } -type UpdateAction = WorkspaceAction & { - workspace: Workspace - workspaceStatus: WorkspaceStatus -} - -export const UpdateButton: FC = ({ handleAction, workspace, workspaceStatus }) => { - const styles = useStyles() - - /** - * Jobs submitted while another job is in progress will be discarded, - * so check whether workspace job status has reached completion (whether successful or not). - */ - const canAcceptJobs = (workspaceStatus: WorkspaceStatus) => - ["started", "stopped", "deleted", "error", "canceled"].includes(workspaceStatus) - - return ( - <> - {workspace.outdated && canAcceptJobs(workspaceStatus) && ( - - )} - - ) -} - export const CancelButton: FC = ({ handleAction }) => { const styles = useStyles() diff --git a/site/src/components/WorkspaceActions/WorkspaceActions.test.tsx b/site/src/components/WorkspaceActions/WorkspaceActions.test.tsx index d916393f70026..ca8b84d0e2d68 100644 --- a/site/src/components/WorkspaceActions/WorkspaceActions.test.tsx +++ b/site/src/components/WorkspaceActions/WorkspaceActions.test.tsx @@ -79,11 +79,11 @@ describe("WorkspaceActions", () => { }) }) describe("when the workspace is outdated", () => { - it("primary is start; secondary are delete, update", async () => { + it("primary is update; secondary are start, delete", async () => { await renderAndClick({ workspace: Mocks.MockOutdatedWorkspace }) - expect(screen.getByTestId("primary-cta")).toHaveTextContent(Language.start) + expect(screen.getByTestId("primary-cta")).toHaveTextContent(Language.update) + expect(screen.getByTestId("secondary-ctas")).toHaveTextContent(Language.start) expect(screen.getByTestId("secondary-ctas")).toHaveTextContent(Language.delete) - expect(screen.getByTestId("secondary-ctas")).toHaveTextContent(Language.update) }) }) }) diff --git a/site/src/components/WorkspaceActions/WorkspaceActions.tsx b/site/src/components/WorkspaceActions/WorkspaceActions.tsx index 10cc02a486270..aec143c4c8ac4 100644 --- a/site/src/components/WorkspaceActions/WorkspaceActions.tsx +++ b/site/src/components/WorkspaceActions/WorkspaceActions.tsx @@ -1,13 +1,20 @@ import Button from "@material-ui/core/Button" import Popover from "@material-ui/core/Popover" import { makeStyles } from "@material-ui/core/styles" -import { FC, ReactNode, useEffect, useRef, useState } from "react" +import { FC, ReactNode, useEffect, useMemo, useRef, useState } from "react" import { Workspace } from "../../api/typesGenerated" -import { getWorkspaceStatus } from "../../util/workspace" +import { getWorkspaceStatus, WorkspaceStatus } from "../../util/workspace" import { CloseDropdown, OpenDropdown } from "../DropdownArrows/DropdownArrows" import { CancelButton, DeleteButton, StartButton, StopButton, UpdateButton } from "./ActionCtas" import { ButtonTypesEnum, WorkspaceStateActions, WorkspaceStateEnum } from "./constants" +/** + * Jobs submitted while another job is in progress will be discarded, + * so check whether workspace job status has reached completion (whether successful or not). + */ +const canAcceptJobs = (workspaceStatus: WorkspaceStatus) => + ["started", "stopped", "deleted", "error", "canceled"].includes(workspaceStatus) + export interface WorkspaceActionsProps { workspace: Workspace handleStart: () => void @@ -34,7 +41,23 @@ export const WorkspaceActions: FC = ({ workspace.latest_build, ) const workspaceState = WorkspaceStateEnum[workspaceStatus] - const actions = WorkspaceStateActions[workspaceState] + + const canUpdateWorkspace = workspace.outdated && canAcceptJobs(workspaceStatus) + + // actions are the primary and secondary CTAs that appear in the workspace actions dropdown + const actions = useMemo(() => { + if (!canUpdateWorkspace) { + return WorkspaceStateActions[workspaceState] + } + + // if an update is available, we make the update button the primary CTA + // and move the former primary CTA to the secondary actions list + const updatedActions = { ...WorkspaceStateActions[workspaceState] } + updatedActions.secondary.unshift(updatedActions.primary) + updatedActions.primary = ButtonTypesEnum.update + + return updatedActions + }, [canUpdateWorkspace, workspaceState]) /** * Ensures we close the popover before calling any action handler @@ -58,16 +81,10 @@ export const WorkspaceActions: FC = ({ // A mapping of button type to the corresponding React component const buttonMapping: ButtonMapping = { + [ButtonTypesEnum.update]: , [ButtonTypesEnum.start]: , [ButtonTypesEnum.stop]: , [ButtonTypesEnum.delete]: , - [ButtonTypesEnum.update]: ( - - ), [ButtonTypesEnum.cancel]: , [ButtonTypesEnum.canceling]: disabledButton, [ButtonTypesEnum.disabled]: disabledButton, diff --git a/site/src/components/WorkspaceActions/constants.ts b/site/src/components/WorkspaceActions/constants.ts index 88531fefedab6..38391b1ec6218 100644 --- a/site/src/components/WorkspaceActions/constants.ts +++ b/site/src/components/WorkspaceActions/constants.ts @@ -45,7 +45,7 @@ export const WorkspaceStateActions: StateActionsType = { }, [WorkspaceStateEnum.started]: { primary: ButtonTypesEnum.stop, - secondary: [ButtonTypesEnum.delete, ButtonTypesEnum.update], + secondary: [ButtonTypesEnum.delete], }, [WorkspaceStateEnum.stopping]: { primary: ButtonTypesEnum.cancel, @@ -53,16 +53,16 @@ export const WorkspaceStateActions: StateActionsType = { }, [WorkspaceStateEnum.stopped]: { primary: ButtonTypesEnum.start, - secondary: [ButtonTypesEnum.delete, ButtonTypesEnum.update], + secondary: [ButtonTypesEnum.delete], }, [WorkspaceStateEnum.canceled]: { primary: ButtonTypesEnum.start, - secondary: [ButtonTypesEnum.stop, ButtonTypesEnum.delete, ButtonTypesEnum.update], + secondary: [ButtonTypesEnum.stop, ButtonTypesEnum.delete], }, // in the case of an error [WorkspaceStateEnum.error]: { primary: ButtonTypesEnum.start, // give the user the ability to start a workspace again - secondary: [ButtonTypesEnum.delete, ButtonTypesEnum.update], // allows the user to delete or update + secondary: [ButtonTypesEnum.delete], // allows the user to delete }, /** * disabled states From fe019ea3039cdd5104d5a1dc328a3a2f9da67500 Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Fri, 22 Jul 2022 13:58:58 +0000 Subject: [PATCH 2/5] added update tooltip --- .../Tooltips/HelpTooltip/HelpTooltip.tsx | 13 ++++---- .../Tooltips/OutdatedHelpTooltip.tsx | 9 +++--- site/src/components/Workspace/Workspace.tsx | 2 +- .../WorkspaceStats/WorkspaceStats.test.tsx | 31 +++++++++++++++++++ .../WorkspaceStats/WorkspaceStats.tsx | 19 +++++++++--- .../WorkspaceStats/createDayString.ts | 9 ++++++ 6 files changed, 68 insertions(+), 15 deletions(-) create mode 100644 site/src/components/WorkspaceStats/WorkspaceStats.test.tsx create mode 100644 site/src/components/WorkspaceStats/createDayString.ts diff --git a/site/src/components/Tooltips/HelpTooltip/HelpTooltip.tsx b/site/src/components/Tooltips/HelpTooltip/HelpTooltip.tsx index d8b4e460d26af..1c97f47431c7a 100644 --- a/site/src/components/Tooltips/HelpTooltip/HelpTooltip.tsx +++ b/site/src/components/Tooltips/HelpTooltip/HelpTooltip.tsx @@ -33,7 +33,7 @@ export const HelpTooltip: React.FC = ({ children, open, size = const styles = useStyles({ size }) const anchorRef = useRef(null) const [isOpen, setIsOpen] = useState(!!open) - const id = isOpen ? "help-popover" : undefined + const id = isOpen ? "help-popover" : "" const onClose = () => { setIsOpen(false) @@ -110,16 +110,17 @@ export const HelpTooltipLink: React.FC<{ href: string }> = ({ children, href }) ) } -export const HelpTooltipAction: React.FC<{ icon: Icon; onClick: () => void }> = ({ - children, - icon: Icon, - onClick, -}) => { +export const HelpTooltipAction: React.FC<{ + icon: Icon + onClick: () => void + ariaLabel?: string +}> = ({ children, icon: Icon, onClick, ariaLabel }) => { const styles = useStyles() const tooltip = useHelpTooltip() return (