-
Notifications
You must be signed in to change notification settings - Fork 929
chore: add support for one-way WebSockets to UI #16855
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
b8cfe76
367906d
09f5e95
04a3846
ca8e94f
81a723a
4364a3d
ecb2940
bfe4d9f
6cdfc21
682e2f4
20ad778
9b19ceb
423910f
247dbb6
6e3e0d8
c422379
0824dd4
db448d7
60bf505
c1cee57
70b74e2
8e34e91
8db068a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,9 +3,11 @@ import Skeleton from "@mui/material/Skeleton"; | |
import Tooltip from "@mui/material/Tooltip"; | ||
import { watchAgentMetadata } from "api/api"; | ||
import type { | ||
ServerSentEvent, | ||
WorkspaceAgent, | ||
WorkspaceAgentMetadata, | ||
} from "api/typesGenerated"; | ||
import { displayError } from "components/GlobalSnackbar/utils"; | ||
import { Stack } from "components/Stack/Stack"; | ||
import dayjs from "dayjs"; | ||
import { | ||
|
@@ -17,6 +19,7 @@ import { | |
useState, | ||
} from "react"; | ||
import { MONOSPACE_FONT_FAMILY } from "theme/constants"; | ||
import type { OneWayWebSocket } from "utils/OneWayWebSocket"; | ||
|
||
type ItemStatus = "stale" | "valid" | "loading"; | ||
|
||
|
@@ -46,43 +49,47 @@ export const AgentMetadata: FC<AgentMetadataProps> = ({ | |
agent, | ||
storybookMetadata, | ||
}) => { | ||
const [metadata, setMetadata] = useState< | ||
WorkspaceAgentMetadata[] | undefined | ||
>(undefined); | ||
|
||
const [metadata, setMetadata] = useState<WorkspaceAgentMetadata[]>(); | ||
useEffect(() => { | ||
if (storybookMetadata !== undefined) { | ||
setMetadata(storybookMetadata); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was removed because it introduces unnecessary renders – better to just use |
||
return; | ||
} | ||
|
||
let timeout: ReturnType<typeof setTimeout> | undefined = undefined; | ||
let timeoutId: number | undefined = undefined; | ||
let latestSocket: OneWayWebSocket | undefined = undefined; | ||
|
||
const connect = (): (() => void) => { | ||
const source = watchAgentMetadata(agent.id); | ||
const createNewConnection = () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The previous
Figured it'd be better to split up those responsibilities |
||
const socket = watchAgentMetadata(agent.id); | ||
latestSocket = socket; | ||
|
||
source.onerror = (e) => { | ||
console.error("received error in watch stream", e); | ||
socket.addEventListener("error", () => { | ||
displayError("Socket closed unexpectedly. Creating new connection..."); | ||
setMetadata(undefined); | ||
source.close(); | ||
|
||
timeout = setTimeout(() => { | ||
connect(); | ||
}, 3000); | ||
}; | ||
|
||
source.addEventListener("data", (e) => { | ||
const data = JSON.parse(e.data); | ||
setMetadata(data); | ||
window.clearTimeout(timeoutId); | ||
timeoutId = window.setTimeout(() => { | ||
createNewConnection(); | ||
}, 3_000); | ||
}); | ||
return () => { | ||
if (timeout !== undefined) { | ||
clearTimeout(timeout); | ||
|
||
socket.addEventListener("message", (e) => { | ||
try { | ||
const payload = JSON.parse(e.data) as ServerSentEvent; | ||
if (payload.type === "data") { | ||
setMetadata(payload.data as WorkspaceAgentMetadata[]); | ||
} | ||
} catch { | ||
displayError( | ||
"Unable to process newest response from server. Please try refreshing the page.", | ||
); | ||
Parkreiner marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
source.close(); | ||
}; | ||
}); | ||
}; | ||
|
||
createNewConnection(); | ||
return () => { | ||
window.clearTimeout(timeoutId); | ||
latestSocket?.close(); | ||
}; | ||
return connect(); | ||
}, [agent.id, storybookMetadata]); | ||
|
||
if (metadata === undefined) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,46 +1,38 @@ | ||
import { watchBuildLogsByTemplateVersionId } from "api/api"; | ||
import type { ProvisionerJobLog, TemplateVersion } from "api/typesGenerated"; | ||
import { useEffectEvent } from "hooks/hookPolyfills"; | ||
import { useEffect, useState } from "react"; | ||
|
||
export const useWatchVersionLogs = ( | ||
templateVersion: TemplateVersion | undefined, | ||
options?: { onDone: () => Promise<unknown> }, | ||
) => { | ||
const [logs, setLogs] = useState<ProvisionerJobLog[] | undefined>(); | ||
const [logs, setLogs] = useState<ProvisionerJobLog[]>(); | ||
const templateVersionId = templateVersion?.id; | ||
const templateVersionStatus = templateVersion?.job.status; | ||
const [cachedVersionId, setCachedVersionId] = useState(templateVersionId); | ||
if (cachedVersionId !== templateVersionId) { | ||
setCachedVersionId(templateVersionId); | ||
setLogs([]); | ||
} | ||
Comment on lines
+12
to
+16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is needed to remove Biome's There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is in regards to // biome-ignore lint/correctness/useExhaustiveDependencies: reason
useEffect(() => {
if (cachedVersionId !== templateVersionId) {
setCachedVersionId(templateVersionId);
setLogs([]);
}
}, [...]); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, the linter warning is actually valid – the Was mainly calling it out, because while it's the "correct" way to do things (and is shouted out in the React docs), I do think it's an ugly pattern, and you need a lot of context for how React works under the hood to know why it's better than the |
||
|
||
// biome-ignore lint/correctness/useExhaustiveDependencies: consider refactoring | ||
const stableOnDone = useEffectEvent(() => options?.onDone()); | ||
const status = templateVersion?.job.status; | ||
const canWatch = status === "running" || status === "pending"; | ||
useEffect(() => { | ||
setLogs(undefined); | ||
}, [templateVersionId]); | ||
|
||
useEffect(() => { | ||
if (!templateVersionId || !templateVersionStatus) { | ||
return; | ||
} | ||
|
||
if ( | ||
templateVersionStatus !== "running" && | ||
templateVersionStatus !== "pending" | ||
) { | ||
if (!templateVersionId || !canWatch) { | ||
return; | ||
} | ||
|
||
const socket = watchBuildLogsByTemplateVersionId(templateVersionId, { | ||
onMessage: (log) => { | ||
setLogs((logs) => (logs ? [...logs, log] : [log])); | ||
}, | ||
onDone: options?.onDone, | ||
onError: (error) => { | ||
console.error(error); | ||
onError: (error) => console.error(error), | ||
onDone: stableOnDone, | ||
onMessage: (newLog) => { | ||
setLogs((current) => [...(current ?? []), newLog]); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Really wanted to just make sure that the logs is always an array, and get rid of the |
||
}, | ||
}); | ||
|
||
return () => { | ||
socket.close(); | ||
}; | ||
}, [options?.onDone, templateVersionId, templateVersionStatus]); | ||
return () => socket.close(); | ||
}, [stableOnDone, canWatch, templateVersionId]); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The previous version of the effect was re-synchronizing way too often, because we put a function without a stable memory reference and
Best option seemed to be to wrap the callback, and also collapse the status to a boolean |
||
|
||
return logs; | ||
}; |
Uh oh!
There was an error while loading. Please reload this page.