From a9fb43c6c8b4c9e8b42cc5c89e2980749b9e7889 Mon Sep 17 00:00:00 2001 From: Bruno Date: Thu, 2 Jun 2022 16:50:22 +0000 Subject: [PATCH 1/9] Load logs from stream --- .vscode/settings.json | 1 + site/src/api/api.ts | 16 ++++ .../workspaceBuild/workspaceBuildXService.ts | 79 ++++++++++++++----- 3 files changed, 77 insertions(+), 19 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index e14c039bb7457..1ba61d11c056a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -72,6 +72,7 @@ "VMID", "weblinks", "webrtc", + "workspacebuilds", "xerrors", "xstate", "yamux" diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 68c41b77e2ae7..aa7ea14edfdfc 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -270,3 +270,19 @@ export const getWorkspaceBuildLogs = async (buildname: string): Promise(`/api/v2/workspacebuilds/${buildname}/logs`) return response.data } + +export const streamWorkspaceBuildLogs = async (buildname: string): Promise> => { + // Axios does not support HTTP stream in the browser + // https://github.com/axios/axios/issues/1474 + // So we are going to use window.fetch and return a "stream" reader + const reader = await window.fetch(`/api/v2/workspacebuilds/${buildname}/logs?follow=true`).then((res) => { + if (!res.body) { + throw new Error("No body returned from the response") + } + + // eslint-disable-next-line compat/compat + return res.body.pipeThrough(new TextDecoderStream()).getReader() + }) + + return reader +} diff --git a/site/src/xServices/workspaceBuild/workspaceBuildXService.ts b/site/src/xServices/workspaceBuild/workspaceBuildXService.ts index b6cfa8bf3e3e3..45db31f996ad5 100644 --- a/site/src/xServices/workspaceBuild/workspaceBuildXService.ts +++ b/site/src/xServices/workspaceBuild/workspaceBuildXService.ts @@ -9,19 +9,28 @@ type LogsContext = { getBuildError?: Error | unknown // Logs logs?: ProvisionerJobLog[] - getBuildLogsError?: Error | unknown } +type LogsEvent = + | { + type: "ADD_LOGS" + logs: ProvisionerJobLog[] + } + | { + type: "NO_MORE_LOGS" + } + export const workspaceBuildMachine = createMachine( { id: "workspaceBuildState", schema: { context: {} as LogsContext, + events: {} as LogsEvent, services: {} as { getWorkspaceBuild: { data: WorkspaceBuild } - getWorkspaceBuildLogs: { + getLogs: { data: ProvisionerJobLog[] } }, @@ -50,25 +59,38 @@ export const workspaceBuildMachine = createMachine( }, }, logs: { - initial: "gettingLogs", + initial: "gettingExistentLogs", states: { - gettingLogs: { - entry: "clearGetBuildLogsError", + gettingExistentLogs: { invoke: { - src: "getWorkspaceBuildLogs", + id: "getLogs", + src: "getLogs", onDone: { - target: "idle", - actions: "assignLogs", - }, - onError: { - target: "idle", - actions: "assignGetBuildLogsError", + actions: ["assignLogs"], + target: "watchingLogs", }, }, }, - idle: {}, + watchingLogs: { + id: "watchingLogs", + invoke: { + id: "streamWorkspaceBuildLogs", + src: "streamWorkspaceBuildLogs", + }, + }, + }, + on: { + ADD_LOGS: { + actions: "addNewLogs", + }, + NO_MORE_LOGS: { + target: "loaded", + }, }, }, + loaded: { + type: "final", + }, }, }, { @@ -87,16 +109,35 @@ export const workspaceBuildMachine = createMachine( assignLogs: assign({ logs: (_, event) => event.data, }), - assignGetBuildLogsError: assign({ - getBuildLogsError: (_, event) => event.data, - }), - clearGetBuildLogsError: assign({ - getBuildLogsError: (_) => undefined, + addNewLogs: assign({ + logs: (context, event) => { + const previousLogs = context.logs ?? [] + return [...previousLogs, ...event.logs] + }, }), }, services: { getWorkspaceBuild: (ctx) => API.getWorkspaceBuild(ctx.buildId), - getWorkspaceBuildLogs: (ctx) => API.getWorkspaceBuildLogs(ctx.buildId), + getLogs: async (ctx) => API.getWorkspaceBuildLogs(ctx.buildId), + streamWorkspaceBuildLogs: (ctx) => async (callback) => { + const reader = await API.streamWorkspaceBuildLogs(ctx.buildId) + + // Watching for the stream + // eslint-disable-next-line no-constant-condition + while (true) { + const { value, done } = await reader.read() + + if (done) { + callback("NO_MORE_LOGS") + break + } + + if (value) { + const logs = value.split("\n").map((jsonString) => JSON.parse(jsonString) as ProvisionerJobLog) + callback({ type: "ADD_LOGS", logs }) + } + } + }, }, }, ) From 54784117c63f9d5286d0c5116c86e708e6e52c98 Mon Sep 17 00:00:00 2001 From: Bruno Date: Thu, 2 Jun 2022 17:27:37 +0000 Subject: [PATCH 2/9] Use polyfill for streaming --- site/can-ndjson-stream.d.ts | 4 ++++ site/package.json | 1 + site/src/api/api.ts | 17 ++++++++--------- .../workspaceBuild/workspaceBuildXService.ts | 17 +++++++---------- site/yarn.lock | 12 ++++++++++++ 5 files changed, 32 insertions(+), 19 deletions(-) create mode 100644 site/can-ndjson-stream.d.ts diff --git a/site/can-ndjson-stream.d.ts b/site/can-ndjson-stream.d.ts new file mode 100644 index 0000000000000..5859213801d93 --- /dev/null +++ b/site/can-ndjson-stream.d.ts @@ -0,0 +1,4 @@ +declare module "can-ndjson-stream" { + function ndjsonStream(body: ReadableStream | null): Promise> + export default ndjsonStream +} diff --git a/site/package.json b/site/package.json index 8e9fdfe5c5f32..20f7adaf0bccd 100644 --- a/site/package.json +++ b/site/package.json @@ -35,6 +35,7 @@ "@xstate/inspect": "0.6.5", "@xstate/react": "3.0.0", "axios": "0.26.1", + "can-ndjson-stream": "1.0.2", "cronstrue": "2.5.0", "dayjs": "1.11.2", "formik": "2.2.9", diff --git a/site/src/api/api.ts b/site/src/api/api.ts index aa7ea14edfdfc..b044d406bad9b 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -1,4 +1,5 @@ import axios, { AxiosRequestHeaders } from "axios" +import ndjsonStream from "can-ndjson-stream" import * as Types from "./types" import { WorkspaceBuildTransition } from "./types" import * as TypesGen from "./typesGenerated" @@ -271,18 +272,16 @@ export const getWorkspaceBuildLogs = async (buildname: string): Promise> => { +export const streamWorkspaceBuildLogs = async ( + buildname: string, +): Promise> => { // Axios does not support HTTP stream in the browser // https://github.com/axios/axios/issues/1474 // So we are going to use window.fetch and return a "stream" reader - const reader = await window.fetch(`/api/v2/workspacebuilds/${buildname}/logs?follow=true`).then((res) => { - if (!res.body) { - throw new Error("No body returned from the response") - } - - // eslint-disable-next-line compat/compat - return res.body.pipeThrough(new TextDecoderStream()).getReader() - }) + const reader = await window + .fetch(`/api/v2/workspacebuilds/${buildname}/logs?follow=true`) + .then((res) => ndjsonStream(res.body)) + .then((stream) => stream.getReader()) return reader } diff --git a/site/src/xServices/workspaceBuild/workspaceBuildXService.ts b/site/src/xServices/workspaceBuild/workspaceBuildXService.ts index 45db31f996ad5..7ec045a556a07 100644 --- a/site/src/xServices/workspaceBuild/workspaceBuildXService.ts +++ b/site/src/xServices/workspaceBuild/workspaceBuildXService.ts @@ -13,8 +13,8 @@ type LogsContext = { type LogsEvent = | { - type: "ADD_LOGS" - logs: ProvisionerJobLog[] + type: "ADD_LOG" + log: ProvisionerJobLog } | { type: "NO_MORE_LOGS" @@ -80,8 +80,8 @@ export const workspaceBuildMachine = createMachine( }, }, on: { - ADD_LOGS: { - actions: "addNewLogs", + ADD_LOG: { + actions: "addLog", }, NO_MORE_LOGS: { target: "loaded", @@ -109,10 +109,10 @@ export const workspaceBuildMachine = createMachine( assignLogs: assign({ logs: (_, event) => event.data, }), - addNewLogs: assign({ + addLog: assign({ logs: (context, event) => { const previousLogs = context.logs ?? [] - return [...previousLogs, ...event.logs] + return [...previousLogs, event.log] }, }), }, @@ -132,10 +132,7 @@ export const workspaceBuildMachine = createMachine( break } - if (value) { - const logs = value.split("\n").map((jsonString) => JSON.parse(jsonString) as ProvisionerJobLog) - callback({ type: "ADD_LOGS", logs }) - } + callback({ type: "ADD_LOG", log: value }) } }, }, diff --git a/site/yarn.lock b/site/yarn.lock index abc20e7ca7004..d1b29591f7fca 100644 --- a/site/yarn.lock +++ b/site/yarn.lock @@ -4731,6 +4731,18 @@ camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== +can-namespace@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/can-namespace/-/can-namespace-1.0.0.tgz#0b8fafafbb11352b9ead4222ffe3822405b43e99" + integrity sha512-1sBY/SLwwcmxz3NhyVhLjt2uD/dZ7V1mII82/MIXSDn5QXnslnosJnjlP8+yTx2uTCRvw1jlFDElRs4pX7AG5w== + +can-ndjson-stream@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/can-ndjson-stream/-/can-ndjson-stream-1.0.2.tgz#6a8131f9c8c697215163b3fe49a0c02e4439cb47" + integrity sha512-//tM8wcTV42SyD1JGua7WMVftZEeTwapcHJTTe3vJwuVywXD01CJbdEkgwRYjy2evIByVJV21ZKBdSv5ygIw1w== + dependencies: + can-namespace "^1.0.0" + caniuse-api@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" From 600d5bf7ec3025d6a3891323c9d30bb8c0187e31 Mon Sep 17 00:00:00 2001 From: Bruno Date: Thu, 2 Jun 2022 17:49:13 +0000 Subject: [PATCH 3/9] Add loading indicator --- .../WorkspaceBuildLogs.stories.tsx | 6 ++++++ .../WorkspaceBuildLogs/WorkspaceBuildLogs.tsx | 17 ++++++++++++++--- .../WorkspaceBuildPage/WorkspaceBuildPage.tsx | 8 ++++---- .../workspaceBuild/workspaceBuildXService.ts | 7 ++----- 4 files changed, 26 insertions(+), 12 deletions(-) diff --git a/site/src/components/WorkspaceBuildLogs/WorkspaceBuildLogs.stories.tsx b/site/src/components/WorkspaceBuildLogs/WorkspaceBuildLogs.stories.tsx index 71886186d2804..4c5ef7b7a8c99 100644 --- a/site/src/components/WorkspaceBuildLogs/WorkspaceBuildLogs.stories.tsx +++ b/site/src/components/WorkspaceBuildLogs/WorkspaceBuildLogs.stories.tsx @@ -13,3 +13,9 @@ export const Example = Template.bind({}) Example.args = { logs: MockWorkspaceBuildLogs, } + +export const Loading = Template.bind({}) +Loading.args = { + logs: MockWorkspaceBuildLogs, + isLoading: true, +} diff --git a/site/src/components/WorkspaceBuildLogs/WorkspaceBuildLogs.tsx b/site/src/components/WorkspaceBuildLogs/WorkspaceBuildLogs.tsx index 3d2a0bff8b788..5afb4dac8cb60 100644 --- a/site/src/components/WorkspaceBuildLogs/WorkspaceBuildLogs.tsx +++ b/site/src/components/WorkspaceBuildLogs/WorkspaceBuildLogs.tsx @@ -1,3 +1,4 @@ +import CircularProgress from "@material-ui/core/CircularProgress" import { makeStyles } from "@material-ui/core/styles" import dayjs from "dayjs" import { FC } from "react" @@ -35,16 +36,17 @@ const getStageDurationInSeconds = (logs: ProvisionerJobLog[]) => { export interface WorkspaceBuildLogsProps { logs: ProvisionerJobLog[] + isLoading: boolean } -export const WorkspaceBuildLogs: FC = ({ logs }) => { +export const WorkspaceBuildLogs: FC = ({ logs, isLoading }) => { const groupedLogsByStage = groupLogsByStage(logs) const stages = Object.keys(groupedLogsByStage) const styles = useStyles() return (
- {stages.map((stage) => { + {stages.map((stage, stageIndex) => { const logs = groupedLogsByStage[stage] const isEmpty = logs.every((log) => log.output === "") const lines = logs.map((log) => ({ @@ -52,12 +54,16 @@ export const WorkspaceBuildLogs: FC = ({ logs }) => { output: log.output, })) const duration = getStageDurationInSeconds(logs) + const isLastStage = stageIndex === stages.length - 1 + const shouldDisplaySpinner = isLoading && isLastStage + const shouldDisplayDuration = !isLoading && duration return (
{stage}
- {duration &&
{duration} seconds
} + {shouldDisplaySpinner && } + {shouldDisplayDuration &&
{duration} seconds
}
{!isEmpty && }
@@ -78,6 +84,7 @@ const useStyles = makeStyles((theme) => ({ fontSize: theme.typography.body1.fontSize, padding: theme.spacing(2), paddingLeft: theme.spacing(4), + paddingRight: theme.spacing(4), borderBottom: `1px solid ${theme.palette.divider}`, backgroundColor: theme.palette.background.paper, display: "flex", @@ -94,4 +101,8 @@ const useStyles = makeStyles((theme) => ({ padding: theme.spacing(2), paddingLeft: theme.spacing(4), }, + + spinner: { + marginLeft: "auto", + }, })) diff --git a/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.tsx b/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.tsx index 458cdf5f2bf90..700a7240246a6 100644 --- a/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.tsx +++ b/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.tsx @@ -4,7 +4,6 @@ import { useMachine } from "@xstate/react" import { FC } from "react" import { useParams } from "react-router-dom" import { ProvisionerJobLog } from "../../api/typesGenerated" -import { Loader } from "../../components/Loader/Loader" import { Margins } from "../../components/Margins/Margins" import { Stack } from "../../components/Stack/Stack" import { WorkspaceBuildLogs } from "../../components/WorkspaceBuildLogs/WorkspaceBuildLogs" @@ -27,8 +26,10 @@ const useBuildId = () => { export const WorkspaceBuildPage: FC = () => { const buildId = useBuildId() - const [buildState] = useMachine(workspaceBuildMachine, { context: { buildId } }) + // We can initialize logs as an empty array because it will be a stream + const [buildState] = useMachine(workspaceBuildMachine, { context: { buildId, logs: [] } }) const { logs, build } = buildState.context + const isLoading = !buildState.matches("loaded") const styles = useStyles() return ( @@ -39,8 +40,7 @@ export const WorkspaceBuildPage: FC = () => { {build && } - {!logs && } - {logs && } + ) diff --git a/site/src/xServices/workspaceBuild/workspaceBuildXService.ts b/site/src/xServices/workspaceBuild/workspaceBuildXService.ts index 7ec045a556a07..137616c6da7d4 100644 --- a/site/src/xServices/workspaceBuild/workspaceBuildXService.ts +++ b/site/src/xServices/workspaceBuild/workspaceBuildXService.ts @@ -8,7 +8,7 @@ type LogsContext = { build?: WorkspaceBuild getBuildError?: Error | unknown // Logs - logs?: ProvisionerJobLog[] + logs: ProvisionerJobLog[] } type LogsEvent = @@ -110,10 +110,7 @@ export const workspaceBuildMachine = createMachine( logs: (_, event) => event.data, }), addLog: assign({ - logs: (context, event) => { - const previousLogs = context.logs ?? [] - return [...previousLogs, event.log] - }, + logs: (context, event) => [...context.logs, event.log], }), }, services: { From 79758dab298210c8cbceb9b2bf63a2c2ab466692 Mon Sep 17 00:00:00 2001 From: Bruno Date: Thu, 2 Jun 2022 18:04:19 +0000 Subject: [PATCH 4/9] Fix loader --- .../WorkspaceBuildLogs.stories.tsx | 2 +- .../WorkspaceBuildLogs/WorkspaceBuildLogs.tsx | 8 ++++---- .../WorkspaceBuildPage/WorkspaceBuildPage.tsx | 8 +++++--- .../workspaceBuild/workspaceBuildXService.ts | 15 +++++++++------ 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/site/src/components/WorkspaceBuildLogs/WorkspaceBuildLogs.stories.tsx b/site/src/components/WorkspaceBuildLogs/WorkspaceBuildLogs.stories.tsx index 4c5ef7b7a8c99..05d5bcfe8da30 100644 --- a/site/src/components/WorkspaceBuildLogs/WorkspaceBuildLogs.stories.tsx +++ b/site/src/components/WorkspaceBuildLogs/WorkspaceBuildLogs.stories.tsx @@ -17,5 +17,5 @@ Example.args = { export const Loading = Template.bind({}) Loading.args = { logs: MockWorkspaceBuildLogs, - isLoading: true, + isWaitingForLogs: true, } diff --git a/site/src/components/WorkspaceBuildLogs/WorkspaceBuildLogs.tsx b/site/src/components/WorkspaceBuildLogs/WorkspaceBuildLogs.tsx index 5afb4dac8cb60..8af5f947cd5eb 100644 --- a/site/src/components/WorkspaceBuildLogs/WorkspaceBuildLogs.tsx +++ b/site/src/components/WorkspaceBuildLogs/WorkspaceBuildLogs.tsx @@ -36,10 +36,10 @@ const getStageDurationInSeconds = (logs: ProvisionerJobLog[]) => { export interface WorkspaceBuildLogsProps { logs: ProvisionerJobLog[] - isLoading: boolean + isWaitingForLogs: boolean } -export const WorkspaceBuildLogs: FC = ({ logs, isLoading }) => { +export const WorkspaceBuildLogs: FC = ({ logs, isWaitingForLogs }) => { const groupedLogsByStage = groupLogsByStage(logs) const stages = Object.keys(groupedLogsByStage) const styles = useStyles() @@ -55,8 +55,8 @@ export const WorkspaceBuildLogs: FC = ({ logs, isLoadin })) const duration = getStageDurationInSeconds(logs) const isLastStage = stageIndex === stages.length - 1 - const shouldDisplaySpinner = isLoading && isLastStage - const shouldDisplayDuration = !isLoading && duration + const shouldDisplaySpinner = isWaitingForLogs && isLastStage + const shouldDisplayDuration = !isWaitingForLogs && duration return (
diff --git a/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.tsx b/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.tsx index 700a7240246a6..4617a75d0986e 100644 --- a/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.tsx +++ b/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.tsx @@ -4,6 +4,7 @@ import { useMachine } from "@xstate/react" import { FC } from "react" import { useParams } from "react-router-dom" import { ProvisionerJobLog } from "../../api/typesGenerated" +import { Loader } from "../../components/Loader/Loader" import { Margins } from "../../components/Margins/Margins" import { Stack } from "../../components/Stack/Stack" import { WorkspaceBuildLogs } from "../../components/WorkspaceBuildLogs/WorkspaceBuildLogs" @@ -27,9 +28,9 @@ const useBuildId = () => { export const WorkspaceBuildPage: FC = () => { const buildId = useBuildId() // We can initialize logs as an empty array because it will be a stream - const [buildState] = useMachine(workspaceBuildMachine, { context: { buildId, logs: [] } }) + const [buildState] = useMachine(workspaceBuildMachine, { context: { buildId } }) const { logs, build } = buildState.context - const isLoading = !buildState.matches("loaded") + const isWaitingForLogs = !buildState.matches("logs.loaded") const styles = useStyles() return ( @@ -40,7 +41,8 @@ export const WorkspaceBuildPage: FC = () => { {build && } - + {!logs && } + {logs && } ) diff --git a/site/src/xServices/workspaceBuild/workspaceBuildXService.ts b/site/src/xServices/workspaceBuild/workspaceBuildXService.ts index 137616c6da7d4..924fef3ca93e3 100644 --- a/site/src/xServices/workspaceBuild/workspaceBuildXService.ts +++ b/site/src/xServices/workspaceBuild/workspaceBuildXService.ts @@ -8,7 +8,7 @@ type LogsContext = { build?: WorkspaceBuild getBuildError?: Error | unknown // Logs - logs: ProvisionerJobLog[] + logs?: ProvisionerJobLog[] } type LogsEvent = @@ -78,19 +78,19 @@ export const workspaceBuildMachine = createMachine( src: "streamWorkspaceBuildLogs", }, }, + loaded: { + type: "final", + }, }, on: { ADD_LOG: { actions: "addLog", }, NO_MORE_LOGS: { - target: "loaded", + target: "logs.loaded", }, }, }, - loaded: { - type: "final", - }, }, }, { @@ -110,7 +110,10 @@ export const workspaceBuildMachine = createMachine( logs: (_, event) => event.data, }), addLog: assign({ - logs: (context, event) => [...context.logs, event.log], + logs: (context, event) => { + const previousLogs = context.logs ?? [] + return [...previousLogs, event.log] + }, }), }, services: { From ec8c0880e65ad3115655ba6784a98a5a5cad181b Mon Sep 17 00:00:00 2001 From: Bruno Date: Thu, 2 Jun 2022 18:07:24 +0000 Subject: [PATCH 5/9] Remove unused comment --- site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.tsx b/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.tsx index 4617a75d0986e..2f09b302db7a0 100644 --- a/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.tsx +++ b/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.tsx @@ -27,7 +27,6 @@ const useBuildId = () => { export const WorkspaceBuildPage: FC = () => { const buildId = useBuildId() - // We can initialize logs as an empty array because it will be a stream const [buildState] = useMachine(workspaceBuildMachine, { context: { buildId } }) const { logs, build } = buildState.context const isWaitingForLogs = !buildState.matches("logs.loaded") From 7232e6548fec797b93a6988b1e11cf4bebc1dd15 Mon Sep 17 00:00:00 2001 From: Bruno Date: Thu, 2 Jun 2022 18:19:42 +0000 Subject: [PATCH 6/9] Fix lint --- site/src/xServices/workspaceBuild/workspaceBuildXService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/xServices/workspaceBuild/workspaceBuildXService.ts b/site/src/xServices/workspaceBuild/workspaceBuildXService.ts index 924fef3ca93e3..88008892fe251 100644 --- a/site/src/xServices/workspaceBuild/workspaceBuildXService.ts +++ b/site/src/xServices/workspaceBuild/workspaceBuildXService.ts @@ -123,7 +123,7 @@ export const workspaceBuildMachine = createMachine( const reader = await API.streamWorkspaceBuildLogs(ctx.buildId) // Watching for the stream - // eslint-disable-next-line no-constant-condition + // eslint-disable-next-line no-constant-condition, @typescript-eslint/no-unnecessary-condition while (true) { const { value, done } = await reader.read() From 1041f37ac9e20c2e203977c7f95753455e0c3ffc Mon Sep 17 00:00:00 2001 From: Bruno Date: Thu, 2 Jun 2022 18:21:11 +0000 Subject: [PATCH 7/9] Add definition type in tsconfig test --- site/tsconfig.test.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/tsconfig.test.json b/site/tsconfig.test.json index 416150b4d41fa..4e497e63e8eb6 100644 --- a/site/tsconfig.test.json +++ b/site/tsconfig.test.json @@ -1,5 +1,5 @@ { "extends": "./tsconfig.json", "exclude": ["node_modules", "_jest"], - "include": ["**/*.stories.tsx", "**/*.test.tsx"] + "include": ["**/*.stories.tsx", "**/*.test.tsx", "**/*.d.ts"] } From 3d7acf16a6b29b0133590abb3c72b8a903395ade Mon Sep 17 00:00:00 2001 From: Bruno Date: Fri, 3 Jun 2022 12:49:52 +0000 Subject: [PATCH 8/9] Fix test --- .../WorkspaceBuildPage/WorkspaceBuildPage.test.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.test.tsx b/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.test.tsx index b4f3e3f29e88f..cebff84da793d 100644 --- a/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.test.tsx +++ b/site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.test.tsx @@ -1,9 +1,21 @@ import { screen } from "@testing-library/react" +import * as API from "../../api/api" import { MockWorkspaceBuild, MockWorkspaceBuildLogs, renderWithAuth } from "../../testHelpers/renderHelpers" import { WorkspaceBuildPage } from "./WorkspaceBuildPage" describe("WorkspaceBuildPage", () => { it("renders the stats and logs", async () => { + jest.spyOn(API, "streamWorkspaceBuildLogs").mockResolvedValueOnce({ + read() { + return Promise.resolve({ + value: undefined, + done: true, + }) + }, + releaseLock: jest.fn(), + closed: Promise.resolve(undefined), + cancel: jest.fn(), + }) renderWithAuth(, { route: `/builds/${MockWorkspaceBuild.id}`, path: "/builds/:buildId" }) await screen.findByText(MockWorkspaceBuild.workspace_name) From 27d1020ca8f1b71030097baaeb0398ca75934f93 Mon Sep 17 00:00:00 2001 From: Bruno Date: Fri, 3 Jun 2022 14:14:44 +0000 Subject: [PATCH 9/9] Move seconds to language --- .../WorkspaceBuildLogs/WorkspaceBuildLogs.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/site/src/components/WorkspaceBuildLogs/WorkspaceBuildLogs.tsx b/site/src/components/WorkspaceBuildLogs/WorkspaceBuildLogs.tsx index 8af5f947cd5eb..b5e438c4b97c2 100644 --- a/site/src/components/WorkspaceBuildLogs/WorkspaceBuildLogs.tsx +++ b/site/src/components/WorkspaceBuildLogs/WorkspaceBuildLogs.tsx @@ -6,6 +6,10 @@ import { ProvisionerJobLog } from "../../api/typesGenerated" import { MONOSPACE_FONT_FAMILY } from "../../theme/constants" import { Logs } from "../Logs/Logs" +const Language = { + seconds: "seconds", +} + type Stage = ProvisionerJobLog["stage"] const groupLogsByStage = (logs: ProvisionerJobLog[]) => { @@ -63,7 +67,11 @@ export const WorkspaceBuildLogs: FC = ({ logs, isWaitin
{stage}
{shouldDisplaySpinner && } - {shouldDisplayDuration &&
{duration} seconds
} + {shouldDisplayDuration && ( +
+ {duration} {Language.seconds} +
+ )}
{!isEmpty && }