Skip to content

Commit d896b74

Browse files
feat(site): display build logs on workspace transitioning statuses (coder#8397)
1 parent b7641b2 commit d896b74

File tree

20 files changed

+426
-133
lines changed

20 files changed

+426
-133
lines changed

coderd/apidoc/docs.go

+4-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

+4-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

codersdk/deployment.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -1763,6 +1763,7 @@ const (
17631763
// oidc.
17641764
ExperimentConvertToOIDC Experiment = "convert-to-oidc"
17651765

1766+
ExperimentWorkspaceBuildLogsUI Experiment = "workspace_build_logs_ui"
17661767
// Add new experiments here!
17671768
// ExperimentExample Experiment = "example"
17681769
)
@@ -1771,7 +1772,9 @@ const (
17711772
// users to opt-in to via --experimental='*'.
17721773
// Experiments that are not ready for consumption by all users should
17731774
// not be included here and will be essentially hidden.
1774-
var ExperimentsAll = Experiments{}
1775+
var ExperimentsAll = Experiments{
1776+
ExperimentWorkspaceBuildLogsUI,
1777+
}
17751778

17761779
// Experiments is a list of experiments that are enabled for the deployment.
17771780
// Multiple experiments may be enabled at the same time.

docs/api/schemas.md

+7-6
Original file line numberDiff line numberDiff line change
@@ -2531,12 +2531,13 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
25312531

25322532
#### Enumerated Values
25332533

2534-
| Value |
2535-
| ------------------------ |
2536-
| `moons` |
2537-
| `workspace_actions` |
2538-
| `tailnet_pg_coordinator` |
2539-
| `convert-to-oidc` |
2534+
| Value |
2535+
| ------------------------- |
2536+
| `moons` |
2537+
| `workspace_actions` |
2538+
| `tailnet_pg_coordinator` |
2539+
| `convert-to-oidc` |
2540+
| `workspace_build_logs_ui` |
25402541

25412542
## codersdk.Feature
25422543

site/.storybook/preview.jsx

+8
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { HelmetProvider } from "react-helmet-async"
55
import { dark } from "../src/theme"
66
import "../src/theme/globalFonts"
77
import "../src/i18n"
8+
import { LocalPreferencesProvider } from "../src/contexts/LocalPreferencesContext"
89

910
export const decorators = [
1011
(Story) => (
@@ -23,6 +24,13 @@ export const decorators = [
2324
</HelmetProvider>
2425
)
2526
},
27+
(Story) => {
28+
return (
29+
<LocalPreferencesProvider>
30+
<Story />
31+
</LocalPreferencesProvider>
32+
)
33+
},
2634
]
2735

2836
export const parameters = {

site/src/api/typesGenerated.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1431,11 +1431,13 @@ export type Experiment =
14311431
| "moons"
14321432
| "tailnet_pg_coordinator"
14331433
| "workspace_actions"
1434+
| "workspace_build_logs_ui"
14341435
export const Experiments: Experiment[] = [
14351436
"convert-to-oidc",
14361437
"moons",
14371438
"tailnet_pg_coordinator",
14381439
"workspace_actions",
1440+
"workspace_build_logs_ui",
14391441
]
14401442

14411443
// From codersdk/deployment.go

site/src/app.tsx

+14-11
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { GlobalSnackbar } from "./components/GlobalSnackbar/GlobalSnackbar"
99
import { dark } from "./theme"
1010
import "./theme/globalFonts"
1111
import { StyledEngineProvider, ThemeProvider } from "@mui/material/styles"
12+
import { LocalPreferencesProvider } from "contexts/LocalPreferencesContext"
1213

1314
const queryClient = new QueryClient({
1415
defaultOptions: {
@@ -25,17 +26,19 @@ export const AppProviders: FC<PropsWithChildren> = ({ children }) => {
2526
return (
2627
<HelmetProvider>
2728
<StyledEngineProvider injectFirst>
28-
<ThemeProvider theme={dark}>
29-
<CssBaseline enableColorScheme />
30-
<ErrorBoundary>
31-
<QueryClientProvider client={queryClient}>
32-
<AuthProvider>
33-
{children}
34-
<GlobalSnackbar />
35-
</AuthProvider>
36-
</QueryClientProvider>
37-
</ErrorBoundary>
38-
</ThemeProvider>
29+
<LocalPreferencesProvider>
30+
<ThemeProvider theme={dark}>
31+
<CssBaseline enableColorScheme />
32+
<ErrorBoundary>
33+
<QueryClientProvider client={queryClient}>
34+
<AuthProvider>
35+
{children}
36+
<GlobalSnackbar />
37+
</AuthProvider>
38+
</QueryClientProvider>
39+
</ErrorBoundary>
40+
</ThemeProvider>
41+
</LocalPreferencesProvider>
3942
</StyledEngineProvider>
4043
</HelmetProvider>
4144
)

site/src/components/Logs/Logs.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,11 @@ const useStyles = makeStyles<
9797
>((theme) => ({
9898
root: {
9999
minHeight: 156,
100-
padding: theme.spacing(2, 0),
100+
padding: theme.spacing(1, 0),
101101
borderRadius: theme.shape.borderRadius,
102102
overflowX: "auto",
103103
background: theme.palette.background.default,
104+
borderBottom: `1px solid ${theme.palette.divider}`,
104105
},
105106
scrollWrapper: {
106107
minWidth: "fit-content",

site/src/components/TemplateVersionEditor/TemplateVersionEditor.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,7 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
379379

380380
{buildLogs && buildLogs.length > 0 && (
381381
<WorkspaceBuildLogs
382-
templateEditorPane
382+
sx={{ borderRadius: 0 }}
383383
hideTimestamps
384384
logs={buildLogs}
385385
/>

site/src/components/Workspace/Workspace.stories.tsx

+4-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { withReactContext } from "storybook-react-context"
88
import EventSource from "eventsourcemock"
99
import { ProxyContext, getPreferredProxy } from "contexts/ProxyContext"
1010
import { DashboardProviderContext } from "components/Dashboard/DashboardProvider"
11+
import { WorkspaceBuildLogsSection } from "pages/WorkspacePage/WorkspaceBuildLogsSection"
1112

1213
const MockedAppearance = {
1314
config: Mocks.MockAppearance,
@@ -152,7 +153,7 @@ export const FailedWithLogs: Story = {
152153
},
153154
},
154155
},
155-
failedBuildLogs: makeFailedBuildLogs(),
156+
buildLogs: <WorkspaceBuildLogsSection logs={makeFailedBuildLogs()} />,
156157
},
157158
}
158159

@@ -170,8 +171,8 @@ export const FailedWithRetry: Story = {
170171
},
171172
},
172173
},
173-
failedBuildLogs: makeFailedBuildLogs(),
174174
canRetryDebugMode: true,
175+
buildLogs: <WorkspaceBuildLogsSection logs={makeFailedBuildLogs()} />,
175176
},
176177
}
177178

@@ -229,6 +230,7 @@ export const CancellationError: Story = {
229230
message: "Job could not be canceled.",
230231
}),
231232
},
233+
buildLogs: <WorkspaceBuildLogsSection logs={makeFailedBuildLogs()} />,
232234
},
233235
}
234236

site/src/components/Workspace/Workspace.tsx

+29-25
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import Button from "@mui/material/Button"
22
import { makeStyles } from "@mui/styles"
33
import { Avatar } from "components/Avatar/Avatar"
44
import { AgentRow } from "components/Resources/AgentRow"
5-
import { WorkspaceBuildLogs } from "components/WorkspaceBuildLogs/WorkspaceBuildLogs"
65
import {
76
ActiveTransition,
87
WorkspaceBuildProgress,
@@ -70,8 +69,10 @@ export interface WorkspaceProps {
7069
sshPrefix?: string
7170
template?: TypesGen.Template
7271
quota_budget?: number
73-
failedBuildLogs: TypesGen.ProvisionerJobLog[] | undefined
7472
handleBuildRetry: () => void
73+
buildLogs?: React.ReactNode
74+
canChangeBuildLogsVisibility: boolean
75+
isWorkspaceBuildLogsUIActive: boolean
7576
}
7677

7778
/**
@@ -102,9 +103,11 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
102103
sshPrefix,
103104
template,
104105
quota_budget,
105-
failedBuildLogs,
106106
handleBuildRetry,
107107
templateWarnings,
108+
buildLogs,
109+
canChangeBuildLogsVisibility,
110+
isWorkspaceBuildLogsUIActive,
108111
}) => {
109112
const styles = useStyles()
110113
const navigate = useNavigate()
@@ -208,6 +211,8 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
208211
canChangeVersions={canChangeVersions}
209212
isUpdating={isUpdating}
210213
isRestarting={isRestarting}
214+
canChangeBuildLogsVisibility={canChangeBuildLogsVisibility}
215+
isWorkspaceBuildLogsUIActive={isWorkspaceBuildLogsUIActive}
211216
/>
212217
</PageHeaderActions>
213218
</FullWidthPageHeader>
@@ -259,28 +264,25 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
259264
</Alert>
260265
</Maybe>
261266

262-
{failedBuildLogs && (
263-
<Stack>
264-
<Alert
265-
severity="error"
266-
actions={
267-
canRetryDebugMode && (
268-
<Button
269-
key={0}
270-
onClick={handleBuildRetry}
271-
variant="text"
272-
size="small"
273-
>
274-
{t("actionButton.retryDebugMode")}
275-
</Button>
276-
)
277-
}
278-
>
279-
<AlertTitle>Workspace build failed</AlertTitle>
280-
<AlertDetail>{workspace.latest_build.job.error}</AlertDetail>
281-
</Alert>
282-
<WorkspaceBuildLogs logs={failedBuildLogs} />
283-
</Stack>
267+
{workspace.latest_build.job.error && (
268+
<Alert
269+
severity="error"
270+
actions={
271+
canRetryDebugMode && (
272+
<Button
273+
key={0}
274+
onClick={handleBuildRetry}
275+
variant="text"
276+
size="small"
277+
>
278+
{t("actionButton.retryDebugMode")}
279+
</Button>
280+
)
281+
}
282+
>
283+
<AlertTitle>Workspace build failed</AlertTitle>
284+
<AlertDetail>{workspace.latest_build.job.error}</AlertDetail>
285+
</Alert>
284286
)}
285287

286288
{transitionStats !== undefined && (
@@ -290,6 +292,8 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
290292
/>
291293
)}
292294

295+
{buildLogs}
296+
293297
{typeof resources !== "undefined" && resources.length > 0 && (
294298
<Resources
295299
resources={resources}

site/src/components/WorkspaceActions/WorkspaceActions.tsx

+44
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ import SettingsOutlined from "@mui/icons-material/SettingsOutlined"
2222
import HistoryOutlined from "@mui/icons-material/HistoryOutlined"
2323
import DeleteOutlined from "@mui/icons-material/DeleteOutlined"
2424
import IconButton from "@mui/material/IconButton"
25+
import Divider from "@mui/material/Divider"
26+
import VisibilityOffOutlined from "@mui/icons-material/VisibilityOffOutlined"
27+
import VisibilityOutlined from "@mui/icons-material/VisibilityOutlined"
28+
import { useLocalPreferences } from "contexts/LocalPreferencesContext"
2529

2630
export interface WorkspaceActionsProps {
2731
workspaceStatus: WorkspaceStatus
@@ -38,6 +42,8 @@ export interface WorkspaceActionsProps {
3842
isRestarting: boolean
3943
children?: ReactNode
4044
canChangeVersions: boolean
45+
canChangeBuildLogsVisibility: boolean
46+
isWorkspaceBuildLogsUIActive: boolean
4147
}
4248

4349
export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
@@ -54,6 +60,8 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
5460
isUpdating,
5561
isRestarting,
5662
canChangeVersions,
63+
canChangeBuildLogsVisibility,
64+
isWorkspaceBuildLogsUIActive,
5765
}) => {
5866
const styles = useStyles()
5967
const {
@@ -64,6 +72,9 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
6472
const canBeUpdated = isOutdated && canAcceptJobs
6573
const menuTriggerRef = useRef<HTMLButtonElement>(null)
6674
const [isMenuOpen, setIsMenuOpen] = useState(false)
75+
const localPreferences = useLocalPreferences()
76+
const isBuildLogsVisible =
77+
localPreferences.getPreference("buildLogsVisibility") === "visible"
6778

6879
// A mapping of button type to the corresponding React component
6980
const buttonMapping: ButtonMapping = {
@@ -140,6 +151,39 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
140151
<DeleteOutlined />
141152
Delete
142153
</MenuItem>
154+
155+
{isWorkspaceBuildLogsUIActive && (
156+
<>
157+
<Divider sx={{ borderColor: (theme) => theme.palette.divider }} />
158+
{isBuildLogsVisible ? (
159+
<MenuItem
160+
disabled={!canChangeBuildLogsVisibility}
161+
onClick={onMenuItemClick(() => {
162+
localPreferences.setPreference(
163+
"buildLogsVisibility",
164+
"hide",
165+
)
166+
})}
167+
>
168+
<VisibilityOffOutlined />
169+
Hide build logs
170+
</MenuItem>
171+
) : (
172+
<MenuItem
173+
disabled={!canChangeBuildLogsVisibility}
174+
onClick={onMenuItemClick(() => {
175+
localPreferences.setPreference(
176+
"buildLogsVisibility",
177+
"visible",
178+
)
179+
})}
180+
>
181+
<VisibilityOutlined />
182+
Show build logs
183+
</MenuItem>
184+
)}
185+
</>
186+
)}
143187
</Menu>
144188
</div>
145189
</div>

0 commit comments

Comments
 (0)