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()
- })
- })
})