refactor(site): Update agent status to include the lifecycle (#5835)

This commit is contained in:
Bruno Quaresma
2023-01-24 14:48:03 -03:00
committed by GitHub
parent f65c7ca6b3
commit bef9e72078
5 changed files with 182 additions and 70 deletions
@@ -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,
+138 -3
View File
@@ -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 Id
// 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),
},
}))
+13 -1
View File
@@ -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"
}
+4 -1
View File
@@ -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()
})
})
})