mirror of
https://github.com/coder/coder.git
synced 2026-09-22 13:10:21 +08:00
refactor(site): Update agent status to include the lifecycle (#5835)
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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 (
|
||||
<div
|
||||
role="status"
|
||||
aria-label={t("agentStatus.connected")}
|
||||
aria-label={t("agentStatus.connected.ready")}
|
||||
className={combineClasses([styles.status, styles.connected])}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const StartingLifecycle: React.FC = () => {
|
||||
const styles = useStyles()
|
||||
const { t } = useTranslation("workspacePage")
|
||||
|
||||
return (
|
||||
<Tooltip title={t("agentStatus.connected.starting")}>
|
||||
<div
|
||||
role="status"
|
||||
aria-label={t("agentStatus.connected.starting")}
|
||||
className={combineClasses([styles.status, styles.connecting])}
|
||||
/>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
const StartTimeoutLifecycle: React.FC<{
|
||||
agent: WorkspaceAgent
|
||||
}> = ({ agent }) => {
|
||||
const { t } = useTranslation("agent")
|
||||
const styles = useStyles()
|
||||
const anchorRef = useRef<SVGSVGElement>(null)
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const id = isOpen ? "timeout-popover" : undefined
|
||||
|
||||
return (
|
||||
<>
|
||||
<WarningRounded
|
||||
ref={anchorRef}
|
||||
onMouseEnter={() => setIsOpen(true)}
|
||||
onMouseLeave={() => setIsOpen(false)}
|
||||
role="status"
|
||||
aria-label={t("status.startTimeout")}
|
||||
className={styles.timeoutWarning}
|
||||
/>
|
||||
<HelpPopover
|
||||
id={id}
|
||||
open={isOpen}
|
||||
anchorEl={anchorRef.current}
|
||||
onOpen={() => setIsOpen(true)}
|
||||
onClose={() => setIsOpen(false)}
|
||||
>
|
||||
<HelpTooltipTitle>{t("startTimeoutTooltip.title")}</HelpTooltipTitle>
|
||||
<HelpTooltipText>
|
||||
{t("startTimeoutTooltip.message")}{" "}
|
||||
<Link
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href={agent.troubleshooting_url}
|
||||
>
|
||||
{t("startTimeoutTooltip.link")}
|
||||
</Link>
|
||||
.
|
||||
</HelpTooltipText>
|
||||
</HelpPopover>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const StartErrorLifecycle: React.FC<{
|
||||
agent: WorkspaceAgent
|
||||
}> = ({ agent }) => {
|
||||
const { t } = useTranslation("agent")
|
||||
const styles = useStyles()
|
||||
const anchorRef = useRef<SVGSVGElement>(null)
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const id = isOpen ? "timeout-popover" : undefined
|
||||
|
||||
return (
|
||||
<>
|
||||
<WarningRounded
|
||||
ref={anchorRef}
|
||||
onMouseEnter={() => setIsOpen(true)}
|
||||
onMouseLeave={() => setIsOpen(false)}
|
||||
role="status"
|
||||
aria-label={t("status.error")}
|
||||
className={styles.errorWarning}
|
||||
/>
|
||||
<HelpPopover
|
||||
id={id}
|
||||
open={isOpen}
|
||||
anchorEl={anchorRef.current}
|
||||
onOpen={() => setIsOpen(true)}
|
||||
onClose={() => setIsOpen(false)}
|
||||
>
|
||||
<HelpTooltipTitle>{t("startErrorTooltip.title")}</HelpTooltipTitle>
|
||||
<HelpTooltipText>
|
||||
{t("startErrorTooltip.message")}{" "}
|
||||
<Link
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href={agent.troubleshooting_url}
|
||||
>
|
||||
{t("startErrorTooltip.link")}
|
||||
</Link>
|
||||
.
|
||||
</HelpTooltipText>
|
||||
</HelpPopover>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const ConnectedStatus: React.FC<{
|
||||
agent: WorkspaceAgent
|
||||
}> = ({ agent }) => {
|
||||
return (
|
||||
<ChooseOne>
|
||||
<Cond condition={agent.lifecycle_state === "ready"}>
|
||||
<ReadyLifeCycle />
|
||||
</Cond>
|
||||
<Cond condition={agent.lifecycle_state === "start_timeout"}>
|
||||
<StartTimeoutLifecycle agent={agent} />
|
||||
</Cond>
|
||||
<Cond condition={agent.lifecycle_state === "start_error"}>
|
||||
<StartErrorLifecycle agent={agent} />
|
||||
</Cond>
|
||||
<Cond>
|
||||
<StartingLifecycle />
|
||||
</Cond>
|
||||
</ChooseOne>
|
||||
)
|
||||
}
|
||||
|
||||
const DisconnectedStatus: React.FC = () => {
|
||||
const styles = useStyles()
|
||||
const { t } = useTranslation("workspacePage")
|
||||
@@ -105,7 +232,7 @@ export const AgentStatus: React.FC<{
|
||||
return (
|
||||
<ChooseOne>
|
||||
<Cond condition={agent.status === "connected"}>
|
||||
<ConnectedStatus />
|
||||
<ConnectedStatus agent={agent} />
|
||||
</Cond>
|
||||
<Cond condition={agent.status === "disconnected"}>
|
||||
<DisconnectedStatus />
|
||||
@@ -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),
|
||||
},
|
||||
}))
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -36,7 +36,10 @@
|
||||
"pending": "Pending"
|
||||
},
|
||||
"agentStatus": {
|
||||
"connected": "Connected",
|
||||
"connected": {
|
||||
"ready": "Ready",
|
||||
"starting": "Starting..."
|
||||
},
|
||||
"connecting": "Connecting...",
|
||||
"disconnected": "Disconnected",
|
||||
"timeout": "Timeout"
|
||||
|
||||
@@ -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<string>(`agentStatus.${MockWorkspaceAgent.status}`, {
|
||||
ns: "workspacePage",
|
||||
}),
|
||||
)
|
||||
expect(agent1Status.length).toEqual(1)
|
||||
const agentDisconnected = await screen.findAllByLabelText(
|
||||
t<string>(`agentStatus.${MockWorkspaceAgentDisconnected.status}`, {
|
||||
ns: "workspacePage",
|
||||
}),
|
||||
)
|
||||
expect(agentDisconnected.length).toEqual(1)
|
||||
const agentConnecting = await screen.findAllByLabelText(
|
||||
t<string>(`agentStatus.${MockWorkspaceAgentConnecting.status}`, {
|
||||
ns: "workspacePage",
|
||||
}),
|
||||
)
|
||||
expect(agentConnecting.length).toEqual(1)
|
||||
expect(getTemplateMock).toBeCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user