Skip to content

Commit 7ec16cf

Browse files
feat(site): add latency to the terminal (#7801)
1 parent 0413ed0 commit 7ec16cf

File tree

10 files changed

+222
-146
lines changed

10 files changed

+222
-146
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { Route, Routes } from "react-router-dom"
21
import { renderWithAuth } from "testHelpers/renderHelpers"
32
import { DashboardLayout } from "./DashboardLayout"
43
import * as API from "api/api"
@@ -10,12 +9,8 @@ test("Show the new Coder version notification", async () => {
109
version: "v0.12.9",
1110
url: "https://github.com/coder/coder/releases/tag/v0.12.9",
1211
})
13-
renderWithAuth(
14-
<Routes>
15-
<Route element={<DashboardLayout />}>
16-
<Route element={<h1>Test page</h1>} />
17-
</Route>
18-
</Routes>,
19-
)
12+
renderWithAuth(<DashboardLayout />, {
13+
children: [{ element: <h1>Test page</h1> }],
14+
})
2015
await screen.findByTestId("update-check-snackbar")
2116
})

site/src/components/Navbar/NavbarView.tsx

+3-44
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import Drawer from "@mui/material/Drawer"
22
import IconButton from "@mui/material/IconButton"
33
import List from "@mui/material/List"
44
import ListItem from "@mui/material/ListItem"
5-
import { makeStyles, useTheme } from "@mui/styles"
5+
import { makeStyles } from "@mui/styles"
66
import MenuIcon from "@mui/icons-material/Menu"
77
import { CoderIcon } from "components/Icons/CoderIcon"
88
import { FC, useRef, useState } from "react"
@@ -20,10 +20,9 @@ import KeyboardArrowDownOutlined from "@mui/icons-material/KeyboardArrowDownOutl
2020
import { ProxyContextValue } from "contexts/ProxyContext"
2121
import { displayError } from "components/GlobalSnackbar/utils"
2222
import Divider from "@mui/material/Divider"
23-
import HelpOutline from "@mui/icons-material/HelpOutline"
24-
import Tooltip from "@mui/material/Tooltip"
2523
import Skeleton from "@mui/material/Skeleton"
2624
import { BUTTON_SM_HEIGHT } from "theme/theme"
25+
import { ProxyStatusLatency } from "components/ProxyStatusLatency/ProxyStatusLatency"
2726

2827
export const USERS_LINK = `/users?filter=${encodeURIComponent("status:active")}`
2928

@@ -232,7 +231,6 @@ const ProxyMenu: FC<{ proxyContextValue: ProxyContextValue }> = ({
232231
</Box>
233232
{selectedProxy.display_name}
234233
<ProxyStatusLatency
235-
proxy={selectedProxy}
236234
latency={latencies?.[selectedProxy.id]?.latencyMS}
237235
/>
238236
</Box>
@@ -277,10 +275,7 @@ const ProxyMenu: FC<{ proxyContextValue: ProxyContextValue }> = ({
277275
/>
278276
</Box>
279277
{proxy.display_name}
280-
<ProxyStatusLatency
281-
proxy={proxy}
282-
latency={latencies?.[proxy.id]?.latencyMS}
283-
/>
278+
<ProxyStatusLatency latency={latencies?.[proxy.id]?.latencyMS} />
284279
</Box>
285280
</MenuItem>
286281
))}
@@ -301,42 +296,6 @@ const ProxyMenu: FC<{ proxyContextValue: ProxyContextValue }> = ({
301296
)
302297
}
303298

304-
const ProxyStatusLatency: FC<{ proxy: TypesGen.Region; latency?: number }> = ({
305-
proxy,
306-
latency,
307-
}) => {
308-
const theme = useTheme()
309-
let color = theme.palette.success.light
310-
311-
if (!latency) {
312-
return (
313-
<Tooltip title="Latency not available">
314-
<HelpOutline
315-
sx={{
316-
ml: "auto",
317-
fontSize: "14px !important",
318-
color: (theme) => theme.palette.text.secondary,
319-
}}
320-
/>
321-
</Tooltip>
322-
)
323-
}
324-
325-
if (latency >= 300) {
326-
color = theme.palette.error.light
327-
}
328-
329-
if (!proxy.healthy || latency >= 100) {
330-
color = theme.palette.warning.light
331-
}
332-
333-
return (
334-
<Box sx={{ color, fontSize: 13, marginLeft: "auto" }}>
335-
{latency.toFixed(0)}ms
336-
</Box>
337-
)
338-
}
339-
340299
const useStyles = makeStyles((theme) => ({
341300
root: {
342301
height: navHeight,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { useTheme } from "@mui/material/styles"
2+
import HelpOutline from "@mui/icons-material/HelpOutline"
3+
import Box from "@mui/material/Box"
4+
import Tooltip from "@mui/material/Tooltip"
5+
import { FC } from "react"
6+
import { getLatencyColor } from "utils/latency"
7+
8+
export const ProxyStatusLatency: FC<{ latency?: number }> = ({ latency }) => {
9+
const theme = useTheme()
10+
const color = getLatencyColor(theme, latency)
11+
12+
if (!latency) {
13+
return (
14+
<Tooltip title="Latency not available">
15+
<HelpOutline
16+
sx={{
17+
ml: "auto",
18+
fontSize: "14px !important",
19+
color,
20+
}}
21+
/>
22+
</Tooltip>
23+
)
24+
}
25+
26+
return (
27+
<Box sx={{ color, fontSize: 13, marginLeft: "auto" }}>
28+
{latency.toFixed(0)}ms
29+
</Box>
30+
)
31+
}

site/src/components/Resources/AgentLatency.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
} from "components/Tooltips/HelpTooltip"
99
import { Stack } from "components/Stack/Stack"
1010
import { WorkspaceAgent, DERPRegion } from "api/typesGenerated"
11-
import { getLatencyColor } from "utils/colors"
11+
import { getLatencyColor } from "utils/latency"
1212

1313
const getDisplayLatency = (theme: Theme, agent: WorkspaceAgent) => {
1414
// Find the right latency to display

site/src/pages/TerminalPage/TerminalPage.test.tsx

+48-71
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,18 @@ import "jest-canvas-mock"
33
import WS from "jest-websocket-mock"
44
import { rest } from "msw"
55
import {
6-
MockPrimaryWorkspaceProxy,
7-
MockProxyLatencies,
6+
MockUser,
87
MockWorkspace,
98
MockWorkspaceAgent,
10-
MockWorkspaceProxies,
119
} from "testHelpers/entities"
1210
import { TextDecoder, TextEncoder } from "util"
1311
import { ReconnectingPTYRequest } from "../../api/types"
14-
import { history, render } from "../../testHelpers/renderHelpers"
12+
import {
13+
renderWithAuth,
14+
waitForLoaderToBeRemoved,
15+
} from "../../testHelpers/renderHelpers"
1516
import { server } from "../../testHelpers/server"
1617
import TerminalPage, { Language } from "./TerminalPage"
17-
import { Route, Routes } from "react-router-dom"
18-
import { ProxyContext } from "contexts/ProxyContext"
1918

2019
Object.defineProperty(window, "matchMedia", {
2120
writable: true,
@@ -35,56 +34,35 @@ Object.defineProperty(window, "TextEncoder", {
3534
value: TextEncoder,
3635
})
3736

38-
const renderTerminal = () => {
39-
// @emyrk using renderWithAuth would be best here, but I was unable to get it to work.
40-
return render(
41-
<Routes>
42-
<Route
43-
path="/:username/:workspace/terminal"
44-
element={
45-
<ProxyContext.Provider
46-
value={{
47-
proxyLatencies: MockProxyLatencies,
48-
proxy: {
49-
proxy: MockPrimaryWorkspaceProxy,
50-
preferredPathAppURL: "",
51-
preferredWildcardHostname: "",
52-
},
53-
proxies: MockWorkspaceProxies,
54-
isFetched: true,
55-
isLoading: false,
56-
setProxy: jest.fn(),
57-
clearProxy: jest.fn(),
58-
refetchProxyLatencies: jest.fn(),
59-
}}
60-
>
61-
<TerminalPage />
62-
</ProxyContext.Provider>
63-
}
64-
/>
65-
</Routes>,
66-
)
37+
const renderTerminal = async (
38+
route = `/${MockUser.username}/${MockWorkspace.name}/terminal`,
39+
) => {
40+
const utils = renderWithAuth(<TerminalPage />, {
41+
route,
42+
path: "/:username/:workspace/terminal",
43+
})
44+
await waitForLoaderToBeRemoved()
45+
return utils
6746
}
6847

6948
const expectTerminalText = (container: HTMLElement, text: string) => {
70-
return waitFor(() => {
71-
const elements = container.getElementsByClassName("xterm-rows")
72-
if (elements.length === 0) {
73-
throw new Error("no xterm-rows")
74-
}
75-
const row = elements[0] as HTMLDivElement
76-
if (!row.textContent) {
77-
throw new Error("no text content")
78-
}
79-
expect(row.textContent).toContain(text)
80-
})
49+
return waitFor(
50+
() => {
51+
const elements = container.getElementsByClassName("xterm-rows")
52+
if (elements.length === 0) {
53+
throw new Error("no xterm-rows")
54+
}
55+
const row = elements[0] as HTMLDivElement
56+
if (!row.textContent) {
57+
throw new Error("no text content")
58+
}
59+
expect(row.textContent).toContain(text)
60+
},
61+
{ timeout: 3_000 },
62+
)
8163
}
8264

8365
describe("TerminalPage", () => {
84-
beforeEach(() => {
85-
history.push(`/some-user/${MockWorkspace.name}/terminal`)
86-
})
87-
8866
it("shows an error if fetching workspace fails", async () => {
8967
// Given
9068
server.use(
@@ -97,7 +75,7 @@ describe("TerminalPage", () => {
9775
)
9876

9977
// When
100-
const { container } = renderTerminal()
78+
const { container } = await renderTerminal()
10179

10280
// Then
10381
await expectTerminalText(container, Language.workspaceErrorMessagePrefix)
@@ -112,67 +90,66 @@ describe("TerminalPage", () => {
11290
)
11391

11492
// When
115-
const { container } = renderTerminal()
93+
const { container } = await renderTerminal()
11694

11795
// Then
11896
await expectTerminalText(container, Language.websocketErrorMessagePrefix)
11997
})
12098

12199
it("renders data from the backend", async () => {
122100
// Given
123-
const server = new WS(
124-
"ws://localhost/api/v2/workspaceagents/" + MockWorkspaceAgent.id + "/pty",
101+
const ws = new WS(
102+
`ws://localhost/api/v2/workspaceagents/${MockWorkspaceAgent.id}/pty`,
125103
)
126104
const text = "something to render"
127105

128106
// When
129-
const { container } = renderTerminal()
107+
const { container } = await renderTerminal()
130108

131109
// Then
132-
await server.connected
133-
server.send(text)
110+
await ws.connected
111+
ws.send(text)
134112
await expectTerminalText(container, text)
135-
server.close()
113+
ws.close()
136114
})
137115

138116
it("resizes on connect", async () => {
139117
// Given
140-
const server = new WS(
141-
"ws://localhost/api/v2/workspaceagents/" + MockWorkspaceAgent.id + "/pty",
118+
const ws = new WS(
119+
`ws://localhost/api/v2/workspaceagents/${MockWorkspaceAgent.id}/pty`,
142120
)
143121

144122
// When
145-
renderTerminal()
123+
await renderTerminal()
146124

147125
// Then
148-
await server.connected
149-
const msg = await server.nextMessage
126+
await ws.connected
127+
const msg = await ws.nextMessage
150128
const req: ReconnectingPTYRequest = JSON.parse(
151129
new TextDecoder().decode(msg as Uint8Array),
152130
)
153131

154132
expect(req.height).toBeGreaterThan(0)
155133
expect(req.width).toBeGreaterThan(0)
156-
server.close()
134+
ws.close()
157135
})
158136

159137
it("supports workspace.agent syntax", async () => {
160138
// Given
161-
const server = new WS(
162-
"ws://localhost/api/v2/workspaceagents/" + MockWorkspaceAgent.id + "/pty",
139+
const ws = new WS(
140+
`ws://localhost/api/v2/workspaceagents/${MockWorkspaceAgent.id}/pty`,
163141
)
164142
const text = "something to render"
165143

166144
// When
167-
history.push(
145+
const { container } = await renderTerminal(
168146
`/some-user/${MockWorkspace.name}.${MockWorkspaceAgent.name}/terminal`,
169147
)
170-
const { container } = renderTerminal()
171148

172149
// Then
173-
await server.connected
174-
server.send(text)
150+
await ws.connected
151+
ws.send(text)
175152
await expectTerminalText(container, text)
176-
server.close()
153+
ws.close()
177154
})
178155
})

0 commit comments

Comments
 (0)