Skip to content

Commit bf42f4e

Browse files
committed
feat(site): Support new agent lifecycle states
1 parent a57877a commit bf42f4e

File tree

5 files changed

+213
-5
lines changed

5 files changed

+213
-5
lines changed

site/src/components/Resources/AgentRow.stories.tsx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ import {
33
MockWorkspace,
44
MockWorkspaceAgent,
55
MockWorkspaceAgentConnecting,
6+
MockWorkspaceAgentOff,
67
MockWorkspaceAgentOutdated,
8+
MockWorkspaceAgentShutdownError,
9+
MockWorkspaceAgentShutdownTimeout,
10+
MockWorkspaceAgentShuttingDown,
711
MockWorkspaceAgentStartError,
812
MockWorkspaceAgentStarting,
913
MockWorkspaceAgentStartTimeout,
@@ -114,6 +118,38 @@ StartError.args = {
114118
showApps: true,
115119
}
116120

121+
export const ShuttingDown = Template.bind({})
122+
ShuttingDown.args = {
123+
agent: MockWorkspaceAgentShuttingDown,
124+
workspace: MockWorkspace,
125+
applicationsHost: "",
126+
showApps: true,
127+
}
128+
129+
export const ShutdownTimeout = Template.bind({})
130+
ShutdownTimeout.args = {
131+
agent: MockWorkspaceAgentShutdownTimeout,
132+
workspace: MockWorkspace,
133+
applicationsHost: "",
134+
showApps: true,
135+
}
136+
137+
export const ShutdownError = Template.bind({})
138+
ShutdownError.args = {
139+
agent: MockWorkspaceAgentShutdownError,
140+
workspace: MockWorkspace,
141+
applicationsHost: "",
142+
showApps: true,
143+
}
144+
145+
export const Off = Template.bind({})
146+
Off.args = {
147+
agent: MockWorkspaceAgentOff,
148+
workspace: MockWorkspace,
149+
applicationsHost: "",
150+
showApps: true,
151+
}
152+
117153
export const ShowingPortForward = Template.bind({})
118154
ShowingPortForward.args = {
119155
agent: MockWorkspaceAgent,

site/src/components/Resources/AgentStatus.tsx

Lines changed: 130 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ import Link from "@material-ui/core/Link"
1616
// If we think in the agent status and lifecycle into a single enum/state I’d
1717
// say we would have: connecting, timeout, disconnected, connected:created,
1818
// connected:starting, connected:start_timeout, connected:start_error,
19-
// connected:ready
19+
// connected:ready, connected:shutting_down, connected:shutdown_timeout,
20+
// connected:shutdown_error, connected:off.
2021

2122
const ReadyLifeCycle: React.FC = () => {
2223
const styles = useStyles()
@@ -132,6 +133,122 @@ const StartErrorLifecycle: React.FC<{
132133
)
133134
}
134135

136+
const ShuttingDownLifecycle: React.FC = () => {
137+
const styles = useStyles()
138+
const { t } = useTranslation("workspacePage")
139+
140+
return (
141+
<Tooltip title={t("agentStatus.connected.shuttingDown")}>
142+
<div
143+
role="status"
144+
aria-label={t("agentStatus.connected.shuttingDown")}
145+
className={combineClasses([styles.status, styles.connecting])}
146+
/>
147+
</Tooltip>
148+
)
149+
}
150+
151+
const ShutdownTimeoutLifecycle: React.FC<{
152+
agent: WorkspaceAgent
153+
}> = ({ agent }) => {
154+
const { t } = useTranslation("agent")
155+
const styles = useStyles()
156+
const anchorRef = useRef<SVGSVGElement>(null)
157+
const [isOpen, setIsOpen] = useState(false)
158+
const id = isOpen ? "timeout-popover" : undefined
159+
160+
return (
161+
<>
162+
<WarningRounded
163+
ref={anchorRef}
164+
onMouseEnter={() => setIsOpen(true)}
165+
onMouseLeave={() => setIsOpen(false)}
166+
role="status"
167+
aria-label={t("status.shutdownTimeout")}
168+
className={styles.timeoutWarning}
169+
/>
170+
<HelpPopover
171+
id={id}
172+
open={isOpen}
173+
anchorEl={anchorRef.current}
174+
onOpen={() => setIsOpen(true)}
175+
onClose={() => setIsOpen(false)}
176+
>
177+
<HelpTooltipTitle>{t("shutdownTimeoutTooltip.title")}</HelpTooltipTitle>
178+
<HelpTooltipText>
179+
{t("shutdownTimeoutTooltip.message")}{" "}
180+
<Link
181+
target="_blank"
182+
rel="noreferrer"
183+
href={agent.troubleshooting_url}
184+
>
185+
{t("shutdownTimeoutTooltip.link")}
186+
</Link>
187+
.
188+
</HelpTooltipText>
189+
</HelpPopover>
190+
</>
191+
)
192+
}
193+
194+
const ShutdownErrorLifecycle: React.FC<{
195+
agent: WorkspaceAgent
196+
}> = ({ agent }) => {
197+
const { t } = useTranslation("agent")
198+
const styles = useStyles()
199+
const anchorRef = useRef<SVGSVGElement>(null)
200+
const [isOpen, setIsOpen] = useState(false)
201+
const id = isOpen ? "timeout-popover" : undefined
202+
203+
return (
204+
<>
205+
<WarningRounded
206+
ref={anchorRef}
207+
onMouseEnter={() => setIsOpen(true)}
208+
onMouseLeave={() => setIsOpen(false)}
209+
role="status"
210+
aria-label={t("status.error")}
211+
className={styles.errorWarning}
212+
/>
213+
<HelpPopover
214+
id={id}
215+
open={isOpen}
216+
anchorEl={anchorRef.current}
217+
onOpen={() => setIsOpen(true)}
218+
onClose={() => setIsOpen(false)}
219+
>
220+
<HelpTooltipTitle>{t("shutdownErrorTooltip.title")}</HelpTooltipTitle>
221+
<HelpTooltipText>
222+
{t("shutdownErrorTooltip.message")}{" "}
223+
<Link
224+
target="_blank"
225+
rel="noreferrer"
226+
href={agent.troubleshooting_url}
227+
>
228+
{t("shutdownErrorTooltip.link")}
229+
</Link>
230+
.
231+
</HelpTooltipText>
232+
</HelpPopover>
233+
</>
234+
)
235+
}
236+
237+
const OffLifeCycle: React.FC = () => {
238+
const styles = useStyles()
239+
const { t } = useTranslation("workspacePage")
240+
241+
return (
242+
<Tooltip title={t("agentStatus.connected.off")}>
243+
<div
244+
role="status"
245+
aria-label={t("agentStatus.connected.off")}
246+
className={combineClasses([styles.status, styles.disconnected])}
247+
/>
248+
</Tooltip>
249+
)
250+
}
251+
135252
const ConnectedStatus: React.FC<{
136253
agent: WorkspaceAgent
137254
}> = ({ agent }) => {
@@ -156,6 +273,18 @@ const ConnectedStatus: React.FC<{
156273
<Cond condition={agent.lifecycle_state === "start_error"}>
157274
<StartErrorLifecycle agent={agent} />
158275
</Cond>
276+
<Cond condition={agent.lifecycle_state === "shutting_down"}>
277+
<ShuttingDownLifecycle agent={agent} />
278+
</Cond>
279+
<Cond condition={agent.lifecycle_state === "shutdown_timeout"}>
280+
<ShutdownTimeoutLifecycle agent={agent} />
281+
</Cond>
282+
<Cond condition={agent.lifecycle_state === "shutdown_error"}>
283+
<ShutdownErrorLifecycle agent={agent} />
284+
</Cond>
285+
<Cond condition={agent.lifecycle_state === "off"}>
286+
<OffLifeCycle />
287+
</Cond>
159288
<Cond>
160289
<StartingLifecycle />
161290
</Cond>

site/src/i18n/en/agent.json

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414
"status": {
1515
"timeout": "Timeout",
1616
"startTimeout": "Start Timeout",
17-
"startError": "Error"
17+
"startError": "Start Error",
18+
"shutdownTimeout": "Stop Timeout",
19+
"shutdownError": "Stop Error"
1820
},
1921
"timeoutTooltip": {
2022
"title": "Agent is taking too long to connect",
@@ -27,8 +29,18 @@
2729
"link": "Troubleshoot"
2830
},
2931
"startErrorTooltip": {
30-
"title": "Error starting agent",
31-
"message": "Something went wrong during the agent start.",
32+
"title": "Error starting the agent",
33+
"message": "Something went wrong during the agent startup.",
34+
"link": "Troubleshoot"
35+
},
36+
"shutdownTimeoutTooltip": {
37+
"title": "Agent is taking too long to stop",
38+
"message": "We noticed this agent is taking longer than expected to stop.",
39+
"link": "Troubleshoot"
40+
},
41+
"shutdownErrorTooltip": {
42+
"title": "Error stopping the agent",
43+
"message": "Something went wrong while trying to stop the agent.",
3244
"link": "Troubleshoot"
3345
},
3446
"unableToConnect": "Unable to connect"

site/src/i18n/en/workspacePage.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@
3939
"agentStatus": {
4040
"connected": {
4141
"ready": "Ready",
42-
"starting": "Starting..."
42+
"starting": "Starting...",
43+
"shuttingDown": "Stopping...",
44+
"off": "Stopped"
4345
},
4446
"connecting": "Connecting...",
4547
"disconnected": "Disconnected",

site/src/testHelpers/entities.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,7 @@ export const MockWorkspaceAgent: TypesGen.WorkspaceAgent = {
401401
lifecycle_state: "starting",
402402
login_before_ready: false,
403403
startup_script_timeout_seconds: 120,
404+
shutdown_script_timeout_seconds: 120,
404405
}
405406

406407
export const MockWorkspaceAgentDisconnected: TypesGen.WorkspaceAgent = {
@@ -478,6 +479,34 @@ export const MockWorkspaceAgentStartError: TypesGen.WorkspaceAgent = {
478479
lifecycle_state: "start_error",
479480
}
480481

482+
export const MockWorkspaceAgentShuttingDown: TypesGen.WorkspaceAgent = {
483+
...MockWorkspaceAgent,
484+
id: "test-workspace-agent-shutting-down",
485+
name: "a-shutting-down-workspace-agent",
486+
lifecycle_state: "shutting_down",
487+
}
488+
489+
export const MockWorkspaceAgentShutdownTimeout: TypesGen.WorkspaceAgent = {
490+
...MockWorkspaceAgent,
491+
id: "test-workspace-agent-shutdown-timeout",
492+
name: "a-workspace-agent-timed-out-while-running-shutdownup-script",
493+
lifecycle_state: "shutdown_timeout",
494+
}
495+
496+
export const MockWorkspaceAgentShutdownError: TypesGen.WorkspaceAgent = {
497+
...MockWorkspaceAgent,
498+
id: "test-workspace-agent-shutdown-error",
499+
name: "a-workspace-agent-errored-while-running-shutdownup-script",
500+
lifecycle_state: "shutdown_error",
501+
}
502+
503+
export const MockWorkspaceAgentOff: TypesGen.WorkspaceAgent = {
504+
...MockWorkspaceAgent,
505+
id: "test-workspace-agent-off",
506+
name: "a-workspace-agent-is-shut-down",
507+
lifecycle_state: "off",
508+
}
509+
481510
export const MockWorkspaceResource: TypesGen.WorkspaceResource = {
482511
agents: [
483512
MockWorkspaceAgent,

0 commit comments

Comments
 (0)