Skip to content

Commit a39b51e

Browse files
committed
chore(site): replace agent log service
1 parent 201a6c0 commit a39b51e

File tree

5 files changed

+91
-179
lines changed

5 files changed

+91
-179
lines changed

site/src/api/api.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1392,7 +1392,7 @@ export const watchBuildLogsByTemplateVersionId = (
13921392
type WatchWorkspaceAgentLogsOptions = {
13931393
after: number;
13941394
onMessage: (logs: TypesGen.WorkspaceAgentLog[]) => void;
1395-
onDone: () => void;
1395+
onDone?: () => void;
13961396
onError: (error: Error) => void;
13971397
};
13981398

@@ -1423,7 +1423,7 @@ export const watchWorkspaceAgentLogs = (
14231423
onError(new Error("socket errored"));
14241424
});
14251425
socket.addEventListener("close", () => {
1426-
onDone();
1426+
onDone && onDone();
14271427
});
14281428

14291429
return socket;

site/src/components/Resources/AgentRow.tsx

Lines changed: 66 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
import Popover from "@mui/material/Popover";
22
import { makeStyles, useTheme } from "@mui/styles";
33
import Skeleton from "@mui/material/Skeleton";
4-
import { useMachine } from "@xstate/react";
4+
import * as API from "api/api";
55
import CodeOutlined from "@mui/icons-material/CodeOutlined";
66
import {
77
CloseDropdown,
88
OpenDropdown,
99
} from "components/DropdownArrows/DropdownArrows";
10-
import { LogLine, logLineHeight } from "components/WorkspaceBuildLogs/Logs";
10+
import {
11+
Line,
12+
LogLine,
13+
logLineHeight,
14+
} from "components/WorkspaceBuildLogs/Logs";
1115
import { PortForwardButton } from "./PortForwardButton";
1216
import { VSCodeDesktopButton } from "components/Resources/VSCodeDesktopButton/VSCodeDesktopButton";
1317
import {
@@ -25,15 +29,11 @@ import AutoSizer from "react-virtualized-auto-sizer";
2529
import { FixedSizeList as List, ListOnScrollProps } from "react-window";
2630
import { colors } from "theme/colors";
2731
import { combineClasses } from "utils/combineClasses";
28-
import {
29-
LineWithID,
30-
workspaceAgentLogsMachine,
31-
} from "xServices/workspaceAgentLogs/workspaceAgentLogsXService";
3232
import {
3333
Workspace,
3434
WorkspaceAgent,
3535
WorkspaceAgentMetadata,
36-
} from "../../api/typesGenerated";
36+
} from "api/typesGenerated";
3737
import { AppLink } from "./AppLink/AppLink";
3838
import { SSHButton } from "./SSHButton/SSHButton";
3939
import { Stack } from "../Stack/Stack";
@@ -44,6 +44,14 @@ import { AgentVersion } from "./AgentVersion";
4444
import { AgentStatus } from "./AgentStatus";
4545
import Collapse from "@mui/material/Collapse";
4646
import { useProxy } from "contexts/ProxyContext";
47+
import { displayError } from "components/GlobalSnackbar/utils";
48+
49+
// Logs are stored as the Line interface to make rendering
50+
// much more efficient. Instead of mapping objects each time, we're
51+
// able to just pass the array of logs to the component.
52+
export interface LineWithID extends Line {
53+
id: number;
54+
}
4755

4856
export interface AgentRowProps {
4957
agent: WorkspaceAgent;
@@ -68,24 +76,10 @@ export const AgentRow: FC<AgentRowProps> = ({
6876
hideVSCodeDesktopButton,
6977
serverVersion,
7078
onUpdateAgent,
71-
storybookLogs,
7279
storybookAgentMetadata,
7380
sshPrefix,
7481
}) => {
7582
const styles = useStyles();
76-
const [logsMachine, sendLogsEvent] = useMachine(workspaceAgentLogsMachine, {
77-
context: { agentID: agent.id },
78-
services: process.env.STORYBOOK
79-
? {
80-
getLogs: async () => {
81-
return storybookLogs || [];
82-
},
83-
streamLogs: () => async () => {
84-
// noop
85-
},
86-
}
87-
: undefined,
88-
});
8983
const theme = useTheme();
9084
const startupScriptAnchorRef = useRef<HTMLButtonElement>(null);
9185
const [startupScriptOpen, setStartupScriptOpen] = useState(false);
@@ -94,36 +88,17 @@ export const AgentRow: FC<AgentRowProps> = ({
9488
showApps &&
9589
((agent.status === "connected" && hasAppsToDisplay) ||
9690
agent.status === "connecting");
97-
const hasStartupFeatures =
98-
Boolean(agent.logs_length) || Boolean(logsMachine.context.logs?.length);
91+
const hasStartupFeatures = Boolean(agent.logs_length);
9992
const { proxy } = useProxy();
100-
10193
const [showLogs, setShowLogs] = useState(
10294
["starting", "start_timeout"].includes(agent.lifecycle_state) &&
10395
hasStartupFeatures,
10496
);
105-
useEffect(() => {
106-
setShowLogs(agent.lifecycle_state !== "ready" && hasStartupFeatures);
107-
}, [agent.lifecycle_state, hasStartupFeatures]);
108-
// External applications can provide startup logs for an agent during it's spawn.
109-
// These could be Kubernetes logs, or other logs that are useful to the user.
110-
// For this reason, we want to fetch these logs when the agent is starting.
111-
useEffect(() => {
112-
if (agent.lifecycle_state === "starting") {
113-
sendLogsEvent("FETCH_LOGS");
114-
}
115-
}, [sendLogsEvent, agent.lifecycle_state]);
116-
useEffect(() => {
117-
// We only want to fetch logs when they are actually shown,
118-
// otherwise we can make a lot of requests that aren't necessary.
119-
if (showLogs && logsMachine.can("FETCH_LOGS")) {
120-
sendLogsEvent("FETCH_LOGS");
121-
}
122-
}, [logsMachine, sendLogsEvent, showLogs]);
97+
const agentLogs = useAgentLogs(agent.id, { enabled: showLogs });
12398
const logListRef = useRef<List>(null);
12499
const logListDivRef = useRef<HTMLDivElement>(null);
125100
const startupLogs = useMemo(() => {
126-
const allLogs = logsMachine.context.logs || [];
101+
const allLogs = agentLogs || [];
127102

128103
const logs = [...allLogs];
129104
if (agent.logs_overflowed) {
@@ -135,8 +110,13 @@ export const AgentRow: FC<AgentRowProps> = ({
135110
});
136111
}
137112
return logs;
138-
}, [logsMachine.context.logs, agent.logs_overflowed]);
113+
}, [agentLogs, agent.logs_overflowed]);
139114
const [bottomOfLogs, setBottomOfLogs] = useState(true);
115+
116+
useEffect(() => {
117+
setShowLogs(agent.lifecycle_state !== "ready" && hasStartupFeatures);
118+
}, [agent.lifecycle_state, hasStartupFeatures]);
119+
140120
// This is a layout effect to remove flicker when we're scrolling to the bottom.
141121
useLayoutEffect(() => {
142122
// If we're currently watching the bottom, we always want to stay at the bottom.
@@ -396,6 +376,48 @@ export const AgentRow: FC<AgentRowProps> = ({
396376
);
397377
};
398378

379+
const useAgentLogs = (agentId: string, { enabled }: { enabled: boolean }) => {
380+
const [logs, setLogs] = useState<LineWithID[]>();
381+
const socket = useRef<WebSocket | null>(null);
382+
383+
useEffect(() => {
384+
if (!enabled) {
385+
socket.current?.close();
386+
return;
387+
}
388+
389+
socket.current = API.watchWorkspaceAgentLogs(agentId, {
390+
// Get all logs
391+
after: 0,
392+
onMessage: (logs) => {
393+
setLogs((previousLogs) => {
394+
const newLogs: LineWithID[] = logs.map((log) => ({
395+
id: log.id,
396+
level: log.level || "info",
397+
output: log.output,
398+
time: log.created_at,
399+
}));
400+
401+
if (!previousLogs) {
402+
return newLogs;
403+
}
404+
405+
return [...previousLogs, ...newLogs];
406+
});
407+
},
408+
onError: () => {
409+
displayError("Error on gettings agent logs");
410+
},
411+
});
412+
413+
return () => {
414+
socket.current?.close();
415+
};
416+
}, [agentId, enabled]);
417+
418+
return logs;
419+
};
420+
399421
const useStyles = makeStyles((theme) => ({
400422
agentRow: {
401423
backgroundColor: theme.palette.background.paperLight,

site/src/components/WorkspaceBuildLogs/Logs.tsx

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,25 @@ export const LogLine: FC<{
6060
const output = useMemo(() => {
6161
return convert.toHtml(line.output.split(/\r/g).pop() as string);
6262
}, [line.output]);
63+
const isUsingLineNumber = number !== undefined;
6364

6465
return (
65-
<div className={combineClasses([styles.line, line.level])} style={style}>
66+
<div
67+
className={combineClasses([
68+
styles.line,
69+
line.level,
70+
isUsingLineNumber && styles.lineNumber,
71+
])}
72+
style={style}
73+
>
6674
{!hideTimestamp && (
6775
<>
68-
<span className={styles.time}>
76+
<span
77+
className={combineClasses([
78+
styles.time,
79+
isUsingLineNumber && styles.number,
80+
])}
81+
>
6982
{number ? number : dayjs(line.time).format(`HH:mm:ss.SSS`)}
7083
</span>
7184
<span className={styles.space} />
@@ -119,6 +132,9 @@ const useStyles = makeStyles((theme) => ({
119132
backgroundColor: theme.palette.warning.dark,
120133
},
121134
},
135+
lineNumber: {
136+
paddingLeft: theme.spacing(2),
137+
},
122138
space: {
123139
userSelect: "none",
124140
width: theme.spacing(3),
@@ -132,4 +148,8 @@ const useStyles = makeStyles((theme) => ({
132148
display: "inline-block",
133149
color: theme.palette.text.secondary,
134150
},
151+
number: {
152+
width: theme.spacing(4),
153+
textAlign: "right",
154+
},
135155
}));

site/src/pages/WorkspacePage/WorkspacePage.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ const renderWorkspacePage = async () => {
4343
jest
4444
.spyOn(api, "watchWorkspaceAgentLogs")
4545
.mockImplementation((_, options) => {
46-
options.onDone();
46+
options.onDone && options.onDone();
4747
return new WebSocket("");
4848
});
4949
renderWithAuth(<WorkspacePage />, {

site/src/xServices/workspaceAgentLogs/workspaceAgentLogsXService.ts

Lines changed: 0 additions & 130 deletions
This file was deleted.

0 commit comments

Comments
 (0)