From 00c6ad64faa568f06aecf0fa6bb9962f29afc057 Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Thu, 30 Mar 2023 18:06:55 +0000 Subject: [PATCH 1/7] Clean up workspace machine --- site/src/components/Workspace/Workspace.tsx | 8 +++++ .../src/pages/WorkspacePage/WorkspacePage.tsx | 26 ++++++-------- .../xServices/workspace/workspaceXService.ts | 36 ++++++------------- 3 files changed, 29 insertions(+), 41 deletions(-) diff --git a/site/src/components/Workspace/Workspace.tsx b/site/src/components/Workspace/Workspace.tsx index d2161fd17cb6a..9e8779a6ad0cd 100644 --- a/site/src/components/Workspace/Workspace.tsx +++ b/site/src/components/Workspace/Workspace.tsx @@ -177,6 +177,14 @@ export const Workspace: FC> = ({ handleUpdate={handleUpdate} /> + {workspace.latest_build.job.error && ( +
+
+ The build failed. See the logs below for more information. +
+
+ )} + {transitionStats !== undefined && ( { - const { username: usernameQueryParam, workspace: workspaceQueryParam } = - useParams() - const username = firstOrItem(usernameQueryParam, null) - const workspaceName = firstOrItem(workspaceQueryParam, null) - const [workspaceState, workspaceSend] = useMachine(workspaceMachine) + const { username, workspace: workspaceName } = useParams() as { + username: string + workspace: string + } + const [workspaceState, workspaceSend] = useMachine(workspaceMachine, { + context: { + workspaceName, + username, + }, + }) const { workspace, getWorkspaceError, @@ -27,16 +31,6 @@ export const WorkspacePage: FC = () => { const { getQuotaError } = quotaState.context const styles = useStyles() - /** - * Get workspace, template, and organization on mount and whenever workspaceId changes. - * workspaceSend should not change. - */ - useEffect(() => { - username && - workspaceName && - workspaceSend({ type: "GET_WORKSPACE", username, workspaceName }) - }, [username, workspaceName, workspaceSend]) - useEffect(() => { username && quotaSend({ type: "GET_QUOTA", username }) }, [username, quotaSend]) diff --git a/site/src/xServices/workspace/workspaceXService.ts b/site/src/xServices/workspace/workspaceXService.ts index 1fc1c543fd78e..093ca8b8379b2 100644 --- a/site/src/xServices/workspace/workspaceXService.ts +++ b/site/src/xServices/workspace/workspaceXService.ts @@ -51,6 +51,9 @@ const Language = { type Permissions = Record, boolean> export interface WorkspaceContext { + // Initial data + username: string + workspaceName: string // our server side events instance eventSource?: EventSource workspace?: TypesGen.Workspace @@ -75,7 +78,6 @@ export interface WorkspaceContext { } export type WorkspaceEvent = - | { type: "GET_WORKSPACE"; workspaceName: string; username: string } | { type: "REFRESH_WORKSPACE"; data: TypesGen.ServerSentEvent["data"] } | { type: "START" } | { type: "STOP" } @@ -164,17 +166,8 @@ export const workspaceMachine = createMachine( } }, }, - initial: "idle", - on: { - GET_WORKSPACE: { - target: ".gettingWorkspace", - internal: false, - }, - }, + initial: "gettingWorkspace", states: { - idle: { - tags: "loading", - }, gettingWorkspace: { entry: ["clearContext"], invoke: { @@ -459,7 +452,7 @@ export const workspaceMachine = createMachine( schedule: { invoke: { id: "scheduleBannerMachine", - src: workspaceScheduleBannerMachine, + src: "scheduleBannerMachine", data: { workspace: (context: WorkspaceContext) => context.workspace, }, @@ -468,11 +461,7 @@ export const workspaceMachine = createMachine( }, }, error: { - on: { - GET_WORKSPACE: { - target: "gettingWorkspace", - }, - }, + type: "final", }, }, }, @@ -613,14 +602,10 @@ export const workspaceMachine = createMachine( }, }, services: { - getWorkspace: async (_, event) => { - return await API.getWorkspaceByOwnerAndName( - event.username, - event.workspaceName, - { - include_deleted: true, - }, - ) + getWorkspace: async ({ username, workspaceName }) => { + return await API.getWorkspaceByOwnerAndName(username, workspaceName, { + include_deleted: true, + }) }, getTemplate: async (context) => { if (context.workspace) { @@ -736,6 +721,7 @@ export const workspaceMachine = createMachine( getApplicationsHost: async () => { return API.getApplicationsHost() }, + scheduleBannerMachine: workspaceScheduleBannerMachine, }, }, ) From 4601bacd6c96093581c6ae34c9addf9ab6e0c27f Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Thu, 30 Mar 2023 18:10:11 +0000 Subject: [PATCH 2/7] Simplify quotas --- site/src/pages/WorkspacePage/WorkspacePage.tsx | 8 ++------ site/src/xServices/quotas/quotasXService.ts | 18 ++++-------------- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/site/src/pages/WorkspacePage/WorkspacePage.tsx b/site/src/pages/WorkspacePage/WorkspacePage.tsx index 8aaee265e707a..ede5aeacc986d 100644 --- a/site/src/pages/WorkspacePage/WorkspacePage.tsx +++ b/site/src/pages/WorkspacePage/WorkspacePage.tsx @@ -3,7 +3,7 @@ import { useMachine } from "@xstate/react" import { AlertBanner } from "components/AlertBanner/AlertBanner" import { ChooseOne, Cond } from "components/Conditionals/ChooseOne" import { Loader } from "components/Loader/Loader" -import { FC, useEffect } from "react" +import { FC } from "react" import { useParams } from "react-router-dom" import { quotaMachine } from "xServices/quotas/quotasXService" import { workspaceMachine } from "xServices/workspace/workspaceXService" @@ -27,14 +27,10 @@ export const WorkspacePage: FC = () => { getTemplateParametersWarning, checkPermissionsError, } = workspaceState.context - const [quotaState, quotaSend] = useMachine(quotaMachine) + const [quotaState] = useMachine(quotaMachine, { context: { username } }) const { getQuotaError } = quotaState.context const styles = useStyles() - useEffect(() => { - username && quotaSend({ type: "GET_QUOTA", username }) - }, [username, quotaSend]) - return ( diff --git a/site/src/xServices/quotas/quotasXService.ts b/site/src/xServices/quotas/quotasXService.ts index 84e22124f65e0..cf6784a64d917 100644 --- a/site/src/xServices/quotas/quotasXService.ts +++ b/site/src/xServices/quotas/quotasXService.ts @@ -3,15 +3,11 @@ import * as API from "../../api/api" import { WorkspaceQuota } from "../../api/typesGenerated" export type QuotaContext = { + username: string quota?: WorkspaceQuota getQuotaError?: Error | unknown } -export type QuotaEvent = { - type: "GET_QUOTA" - username: string -} - export const quotaMachine = createMachine( { id: "quotasMachine", @@ -19,21 +15,15 @@ export const quotaMachine = createMachine( tsTypes: {} as import("./quotasXService.typegen").Typegen0, schema: { context: {} as QuotaContext, - events: {} as QuotaEvent, services: { getQuota: { data: {} as WorkspaceQuota, }, }, }, - context: {}, - initial: "idle", + initial: "gettingQuotas", states: { - idle: { - on: { - GET_QUOTA: "gettingQuotas", - }, - }, + idle: {}, gettingQuotas: { entry: "clearGetQuotaError", invoke: { @@ -67,7 +57,7 @@ export const quotaMachine = createMachine( }), }, services: { - getQuota: (context, event) => API.getWorkspaceQuota(event.username), + getQuota: ({ username }) => API.getWorkspaceQuota(username), }, }, ) From bebc6aac0c6450b5f1d0747b32156509a6db385c Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Thu, 30 Mar 2023 19:36:33 +0000 Subject: [PATCH 3/7] feat(site): Display build error --- site/src/api/api.ts | 16 ++++-- .../components/AlertBanner/AlertBanner.tsx | 17 ++++-- .../AlertBanner/severityConstants.tsx | 10 ++-- site/src/components/Logs/Logs.tsx | 4 +- site/src/components/Workspace/Workspace.tsx | 52 ++++++++++++++++--- .../WorkspaceBuildLogs/WorkspaceBuildLogs.tsx | 6 +++ .../src/pages/WorkspacePage/WorkspacePage.tsx | 24 ++++++++- .../WorkspacePage/WorkspaceReadyPage.tsx | 5 ++ .../xServices/workspace/workspaceXService.ts | 41 +++++++++++++-- 9 files changed, 148 insertions(+), 27 deletions(-) diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 209c4f322ccd7..eb549b6051811 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -489,15 +489,23 @@ export const postWorkspaceBuild = async ( export const startWorkspace = ( workspaceId: string, templateVersionID: string, + debug = false, ) => postWorkspaceBuild(workspaceId, { transition: "start", template_version_id: templateVersionID, + log_level: debug ? "debug" : undefined, + }) +export const stopWorkspace = (workspaceId: string, debug = false) => + postWorkspaceBuild(workspaceId, { + transition: "stop", + log_level: debug ? "debug" : undefined, + }) +export const deleteWorkspace = (workspaceId: string, debug = false) => + postWorkspaceBuild(workspaceId, { + transition: "delete", + log_level: debug ? "debug" : undefined, }) -export const stopWorkspace = (workspaceId: string) => - postWorkspaceBuild(workspaceId, { transition: "stop" }) -export const deleteWorkspace = (workspaceId: string) => - postWorkspaceBuild(workspaceId, { transition: "delete" }) export const cancelWorkspaceBuild = async ( workspaceBuildId: TypesGen.WorkspaceBuild["id"], diff --git a/site/src/components/AlertBanner/AlertBanner.tsx b/site/src/components/AlertBanner/AlertBanner.tsx index 05cd96a328d64..937d0d5b6a5d1 100644 --- a/site/src/components/AlertBanner/AlertBanner.tsx +++ b/site/src/components/AlertBanner/AlertBanner.tsx @@ -60,9 +60,14 @@ export const AlertBanner: FC> = ({ spacing={0} justifyContent="space-between" > - + {severityConstants[severity].icon} - + {children} {alertMessage} {detail && ( @@ -94,11 +99,11 @@ const useStyles = makeStyles((theme) => ({ borderColor: severityConstants[props.severity].color, border: `1px solid ${colors.orange[7]}`, borderRadius: theme.shape.borderRadius, - padding: `${theme.spacing(1)}px ${theme.spacing(2)}px`, + padding: theme.spacing(2), backgroundColor: `${colors.gray[16]}`, textAlign: "left", - "& span": { + "& > span": { paddingTop: `${theme.spacing(0.25)}px`, }, @@ -108,4 +113,8 @@ const useStyles = makeStyles((theme) => ({ marginRight: `${theme.spacing(1)}px`, }, }), + + fullWidth: { + width: "100%", + }, })) diff --git a/site/src/components/AlertBanner/severityConstants.tsx b/site/src/components/AlertBanner/severityConstants.tsx index fcc5e3cc0c89f..bb8dfc21e04aa 100644 --- a/site/src/components/AlertBanner/severityConstants.tsx +++ b/site/src/components/AlertBanner/severityConstants.tsx @@ -13,8 +13,7 @@ export const severityConstants: Record< color: colors.orange[7], icon: ( ), }, @@ -22,15 +21,12 @@ export const severityConstants: Record< color: colors.red[7], icon: ( ), }, info: { color: colors.blue[7], - icon: ( - - ), + icon: , }, } diff --git a/site/src/components/Logs/Logs.tsx b/site/src/components/Logs/Logs.tsx index c85c47e5d5cde..e3d82488a0a5d 100644 --- a/site/src/components/Logs/Logs.tsx +++ b/site/src/components/Logs/Logs.tsx @@ -93,7 +93,7 @@ const useStyles = makeStyles< background: theme.palette.background.default, }, scrollWrapper: { - width: "fit-content", + minWidth: "fit-content", }, line: { wordBreak: "break-all", @@ -109,7 +109,7 @@ const useStyles = makeStyles< }, "&.debug": { - backgroundColor: theme.palette.grey[900], + backgroundColor: theme.palette.background.paperLight, }, "&.warn": { diff --git a/site/src/components/Workspace/Workspace.tsx b/site/src/components/Workspace/Workspace.tsx index 9e8779a6ad0cd..4ef4e37afeaaf 100644 --- a/site/src/components/Workspace/Workspace.tsx +++ b/site/src/components/Workspace/Workspace.tsx @@ -1,6 +1,9 @@ +import Button from "@material-ui/core/Button" import { makeStyles } from "@material-ui/core/styles" +import RefreshOutlined from "@material-ui/icons/RefreshOutlined" import { Avatar } from "components/Avatar/Avatar" import { AgentRow } from "components/Resources/AgentRow" +import { WorkspaceBuildLogs } from "components/WorkspaceBuildLogs/WorkspaceBuildLogs" import { ActiveTransition, WorkspaceBuildProgress, @@ -55,6 +58,8 @@ export interface WorkspaceProps { applicationsHost?: string template?: TypesGen.Template quota_budget?: number + failedBuildLogs: TypesGen.ProvisionerJobLog[] | undefined + handleBuildRetry: () => void } /** @@ -80,6 +85,8 @@ export const Workspace: FC> = ({ applicationsHost, template, quota_budget, + failedBuildLogs, + handleBuildRetry, }) => { const styles = useStyles() const navigate = useNavigate() @@ -177,12 +184,36 @@ export const Workspace: FC> = ({ handleUpdate={handleUpdate} /> - {workspace.latest_build.job.error && ( -
-
- The build failed. See the logs below for more information. -
-
+ {failedBuildLogs && ( + + + + + Workspace build failed + + {workspace.latest_build.job.error} + + + +
+ +
+
+
+ +
)} {transitionStats !== undefined && ( @@ -260,5 +291,14 @@ export const useStyles = makeStyles((theme) => { logs: { border: `1px solid ${theme.palette.divider}`, }, + + errorDetails: { + color: theme.palette.text.secondary, + fontSize: 12, + }, + + fullWidth: { + width: "100%", + }, } }) diff --git a/site/src/components/WorkspaceBuildLogs/WorkspaceBuildLogs.tsx b/site/src/components/WorkspaceBuildLogs/WorkspaceBuildLogs.tsx index 7ba82f2b7075f..d64d54c94a1cf 100644 --- a/site/src/components/WorkspaceBuildLogs/WorkspaceBuildLogs.tsx +++ b/site/src/components/WorkspaceBuildLogs/WorkspaceBuildLogs.tsx @@ -104,6 +104,7 @@ const useStyles = makeStyles< padding: theme.spacing(2), paddingLeft: theme.spacing(3), paddingRight: theme.spacing(3), + borderTop: `1px solid ${theme.palette.divider}`, borderBottom: `1px solid ${theme.palette.divider}`, backgroundColor: theme.palette.background.paper, display: "flex", @@ -113,6 +114,7 @@ const useStyles = makeStyles< "&:first-child": { borderTopLeftRadius: theme.shape.borderRadius, borderTopRightRadius: theme.shape.borderRadius, + borderTop: 0, }, "&:last-child": { @@ -121,6 +123,10 @@ const useStyles = makeStyles< borderBottomLeftRadius: theme.shape.borderRadius, borderBottomRightRadius: theme.shape.borderRadius, }, + + "& + $header": { + borderTop: 0, + }, }, duration: { diff --git a/site/src/pages/WorkspacePage/WorkspacePage.tsx b/site/src/pages/WorkspacePage/WorkspacePage.tsx index ede5aeacc986d..6340e19986b1e 100644 --- a/site/src/pages/WorkspacePage/WorkspacePage.tsx +++ b/site/src/pages/WorkspacePage/WorkspacePage.tsx @@ -1,14 +1,34 @@ import { makeStyles } from "@material-ui/core/styles" +import { useQuery } from "@tanstack/react-query" import { useMachine } from "@xstate/react" +import { getWorkspaceBuildLogs } from "api/api" +import { Workspace } from "api/typesGenerated" import { AlertBanner } from "components/AlertBanner/AlertBanner" import { ChooseOne, Cond } from "components/Conditionals/ChooseOne" import { Loader } from "components/Loader/Loader" -import { FC } from "react" +import { FC, useRef } from "react" import { useParams } from "react-router-dom" import { quotaMachine } from "xServices/quotas/quotasXService" import { workspaceMachine } from "xServices/workspace/workspaceXService" import { WorkspaceReadyPage } from "./WorkspaceReadyPage" +const useFailedBuildLogs = (workspace: Workspace | undefined) => { + const now = useRef(new Date()) + return useQuery({ + queryKey: ["logs", workspace?.latest_build.id], + queryFn: () => { + if (!workspace) { + throw new Error( + `Build log query being called before workspace is defined`, + ) + } + + return getWorkspaceBuildLogs(workspace.latest_build.id, now.current) + }, + enabled: workspace?.latest_build.job.error !== undefined, + }) +} + export const WorkspacePage: FC = () => { const { username, workspace: workspaceName } = useParams() as { username: string @@ -30,6 +50,7 @@ export const WorkspacePage: FC = () => { const [quotaState] = useMachine(quotaMachine, { context: { username } }) const { getQuotaError } = quotaState.context const styles = useStyles() + const failedBuildLogs = useFailedBuildLogs(workspace) return ( @@ -66,6 +87,7 @@ export const WorkspacePage: FC = () => { workspaceState={workspaceState} quotaState={quotaState} workspaceSend={workspaceSend} + failedBuildLogs={failedBuildLogs.data} />
diff --git a/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx b/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx index 88a614df0b234..7189893b0909e 100644 --- a/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx @@ -1,4 +1,5 @@ import { useActor } from "@xstate/react" +import { ProvisionerJobLog } from "api/typesGenerated" import { useDashboard } from "components/Dashboard/DashboardProvider" import dayjs from "dayjs" import { useFeatureVisibility } from "hooks/useFeatureVisibility" @@ -31,11 +32,13 @@ interface WorkspaceReadyPageProps { workspaceState: StateFrom quotaState: StateFrom workspaceSend: (event: WorkspaceEvent) => void + failedBuildLogs: ProvisionerJobLog[] | undefined } export const WorkspaceReadyPage = ({ workspaceState, quotaState, + failedBuildLogs, workspaceSend, }: WorkspaceReadyPageProps): JSX.Element => { const [_, bannerSend] = useActor( @@ -85,6 +88,7 @@ export const WorkspaceReadyPage = ({ { bannerSend({ @@ -112,6 +116,7 @@ export const WorkspaceReadyPage = ({ handleUpdate={() => workspaceSend({ type: "UPDATE" })} handleCancel={() => workspaceSend({ type: "CANCEL" })} handleSettings={() => navigate("settings")} + handleBuildRetry={() => workspaceSend({ type: "RETRY_BUILD" })} resources={workspace.latest_build.resources} builds={builds} canUpdateWorkspace={canUpdateWorkspace} diff --git a/site/src/xServices/workspace/workspaceXService.ts b/site/src/xServices/workspace/workspaceXService.ts index 093ca8b8379b2..ed13c9c6e45f7 100644 --- a/site/src/xServices/workspace/workspaceXService.ts +++ b/site/src/xServices/workspace/workspaceXService.ts @@ -75,6 +75,8 @@ export interface WorkspaceContext { checkPermissionsError?: Error | unknown // applications applicationsHost?: string + // debug + debugMode?: boolean } export type WorkspaceEvent = @@ -94,6 +96,7 @@ export type WorkspaceEvent = | { type: "EVENT_SOURCE_ERROR"; error: Error | unknown } | { type: "INCREASE_DEADLINE"; hours: number } | { type: "DECREASE_DEADLINE"; hours: number } + | { type: "RETRY_BUILD" } export const checks = { readWorkspace: "readWorkspace", @@ -271,6 +274,23 @@ export const workspaceMachine = createMachine( ASK_DELETE: "askingDelete", UPDATE: "requestingUpdate", CANCEL: "requestingCancel", + RETRY_BUILD: [ + { + target: "requestingStart", + cond: "lastBuildWasStarting", + actions: ["enableDebugMode"], + }, + { + target: "requestingStop", + cond: "lastBuildWasStopping", + actions: ["enableDebugMode"], + }, + { + target: "requestingDelete", + cond: "lastBuildWasDeleting", + actions: ["enableDebugMode"], + }, + ], }, }, askingDelete: { @@ -317,7 +337,7 @@ export const workspaceMachine = createMachine( id: "startWorkspace", onDone: [ { - actions: ["assignBuild"], + actions: ["assignBuild", "disableDebugMode"], target: "idle", }, ], @@ -336,7 +356,7 @@ export const workspaceMachine = createMachine( id: "stopWorkspace", onDone: [ { - actions: ["assignBuild"], + actions: ["assignBuild", "disableDebugMode"], target: "idle", }, ], @@ -355,7 +375,7 @@ export const workspaceMachine = createMachine( id: "deleteWorkspace", onDone: [ { - actions: ["assignBuild"], + actions: ["assignBuild", "disableDebugMode"], target: "idle", }, ], @@ -594,12 +614,24 @@ export const workspaceMachine = createMachine( return data.parameters }, }), + // Debug mode when build fails + enableDebugMode: assign({ debugMode: (_) => true }), + disableDebugMode: assign({ debugMode: (_) => false }), }, guards: { moreBuildsAvailable, isMissingBuildParameterError: (_, { data }) => { return data instanceof API.MissingBuildParameters }, + lastBuildWasStarting: ({ workspace }) => { + return workspace?.latest_build.transition === "start" + }, + lastBuildWasStopping: ({ workspace }) => { + return workspace?.latest_build.transition === "stop" + }, + lastBuildWasDeleting: ({ workspace }) => { + return workspace?.latest_build.transition === "delete" + }, }, services: { getWorkspace: async ({ username, workspaceName }) => { @@ -629,6 +661,7 @@ export const workspaceMachine = createMachine( const startWorkspacePromise = await API.startWorkspace( context.workspace.id, context.workspace.latest_build.template_version_id, + context.debugMode, ) send({ type: "REFRESH_TIMELINE" }) return startWorkspacePromise @@ -640,6 +673,7 @@ export const workspaceMachine = createMachine( if (context.workspace) { const stopWorkspacePromise = await API.stopWorkspace( context.workspace.id, + context.debugMode, ) send({ type: "REFRESH_TIMELINE" }) return stopWorkspacePromise @@ -651,6 +685,7 @@ export const workspaceMachine = createMachine( if (context.workspace) { const deleteWorkspacePromise = await API.deleteWorkspace( context.workspace.id, + context.debugMode, ) send({ type: "REFRESH_TIMELINE" }) return deleteWorkspacePromise From 734775c653b3d88cff4218cd5841e15ff3cc701e Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Fri, 31 Mar 2023 12:51:45 +0000 Subject: [PATCH 4/7] Pass debug level instead of debug flag --- site/src/api/api.ts | 18 ++++++++++++------ .../xServices/workspace/workspaceXService.ts | 12 ++++++------ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/site/src/api/api.ts b/site/src/api/api.ts index eb549b6051811..190ec4baae23b 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -489,22 +489,28 @@ export const postWorkspaceBuild = async ( export const startWorkspace = ( workspaceId: string, templateVersionID: string, - debug = false, + logLevel?: TypesGen.CreateWorkspaceBuildRequest["log_level"], ) => postWorkspaceBuild(workspaceId, { transition: "start", template_version_id: templateVersionID, - log_level: debug ? "debug" : undefined, + log_level: logLevel, }) -export const stopWorkspace = (workspaceId: string, debug = false) => +export const stopWorkspace = ( + workspaceId: string, + logLevel?: TypesGen.CreateWorkspaceBuildRequest["log_level"], +) => postWorkspaceBuild(workspaceId, { transition: "stop", - log_level: debug ? "debug" : undefined, + log_level: logLevel, }) -export const deleteWorkspace = (workspaceId: string, debug = false) => +export const deleteWorkspace = ( + workspaceId: string, + logLevel?: TypesGen.CreateWorkspaceBuildRequest["log_level"], +) => postWorkspaceBuild(workspaceId, { transition: "delete", - log_level: debug ? "debug" : undefined, + log_level: logLevel, }) export const cancelWorkspaceBuild = async ( diff --git a/site/src/xServices/workspace/workspaceXService.ts b/site/src/xServices/workspace/workspaceXService.ts index ed13c9c6e45f7..dec7e3bc1842f 100644 --- a/site/src/xServices/workspace/workspaceXService.ts +++ b/site/src/xServices/workspace/workspaceXService.ts @@ -76,7 +76,7 @@ export interface WorkspaceContext { // applications applicationsHost?: string // debug - debugMode?: boolean + createBuildLogLevel?: TypesGen.CreateWorkspaceBuildRequest["log_level"] } export type WorkspaceEvent = @@ -615,8 +615,8 @@ export const workspaceMachine = createMachine( }, }), // Debug mode when build fails - enableDebugMode: assign({ debugMode: (_) => true }), - disableDebugMode: assign({ debugMode: (_) => false }), + enableDebugMode: assign({ createBuildLogLevel: (_) => "debug" as const }), + disableDebugMode: assign({ createBuildLogLevel: (_) => undefined }), }, guards: { moreBuildsAvailable, @@ -661,7 +661,7 @@ export const workspaceMachine = createMachine( const startWorkspacePromise = await API.startWorkspace( context.workspace.id, context.workspace.latest_build.template_version_id, - context.debugMode, + context.createBuildLogLevel, ) send({ type: "REFRESH_TIMELINE" }) return startWorkspacePromise @@ -673,7 +673,7 @@ export const workspaceMachine = createMachine( if (context.workspace) { const stopWorkspacePromise = await API.stopWorkspace( context.workspace.id, - context.debugMode, + context.createBuildLogLevel, ) send({ type: "REFRESH_TIMELINE" }) return stopWorkspacePromise @@ -685,7 +685,7 @@ export const workspaceMachine = createMachine( if (context.workspace) { const deleteWorkspacePromise = await API.deleteWorkspace( context.workspace.id, - context.debugMode, + context.createBuildLogLevel, ) send({ type: "REFRESH_TIMELINE" }) return deleteWorkspacePromise From 97fe930436ca683f534bc4c3398c1a9f8a67d337 Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Fri, 31 Mar 2023 13:14:59 +0000 Subject: [PATCH 5/7] Only show try again if user can update it --- site/src/components/Workspace/Workspace.tsx | 22 +++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/site/src/components/Workspace/Workspace.tsx b/site/src/components/Workspace/Workspace.tsx index 4ef4e37afeaaf..8577d5815a6ac 100644 --- a/site/src/components/Workspace/Workspace.tsx +++ b/site/src/components/Workspace/Workspace.tsx @@ -200,16 +200,18 @@ export const Workspace: FC> = ({ -
- -
+ {canUpdateWorkspace && ( +
+ +
+ )} From b3e0132cb0523c69956cbe05f0312bd109fae62c Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Fri, 31 Mar 2023 13:31:05 +0000 Subject: [PATCH 6/7] Fixed permissions and storybook --- .../Workspace/Workspace.stories.tsx | 509 ++++++++++++++++++ site/src/components/Workspace/Workspace.tsx | 4 +- .../WorkspacePage/WorkspaceReadyPage.tsx | 2 + .../xServices/workspace/workspaceXService.ts | 29 +- 4 files changed, 535 insertions(+), 9 deletions(-) diff --git a/site/src/components/Workspace/Workspace.stories.tsx b/site/src/components/Workspace/Workspace.stories.tsx index 9e97424f016be..a6fb225a20a7d 100644 --- a/site/src/components/Workspace/Workspace.stories.tsx +++ b/site/src/components/Workspace/Workspace.stories.tsx @@ -1,5 +1,6 @@ import { action } from "@storybook/addon-actions" import { Story } from "@storybook/react" +import { ProvisionerJobLog } from "api/typesGenerated" import * as Mocks from "../../testHelpers/entities" import { Workspace, WorkspaceErrors, WorkspaceProps } from "./Workspace" @@ -73,6 +74,23 @@ Failed.args = { }, } +export const FailedWithLogs = Template.bind({}) +FailedWithLogs.args = { + ...Running.args, + workspace: { + ...Mocks.MockFailedWorkspace, + latest_build: { + ...Mocks.MockFailedWorkspace.latest_build, + job: { + ...Mocks.MockFailedWorkspace.latest_build.job, + error: + "recv workspace provision: plan terraform: terraform plan: exit status 1", + }, + }, + }, + failedBuildLogs: makeFailedBuildLogs(), +} + export const Deleting = Template.bind({}) Deleting.args = { ...Running.args, @@ -122,3 +140,494 @@ CancellationError.args = { }), }, } + +function makeFailedBuildLogs(): ProvisionerJobLog[] { + return [ + { + id: 2362, + created_at: "2023-03-21T15:57:42.637Z", + log_source: "provisioner_daemon", + log_level: "info", + stage: "Setting up", + output: "", + }, + { + id: 2363, + created_at: "2023-03-21T15:57:42.674Z", + log_source: "provisioner", + log_level: "debug", + stage: "Planning infrastructure", + output: "", + }, + { + id: 2364, + created_at: "2023-03-21T15:57:42.674Z", + log_source: "provisioner", + log_level: "debug", + stage: "Planning infrastructure", + output: "Initializing the backend...", + }, + { + id: 2365, + created_at: "2023-03-21T15:57:42.674Z", + log_source: "provisioner", + log_level: "debug", + stage: "Planning infrastructure", + output: "", + }, + { + id: 2366, + created_at: "2023-03-21T15:57:42.674Z", + log_source: "provisioner", + log_level: "debug", + stage: "Planning infrastructure", + output: "Initializing provider plugins...", + }, + { + id: 2367, + created_at: "2023-03-21T15:57:42.674Z", + log_source: "provisioner", + log_level: "debug", + stage: "Planning infrastructure", + output: '- Finding coder/coder versions matching "~\u003e 0.6.17"...', + }, + { + id: 2368, + created_at: "2023-03-21T15:57:42.84Z", + log_source: "provisioner", + log_level: "debug", + stage: "Planning infrastructure", + output: + '- Finding kreuzwerker/docker versions matching "~\u003e 3.0.1"...', + }, + { + id: 2369, + created_at: "2023-03-21T15:57:42.986Z", + log_source: "provisioner", + log_level: "debug", + stage: "Planning infrastructure", + output: + "- Using kreuzwerker/docker v3.0.2 from the shared cache directory", + }, + { + id: 2370, + created_at: "2023-03-21T15:57:43.03Z", + log_source: "provisioner", + log_level: "debug", + stage: "Planning infrastructure", + output: "- Using coder/coder v0.6.20 from the shared cache directory", + }, + { + id: 2371, + created_at: "2023-03-21T15:57:43.059Z", + log_source: "provisioner", + log_level: "debug", + stage: "Planning infrastructure", + output: "", + }, + { + id: 2372, + created_at: "2023-03-21T15:57:43.059Z", + log_source: "provisioner", + log_level: "debug", + stage: "Planning infrastructure", + output: + "Terraform has created a lock file .terraform.lock.hcl to record the provider", + }, + { + id: 2373, + created_at: "2023-03-21T15:57:43.059Z", + log_source: "provisioner", + log_level: "debug", + stage: "Planning infrastructure", + output: + "selections it made above. Include this file in your version control repository", + }, + { + id: 2374, + created_at: "2023-03-21T15:57:43.059Z", + log_source: "provisioner", + log_level: "debug", + stage: "Planning infrastructure", + output: + "so that Terraform can guarantee to make the same selections by default when", + }, + { + id: 2375, + created_at: "2023-03-21T15:57:43.059Z", + log_source: "provisioner", + log_level: "debug", + stage: "Planning infrastructure", + output: 'you run "terraform init" in the future.', + }, + { + id: 2376, + created_at: "2023-03-21T15:57:43.059Z", + log_source: "provisioner", + log_level: "debug", + stage: "Planning infrastructure", + output: "", + }, + { + id: 2377, + created_at: "2023-03-21T15:57:43.059Z", + log_source: "provisioner", + log_level: "debug", + stage: "Planning infrastructure", + output: "", + }, + { + id: 2378, + created_at: "2023-03-21T15:57:43.059Z", + log_source: "provisioner", + log_level: "debug", + stage: "Planning infrastructure", + output: "Warning: Incomplete lock file information for providers", + }, + { + id: 2379, + created_at: "2023-03-21T15:57:43.059Z", + log_source: "provisioner", + log_level: "debug", + stage: "Planning infrastructure", + output: "", + }, + { + id: 2380, + created_at: "2023-03-21T15:57:43.059Z", + log_source: "provisioner", + log_level: "debug", + stage: "Planning infrastructure", + output: + "Due to your customized provider installation methods, Terraform was forced to", + }, + { + id: 2381, + created_at: "2023-03-21T15:57:43.059Z", + log_source: "provisioner", + log_level: "debug", + stage: "Planning infrastructure", + output: + "calculate lock file checksums locally for the following providers:", + }, + { + id: 2382, + created_at: "2023-03-21T15:57:43.059Z", + log_source: "provisioner", + log_level: "debug", + stage: "Planning infrastructure", + output: " - coder/coder", + }, + { + id: 2383, + created_at: "2023-03-21T15:57:43.059Z", + log_source: "provisioner", + log_level: "debug", + stage: "Planning infrastructure", + output: " - kreuzwerker/docker", + }, + { + id: 2384, + created_at: "2023-03-21T15:57:43.059Z", + log_source: "provisioner", + log_level: "debug", + stage: "Planning infrastructure", + output: "", + }, + { + id: 2385, + created_at: "2023-03-21T15:57:43.059Z", + log_source: "provisioner", + log_level: "debug", + stage: "Planning infrastructure", + output: + "The current .terraform.lock.hcl file only includes checksums for linux_amd64,", + }, + { + id: 2386, + created_at: "2023-03-21T15:57:43.059Z", + log_source: "provisioner", + log_level: "debug", + stage: "Planning infrastructure", + output: + "so Terraform running on another platform will fail to install these", + }, + { + id: 2387, + created_at: "2023-03-21T15:57:43.059Z", + log_source: "provisioner", + log_level: "debug", + stage: "Planning infrastructure", + output: "providers.", + }, + { + id: 2388, + created_at: "2023-03-21T15:57:43.059Z", + log_source: "provisioner", + log_level: "debug", + stage: "Planning infrastructure", + output: "", + }, + { + id: 2389, + created_at: "2023-03-21T15:57:43.059Z", + log_source: "provisioner", + log_level: "debug", + stage: "Planning infrastructure", + output: "To calculate additional checksums for another platform, run:", + }, + { + id: 2390, + created_at: "2023-03-21T15:57:43.059Z", + log_source: "provisioner", + log_level: "debug", + stage: "Planning infrastructure", + output: " terraform providers lock -platform=linux_amd64", + }, + { + id: 2391, + created_at: "2023-03-21T15:57:43.059Z", + log_source: "provisioner", + log_level: "debug", + stage: "Planning infrastructure", + output: "(where linux_amd64 is the platform to generate)", + }, + { + id: 2392, + created_at: "2023-03-21T15:57:43.059Z", + log_source: "provisioner", + log_level: "debug", + stage: "Planning infrastructure", + output: "", + }, + { + id: 2393, + created_at: "2023-03-21T15:57:43.059Z", + log_source: "provisioner", + log_level: "debug", + stage: "Planning infrastructure", + output: "Terraform has been successfully initialized!", + }, + { + id: 2394, + created_at: "2023-03-21T15:57:43.059Z", + log_source: "provisioner", + log_level: "debug", + stage: "Planning infrastructure", + output: "", + }, + { + id: 2395, + created_at: "2023-03-21T15:57:43.059Z", + log_source: "provisioner", + log_level: "debug", + stage: "Planning infrastructure", + output: + 'You may now begin working with Terraform. Try running "terraform plan" to see', + }, + { + id: 2396, + created_at: "2023-03-21T15:57:43.059Z", + log_source: "provisioner", + log_level: "debug", + stage: "Planning infrastructure", + output: + "any changes that are required for your infrastructure. All Terraform commands", + }, + { + id: 2397, + created_at: "2023-03-21T15:57:43.059Z", + log_source: "provisioner", + log_level: "debug", + stage: "Planning infrastructure", + output: "should now work.", + }, + { + id: 2398, + created_at: "2023-03-21T15:57:43.059Z", + log_source: "provisioner", + log_level: "debug", + stage: "Planning infrastructure", + output: "", + }, + { + id: 2399, + created_at: "2023-03-21T15:57:43.059Z", + log_source: "provisioner", + log_level: "debug", + stage: "Planning infrastructure", + output: + "If you ever set or change modules or backend configuration for Terraform,", + }, + { + id: 2400, + created_at: "2023-03-21T15:57:43.059Z", + log_source: "provisioner", + log_level: "debug", + stage: "Planning infrastructure", + output: + "rerun this command to reinitialize your working directory. If you forget, other", + }, + { + id: 2401, + created_at: "2023-03-21T15:57:43.059Z", + log_source: "provisioner", + log_level: "debug", + stage: "Planning infrastructure", + output: "commands will detect it and remind you to do so if necessary.", + }, + { + id: 2402, + created_at: "2023-03-21T15:57:43.078Z", + log_source: "provisioner", + log_level: "info", + stage: "Planning infrastructure", + output: "Terraform 1.3.4", + }, + { + id: 2403, + created_at: "2023-03-21T15:57:43.401Z", + log_source: "provisioner", + log_level: "info", + stage: "Planning infrastructure", + output: "data.coder_provisioner.me: Refreshing...", + }, + { + id: 2404, + created_at: "2023-03-21T15:57:43.402Z", + log_source: "provisioner", + log_level: "info", + stage: "Planning infrastructure", + output: "data.coder_workspace.me: Refreshing...", + }, + { + id: 2405, + created_at: "2023-03-21T15:57:43.402Z", + log_source: "provisioner", + log_level: "info", + stage: "Planning infrastructure", + output: "data.coder_parameter.security_groups: Refreshing...", + }, + { + id: 2406, + created_at: "2023-03-21T15:57:43.402Z", + log_source: "provisioner", + log_level: "info", + stage: "Planning infrastructure", + output: + "data.coder_provisioner.me: Refresh complete after 0s [id=993f697b-3948-4d31-8377-6c86edc90a83]", + }, + { + id: 2407, + created_at: "2023-03-21T15:57:43.403Z", + log_source: "provisioner", + log_level: "info", + stage: "Planning infrastructure", + output: + "data.coder_workspace.me: Refresh complete after 0s [id=ca18ddca-14b5-4f5f-be55-7bfd2e3c2dc9]", + }, + { + id: 2408, + created_at: "2023-03-21T15:57:43.403Z", + log_source: "provisioner", + log_level: "info", + stage: "Planning infrastructure", + output: + "data.coder_parameter.security_groups: Refresh complete after 0s [id=9832a15f-267b-4abf-9c23-e4265af0befa]", + }, + { + id: 2409, + created_at: "2023-03-21T15:57:43.405Z", + log_source: "provisioner", + log_level: "info", + stage: "Planning infrastructure", + output: + "coder_agent.main: Refreshing state... [id=6c3718cb-605b-4b68-b26f-46dba8767f43]", + }, + { + id: 2410, + created_at: "2023-03-21T15:57:43.406Z", + log_source: "provisioner", + log_level: "info", + stage: "Planning infrastructure", + output: + "coder_agent.main: Refresh complete [id=6c3718cb-605b-4b68-b26f-46dba8767f43]", + }, + { + id: 2411, + created_at: "2023-03-21T15:57:43.407Z", + log_source: "provisioner", + log_level: "info", + stage: "Planning infrastructure", + output: + "docker_volume.home_volume: Refreshing state... [id=coder-ca18ddca-14b5-4f5f-be55-7bfd2e3c2dc9-home]", + }, + { + id: 2412, + created_at: "2023-03-21T15:57:43.41Z", + log_source: "provisioner", + log_level: "info", + stage: "Planning infrastructure", + output: + "coder_app.code-server: Refreshing state... [id=4a45a1cc-9861-4a9c-bd2f-3a2f1abc4c65]", + }, + { + id: 2413, + created_at: "2023-03-21T15:57:43.411Z", + log_source: "provisioner", + log_level: "info", + stage: "Planning infrastructure", + output: + "coder_app.code-server: Refresh complete [id=4a45a1cc-9861-4a9c-bd2f-3a2f1abc4c65]", + }, + { + id: 2414, + created_at: "2023-03-21T15:57:43.417Z", + log_source: "provisioner", + log_level: "error", + stage: "Planning infrastructure", + output: + "Error: Unable to inspect volume: Error: No such volume: coder-ca18ddca-14b5-4f5f-be55-7bfd2e3c2dc9-home", + }, + { + id: 2415, + created_at: "2023-03-21T15:57:43.418Z", + log_source: "provisioner", + log_level: "error", + stage: "Planning infrastructure", + output: 'on main.tf line 61, in resource "docker_volume" "home_volume":', + }, + { + id: 2416, + created_at: "2023-03-21T15:57:43.418Z", + log_source: "provisioner", + log_level: "error", + stage: "Planning infrastructure", + output: ' 61: resource "docker_volume" "home_volume" {', + }, + { + id: 2417, + created_at: "2023-03-21T15:57:43.418Z", + log_source: "provisioner", + log_level: "error", + stage: "Planning infrastructure", + output: "", + }, + { + id: 2418, + created_at: "2023-03-21T15:57:43.419Z", + log_source: "provisioner", + log_level: "error", + stage: "Planning infrastructure", + output: "", + }, + { + id: 2419, + created_at: "2023-03-21T15:57:43.422Z", + log_source: "provisioner_daemon", + log_level: "info", + stage: "Cleaning Up", + output: "", + }, + ] +} diff --git a/site/src/components/Workspace/Workspace.tsx b/site/src/components/Workspace/Workspace.tsx index 8577d5815a6ac..29be91cb5e739 100644 --- a/site/src/components/Workspace/Workspace.tsx +++ b/site/src/components/Workspace/Workspace.tsx @@ -51,6 +51,7 @@ export interface WorkspaceProps { resources?: TypesGen.WorkspaceResource[] builds?: TypesGen.WorkspaceBuild[] canUpdateWorkspace: boolean + canUpdateTemplate: boolean hideSSHButton?: boolean hideVSCodeDesktopButton?: boolean workspaceErrors: Partial> @@ -78,6 +79,7 @@ export const Workspace: FC> = ({ resources, builds, canUpdateWorkspace, + canUpdateTemplate, workspaceErrors, hideSSHButton, hideVSCodeDesktopButton, @@ -200,7 +202,7 @@ export const Workspace: FC> = ({ - {canUpdateWorkspace && ( + {canUpdateTemplate && (
)} diff --git a/site/src/i18n/en/workspacePage.json b/site/src/i18n/en/workspacePage.json index b9b63d1d3d9da..fa5e9f8335021 100644 --- a/site/src/i18n/en/workspacePage.json +++ b/site/src/i18n/en/workspacePage.json @@ -28,7 +28,8 @@ "starting": "Starting...", "stopping": "Stopping...", "deleting": "Deleting...", - "settings": "Settings" + "settings": "Settings", + "retryDebugMode": "Try again in debug mode" }, "disabledButton": { "canceling": "Canceling",