diff --git a/site/src/components/Resources/AgentRow.stories.tsx b/site/src/components/Resources/AgentRow.stories.tsx index 7a74352db92fc..4871030bfbd19 100644 --- a/site/src/components/Resources/AgentRow.stories.tsx +++ b/site/src/components/Resources/AgentRow.stories.tsx @@ -3,6 +3,9 @@ import { MockWorkspace, MockWorkspaceAgent, MockWorkspaceAgentConnecting, + MockWorkspaceAgentStartError, + MockWorkspaceAgentStarting, + MockWorkspaceAgentStartTimeout, MockWorkspaceAgentTimeout, MockWorkspaceApp, } from "testHelpers/entities" @@ -86,6 +89,30 @@ Timeout.args = { showApps: true, } +export const Starting = Template.bind({}) +Starting.args = { + agent: MockWorkspaceAgentStarting, + workspace: MockWorkspace, + applicationsHost: "", + showApps: true, +} + +export const StartTimeout = Template.bind({}) +StartTimeout.args = { + agent: MockWorkspaceAgentStartTimeout, + workspace: MockWorkspace, + applicationsHost: "", + showApps: true, +} + +export const StartError = Template.bind({}) +StartError.args = { + agent: MockWorkspaceAgentStartError, + workspace: MockWorkspace, + applicationsHost: "", + showApps: true, +} + export const ShowingPortForward = Template.bind({}) ShowingPortForward.args = { agent: MockWorkspaceAgent, diff --git a/site/src/components/Resources/AgentStatus.tsx b/site/src/components/Resources/AgentStatus.tsx index 48de1597c8ca3..b607d3201b5a2 100644 --- a/site/src/components/Resources/AgentStatus.tsx +++ b/site/src/components/Resources/AgentStatus.tsx @@ -13,19 +13,146 @@ import { import { useRef, useState } from "react" import Link from "@material-ui/core/Link" -const ConnectedStatus: React.FC = () => { +// If we think in the agent status and lifecycle into a single enum/state I’d +// say we would have: connecting, timeout, disconnected, connected:created, +// connected:starting, connected:start_timeout, connected:start_error, +// connected:ready + +const ReadyLifeCycle: React.FC = () => { const styles = useStyles() const { t } = useTranslation("workspacePage") return (
) } +const StartingLifecycle: React.FC = () => { + const styles = useStyles() + const { t } = useTranslation("workspacePage") + + return ( + +
+ + ) +} + +const StartTimeoutLifecycle: React.FC<{ + agent: WorkspaceAgent +}> = ({ agent }) => { + const { t } = useTranslation("agent") + const styles = useStyles() + const anchorRef = useRef(null) + const [isOpen, setIsOpen] = useState(false) + const id = isOpen ? "timeout-popover" : undefined + + return ( + <> + setIsOpen(true)} + onMouseLeave={() => setIsOpen(false)} + role="status" + aria-label={t("status.startTimeout")} + className={styles.timeoutWarning} + /> + setIsOpen(true)} + onClose={() => setIsOpen(false)} + > + {t("startTimeoutTooltip.title")} + + {t("startTimeoutTooltip.message")}{" "} + + {t("startTimeoutTooltip.link")} + + . + + + + ) +} + +const StartErrorLifecycle: React.FC<{ + agent: WorkspaceAgent +}> = ({ agent }) => { + const { t } = useTranslation("agent") + const styles = useStyles() + const anchorRef = useRef(null) + const [isOpen, setIsOpen] = useState(false) + const id = isOpen ? "timeout-popover" : undefined + + return ( + <> + setIsOpen(true)} + onMouseLeave={() => setIsOpen(false)} + role="status" + aria-label={t("status.error")} + className={styles.errorWarning} + /> + setIsOpen(true)} + onClose={() => setIsOpen(false)} + > + {t("startErrorTooltip.title")} + + {t("startErrorTooltip.message")}{" "} + + {t("startErrorTooltip.link")} + + . + + + + ) +} + +const ConnectedStatus: React.FC<{ + agent: WorkspaceAgent +}> = ({ agent }) => { + return ( + + + + + + + + + + + + + + + ) +} + const DisconnectedStatus: React.FC = () => { const styles = useStyles() const { t } = useTranslation("workspacePage") @@ -105,7 +232,7 @@ export const AgentStatus: React.FC<{ return ( - + @@ -160,4 +287,12 @@ const useStyles = makeStyles((theme) => ({ position: "relative", top: theme.spacing(1), }, + + errorWarning: { + color: theme.palette.error.main, + width: theme.spacing(2.5), + height: theme.spacing(2.5), + position: "relative", + top: theme.spacing(1), + }, })) diff --git a/site/src/i18n/en/agent.json b/site/src/i18n/en/agent.json index e2edf8805432d..e30049da483dd 100644 --- a/site/src/i18n/en/agent.json +++ b/site/src/i18n/en/agent.json @@ -12,12 +12,24 @@ "noApps": "None" }, "status": { - "timeout": "Timeout" + "timeout": "Timeout", + "startTimeout": "Start Timeout", + "startError": "Error" }, "timeoutTooltip": { "title": "Agent is taking too long to connect", "message": "We noticed this agent is taking longer than expected to connect.", "link": "Troubleshoot" }, + "startTimeoutTooltip": { + "title": "Agent is taking too long to start", + "message": "We noticed this agent is taking longer than expected to start.", + "link": "Troubleshoot" + }, + "startErrorTooltip": { + "title": "Error starting agent", + "message": "Something went wrong during the agent start.", + "link": "Troubleshoot" + }, "unableToConnect": "Unable to connect" } diff --git a/site/src/i18n/en/workspacePage.json b/site/src/i18n/en/workspacePage.json index 2a8820daf5188..17f7e2aba3941 100644 --- a/site/src/i18n/en/workspacePage.json +++ b/site/src/i18n/en/workspacePage.json @@ -36,7 +36,10 @@ "pending": "Pending" }, "agentStatus": { - "connected": "Connected", + "connected": { + "ready": "Ready", + "starting": "Starting..." + }, "connecting": "Connecting...", "disconnected": "Disconnected", "timeout": "Timeout" diff --git a/site/src/pages/WorkspacePage/WorkspacePage.test.tsx b/site/src/pages/WorkspacePage/WorkspacePage.test.tsx index c6fedc3ffeb13..2110e27ada732 100644 --- a/site/src/pages/WorkspacePage/WorkspacePage.test.tsx +++ b/site/src/pages/WorkspacePage/WorkspacePage.test.tsx @@ -18,11 +18,7 @@ import { MockStoppingWorkspace, MockTemplate, MockWorkspace, - MockWorkspaceAgent, - MockWorkspaceAgentConnecting, - MockWorkspaceAgentDisconnected, MockWorkspaceBuild, - MockWorkspaceResource2, renderWithAuth, waitForLoaderToBeRemoved, } from "../../testHelpers/renderHelpers" @@ -284,65 +280,4 @@ describe("WorkspacePage", () => { }) }) }) - - describe("Resources", () => { - it("shows the status of each agent in each resource", async () => { - const getTemplateMock = jest - .spyOn(api, "getTemplate") - .mockResolvedValueOnce(MockTemplate) - - const workspaceWithResources = { - ...MockWorkspace, - latest_build: { - ...MockWorkspaceBuild, - resources: [ - { - ...MockWorkspaceResource2, - agents: [ - MockWorkspaceAgent, - MockWorkspaceAgentDisconnected, - MockWorkspaceAgentConnecting, - ], - }, - ], - }, - } - - server.use( - rest.get( - `/api/v2/users/:username/workspace/:workspaceName`, - (req, res, ctx) => { - return res(ctx.status(200), ctx.json(workspaceWithResources)) - }, - ), - ) - - await renderWorkspacePage() - const agent1Names = await screen.findAllByText(MockWorkspaceAgent.name) - expect(agent1Names.length).toEqual(1) - const agent2Names = await screen.findAllByText( - MockWorkspaceAgentDisconnected.name, - ) - expect(agent2Names.length).toEqual(2) - const agent1Status = await screen.findAllByLabelText( - t(`agentStatus.${MockWorkspaceAgent.status}`, { - ns: "workspacePage", - }), - ) - expect(agent1Status.length).toEqual(1) - const agentDisconnected = await screen.findAllByLabelText( - t(`agentStatus.${MockWorkspaceAgentDisconnected.status}`, { - ns: "workspacePage", - }), - ) - expect(agentDisconnected.length).toEqual(1) - const agentConnecting = await screen.findAllByLabelText( - t(`agentStatus.${MockWorkspaceAgentConnecting.status}`, { - ns: "workspacePage", - }), - ) - expect(agentConnecting.length).toEqual(1) - expect(getTemplateMock).toBeCalled() - }) - }) })