Skip to content

Commit 9caa4cf

Browse files
feat(site): display build logs history in the build log page (#9150)
1 parent be40dc8 commit 9caa4cf

15 files changed

+399
-185
lines changed

site/src/api/api.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,7 @@ export function waitForBuild(build: TypesGen.WorkspaceBuild) {
488488
const { job } = await getWorkspaceBuildByNumber(
489489
build.workspace_owner_name,
490490
build.workspace_name,
491-
String(build.build_number),
491+
build.build_number,
492492
)
493493
latestJobInfo = job
494494

@@ -772,7 +772,7 @@ export const getWorkspaceBuilds = async (
772772
export const getWorkspaceBuildByNumber = async (
773773
username = "me",
774774
workspaceName: string,
775-
buildNumber: string,
775+
buildNumber: number,
776776
): Promise<TypesGen.WorkspaceBuild> => {
777777
const response = await axios.get<TypesGen.WorkspaceBuild>(
778778
`/api/v2/users/${username}/workspace/${workspaceName}/builds/${buildNumber}`,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import PlayArrowOutlined from "@mui/icons-material/PlayArrowOutlined"
2+
import StopOutlined from "@mui/icons-material/StopOutlined"
3+
import DeleteOutlined from "@mui/icons-material/DeleteOutlined"
4+
import { WorkspaceTransition } from "api/typesGenerated"
5+
import { ComponentProps } from "react"
6+
7+
type SVGIcon = typeof PlayArrowOutlined
8+
9+
type SVGIconProps = ComponentProps<SVGIcon>
10+
11+
const iconByTransition: Record<WorkspaceTransition, SVGIcon> = {
12+
start: PlayArrowOutlined,
13+
stop: StopOutlined,
14+
delete: DeleteOutlined,
15+
}
16+
17+
export const BuildIcon = (
18+
props: SVGIconProps & { transition: WorkspaceTransition },
19+
) => {
20+
const Icon = iconByTransition[props.transition]
21+
return <Icon {...props} />
22+
}

site/src/components/BuildsTable/BuildAvatar.tsx

+3-11
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
import Badge from "@mui/material/Badge"
22
import { useTheme, withStyles } from "@mui/styles"
33
import { FC } from "react"
4-
import PlayArrowOutlined from "@mui/icons-material/PlayArrowOutlined"
5-
import PauseOutlined from "@mui/icons-material/PauseOutlined"
6-
import DeleteOutlined from "@mui/icons-material/DeleteOutlined"
7-
import { WorkspaceBuild, WorkspaceTransition } from "api/typesGenerated"
4+
import { WorkspaceBuild } from "api/typesGenerated"
85
import { getDisplayWorkspaceBuildStatus } from "utils/workspace"
96
import { Avatar, AvatarProps } from "components/Avatar/Avatar"
107
import { PaletteIndex } from "theme/theme"
118
import { Theme } from "@mui/material/styles"
9+
import { BuildIcon } from "components/BuildIcon/BuildIcon"
1210

1311
interface StylesBadgeProps {
1412
type: PaletteIndex
@@ -31,12 +29,6 @@ export interface BuildAvatarProps {
3129
size?: AvatarProps["size"]
3230
}
3331

34-
const iconByTransition: Record<WorkspaceTransition, JSX.Element> = {
35-
start: <PlayArrowOutlined />,
36-
stop: <PauseOutlined />,
37-
delete: <DeleteOutlined />,
38-
}
39-
4032
export const BuildAvatar: FC<BuildAvatarProps> = ({ build, size }) => {
4133
const theme = useTheme<Theme>()
4234
const displayBuildStatus = getDisplayWorkspaceBuildStatus(theme, build)
@@ -55,7 +47,7 @@ export const BuildAvatar: FC<BuildAvatarProps> = ({ build, size }) => {
5547
badgeContent={<div></div>}
5648
>
5749
<Avatar size={size} colorScheme="darken">
58-
{iconByTransition[build.transition]}
50+
<BuildIcon transition={build.transition} />
5951
</Avatar>
6052
</StyledBadge>
6153
)
+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import Box, { BoxProps } from "@mui/material/Box"
2+
import { styled } from "@mui/styles"
3+
import { colors } from "theme/colors"
4+
5+
export const Sidebar = styled((props: BoxProps) => (
6+
<Box {...props} component="nav" />
7+
))(({ theme }) => ({
8+
width: theme.spacing(32),
9+
flexShrink: 0,
10+
borderRight: `1px solid ${theme.palette.divider}`,
11+
height: "100%",
12+
overflowY: "auto",
13+
}))
14+
15+
export const SidebarItem = styled(
16+
({ active, ...props }: BoxProps & { active?: boolean }) => (
17+
<Box component="button" {...props} />
18+
),
19+
)(({ theme, active }) => ({
20+
background: active ? colors.gray[13] : "none",
21+
border: "none",
22+
fontSize: 14,
23+
width: "100%",
24+
textAlign: "left",
25+
padding: theme.spacing(0, 3),
26+
cursor: "pointer",
27+
pointerEvents: active ? "none" : "auto",
28+
color: active ? theme.palette.text.primary : theme.palette.text.secondary,
29+
"&:hover": {
30+
background: theme.palette.action.hover,
31+
color: theme.palette.text.primary,
32+
},
33+
paddingTop: theme.spacing(1.25),
34+
paddingBottom: theme.spacing(1.25),
35+
}))
36+
37+
export const SidebarCaption = styled(Box)(({ theme }) => ({
38+
fontSize: 10,
39+
textTransform: "uppercase",
40+
fontWeight: 500,
41+
color: theme.palette.text.secondary,
42+
padding: theme.spacing(1.5, 3),
43+
letterSpacing: "0.5px",
44+
}))

site/src/components/WorkspaceBuildStats/WorkspaceBuildStats.stories.tsx

-37
This file was deleted.

site/src/components/WorkspaceBuildStats/WorkspaceBuildStats.tsx

-38
This file was deleted.

site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.test.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ describe("WorkspaceBuildPage", () => {
2222
expect(getWorkspaceBuildSpy).toBeCalledWith(
2323
MockWorkspace.owner_name,
2424
MockWorkspace.name,
25-
`${MockWorkspaceBuild.build_number}`,
25+
MockWorkspaceBuild.build_number,
2626
),
2727
)
2828
})

site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.tsx

+29-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
import { useMachine } from "@xstate/react"
2-
import { FC } from "react"
2+
import { FC, useEffect } from "react"
33
import { Helmet } from "react-helmet-async"
44
import { useParams } from "react-router-dom"
55
import { pageTitle } from "../../utils/page"
66
import { workspaceBuildMachine } from "../../xServices/workspaceBuild/workspaceBuildXService"
77
import { WorkspaceBuildPageView } from "./WorkspaceBuildPageView"
8+
import { useQuery } from "@tanstack/react-query"
9+
import { getWorkspaceBuilds } from "api/api"
10+
import dayjs from "dayjs"
11+
import { usePermissions } from "hooks"
812

913
export const WorkspaceBuildPage: FC = () => {
1014
const params = useParams() as {
@@ -13,12 +17,27 @@ export const WorkspaceBuildPage: FC = () => {
1317
buildNumber: string
1418
}
1519
const workspaceName = params.workspace
16-
const buildNumber = params.buildNumber
20+
const buildNumber = Number(params.buildNumber)
1721
const username = params.username.replace("@", "")
18-
const [buildState] = useMachine(workspaceBuildMachine, {
22+
const [buildState, send] = useMachine(workspaceBuildMachine, {
1923
context: { username, workspaceName, buildNumber, timeCursor: new Date() },
2024
})
2125
const { logs, build } = buildState.context
26+
const { data: builds } = useQuery({
27+
queryKey: ["builds", username, build?.workspace_id],
28+
queryFn: () => {
29+
return getWorkspaceBuilds(
30+
build?.workspace_id ?? "",
31+
dayjs().add(-30, "day").toDate(),
32+
)
33+
},
34+
enabled: Boolean(build),
35+
})
36+
const permissions = usePermissions()
37+
38+
useEffect(() => {
39+
send("RESET", { buildNumber, timeCursor: new Date() })
40+
}, [buildNumber, send])
2241

2342
return (
2443
<>
@@ -32,7 +51,13 @@ export const WorkspaceBuildPage: FC = () => {
3251
</title>
3352
</Helmet>
3453

35-
<WorkspaceBuildPageView logs={logs} build={build} />
54+
<WorkspaceBuildPageView
55+
logs={logs}
56+
build={build}
57+
builds={builds}
58+
activeBuildNumber={buildNumber}
59+
hasDeploymentBanner={permissions.viewDeploymentStats}
60+
/>
3661
</>
3762
)
3863
}
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,49 @@
1-
import { ComponentMeta, Story } from "@storybook/react"
1+
import { Meta, StoryObj } from "@storybook/react"
22
import {
33
MockFailedWorkspaceBuild,
44
MockWorkspaceBuild,
55
MockWorkspaceBuildLogs,
66
} from "../../testHelpers/entities"
7-
import {
8-
WorkspaceBuildPageView,
9-
WorkspaceBuildPageViewProps,
10-
} from "./WorkspaceBuildPageView"
7+
import { WorkspaceBuildPageView } from "./WorkspaceBuildPageView"
8+
9+
const defaultBuilds = Array.from({ length: 15 }, (_, i) => ({
10+
...MockWorkspaceBuild,
11+
id: `${i}`,
12+
build_number: i,
13+
}))
1114

12-
export default {
15+
const meta: Meta<typeof WorkspaceBuildPageView> = {
1316
title: "pages/WorkspaceBuildPageView",
1417
component: WorkspaceBuildPageView,
15-
} as ComponentMeta<typeof WorkspaceBuildPageView>
18+
args: {
19+
build: MockWorkspaceBuild,
20+
logs: MockWorkspaceBuildLogs,
21+
builds: defaultBuilds,
22+
activeBuildNumber: defaultBuilds[0].build_number,
23+
hasDeploymentBanner: false,
24+
},
25+
}
1626

17-
const Template: Story<WorkspaceBuildPageViewProps> = (args) => (
18-
<WorkspaceBuildPageView {...args} />
19-
)
27+
export default meta
28+
type Story = StoryObj<typeof WorkspaceBuildPageView>
29+
30+
export const Loaded: Story = {}
31+
32+
export const LoadingBuildLogs: Story = {
33+
args: {
34+
builds: undefined,
35+
},
36+
}
2037

21-
export const Example = Template.bind({})
22-
Example.args = {
23-
build: MockWorkspaceBuild,
24-
logs: MockWorkspaceBuildLogs,
38+
const failedBuild = {
39+
...MockFailedWorkspaceBuild("delete"),
40+
build_number: new Date().getDate(),
2541
}
2642

27-
export const FailedDelete = Template.bind({})
28-
FailedDelete.args = {
29-
build: MockFailedWorkspaceBuild("delete"),
30-
logs: MockWorkspaceBuildLogs,
43+
export const FailedDelete: Story = {
44+
args: {
45+
build: failedBuild,
46+
builds: [failedBuild, ...defaultBuilds],
47+
activeBuildNumber: failedBuild.build_number,
48+
},
3149
}

0 commit comments

Comments
 (0)