Skip to content

Commit e4c7eef

Browse files
BrunoQuaresmakylecarbs
authored andcommitted
feat: Add timeline in the workspace page (#1533)
1 parent 11b1a88 commit e4c7eef

File tree

12 files changed

+453
-149
lines changed

12 files changed

+453
-149
lines changed

site/src/api/api.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,3 +221,8 @@ export const regenerateUserSSHKey = async (userId = "me"): Promise<TypesGen.GitS
221221
const response = await axios.put<TypesGen.GitSSHKey>(`/api/v2/users/${userId}/gitsshkey`)
222222
return response.data
223223
}
224+
225+
export const getWorkspaceBuilds = async (workspaceId: string): Promise<TypesGen.WorkspaceBuild[]> => {
226+
const response = await axios.get<TypesGen.WorkspaceBuild[]>(`/api/v2/workspaces/${workspaceId}/builds`)
227+
return response.data
228+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { ComponentMeta, Story } from "@storybook/react"
2+
import React from "react"
3+
import { MockBuilds } from "../../testHelpers/entities"
4+
import { BuildsTable, BuildsTableProps } from "./BuildsTable"
5+
6+
export default {
7+
title: "components/BuildsTable",
8+
component: BuildsTable,
9+
} as ComponentMeta<typeof BuildsTable>
10+
11+
const Template: Story<BuildsTableProps> = (args) => <BuildsTable {...args} />
12+
13+
export const Example = Template.bind({})
14+
Example.args = {
15+
builds: MockBuilds,
16+
}
17+
18+
export const Empty = Template.bind({})
19+
Empty.args = {
20+
builds: [],
21+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import Box from "@material-ui/core/Box"
2+
import { Theme } from "@material-ui/core/styles"
3+
import Table from "@material-ui/core/Table"
4+
import TableBody from "@material-ui/core/TableBody"
5+
import TableCell from "@material-ui/core/TableCell"
6+
import TableHead from "@material-ui/core/TableHead"
7+
import TableRow from "@material-ui/core/TableRow"
8+
import useTheme from "@material-ui/styles/useTheme"
9+
import dayjs from "dayjs"
10+
import duration from "dayjs/plugin/duration"
11+
import relativeTime from "dayjs/plugin/relativeTime"
12+
import React from "react"
13+
import * as TypesGen from "../../api/typesGenerated"
14+
import { getDisplayStatus } from "../../util/workspace"
15+
import { EmptyState } from "../EmptyState/EmptyState"
16+
import { TableLoader } from "../TableLoader/TableLoader"
17+
18+
dayjs.extend(relativeTime)
19+
dayjs.extend(duration)
20+
21+
export const Language = {
22+
emptyMessage: "No builds found",
23+
inProgressLabel: "In progress",
24+
actionLabel: "Action",
25+
durationLabel: "Duration",
26+
startedAtLabel: "Started at",
27+
statusLabel: "Status",
28+
}
29+
30+
const getDurationInSeconds = (build: TypesGen.WorkspaceBuild) => {
31+
let display = Language.inProgressLabel
32+
33+
if (build.job.started_at && build.job.completed_at) {
34+
const startedAt = dayjs(build.job.started_at)
35+
const completedAt = dayjs(build.job.completed_at)
36+
const diff = completedAt.diff(startedAt, "seconds")
37+
display = `${diff} seconds`
38+
}
39+
40+
return display
41+
}
42+
43+
export interface BuildsTableProps {
44+
builds?: TypesGen.WorkspaceBuild[]
45+
className?: string
46+
}
47+
48+
export const BuildsTable: React.FC<BuildsTableProps> = ({ builds, className }) => {
49+
const isLoading = !builds
50+
const theme: Theme = useTheme()
51+
52+
return (
53+
<Table className={className}>
54+
<TableHead>
55+
<TableRow>
56+
<TableCell width="20%">{Language.actionLabel}</TableCell>
57+
<TableCell width="20%">{Language.durationLabel}</TableCell>
58+
<TableCell width="40%">{Language.startedAtLabel}</TableCell>
59+
<TableCell width="20%">{Language.statusLabel}</TableCell>
60+
</TableRow>
61+
</TableHead>
62+
<TableBody>
63+
{isLoading && <TableLoader />}
64+
{builds &&
65+
builds.map((b) => {
66+
const status = getDisplayStatus(theme, b)
67+
const duration = getDurationInSeconds(b)
68+
69+
return (
70+
<TableRow key={b.id} data-testid={`build-${b.id}`}>
71+
<TableCell>{b.transition}</TableCell>
72+
<TableCell>
73+
<span style={{ color: theme.palette.text.secondary }}>{duration}</span>
74+
</TableCell>
75+
<TableCell>
76+
<span style={{ color: theme.palette.text.secondary }}>{new Date(b.created_at).toLocaleString()}</span>
77+
</TableCell>
78+
<TableCell>
79+
<span style={{ color: status.color }}>{status.status}</span>
80+
</TableCell>
81+
</TableRow>
82+
)
83+
})}
84+
85+
{builds && builds.length === 0 && (
86+
<TableRow>
87+
<TableCell colSpan={999}>
88+
<Box p={4}>
89+
<EmptyState message={Language.emptyMessage} />
90+
</Box>
91+
</TableCell>
92+
</TableRow>
93+
)}
94+
</TableBody>
95+
</Table>
96+
)
97+
}

site/src/components/Workspace/Workspace.tsx

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import Typography from "@material-ui/core/Typography"
33
import React from "react"
44
import * as TypesGen from "../../api/typesGenerated"
55
import { WorkspaceStatus } from "../../util/workspace"
6+
import { BuildsTable } from "../BuildsTable/BuildsTable"
67
import { WorkspaceSchedule } from "../WorkspaceSchedule/WorkspaceSchedule"
78
import { WorkspaceSection } from "../WorkspaceSection/WorkspaceSection"
89
import { WorkspaceStatusBar } from "../WorkspaceStatusBar/WorkspaceStatusBar"
@@ -16,6 +17,7 @@ export interface WorkspaceProps {
1617
handleRetry: () => void
1718
handleUpdate: () => void
1819
workspaceStatus: WorkspaceStatus
20+
builds?: TypesGen.WorkspaceBuild[]
1921
}
2022

2123
/**
@@ -28,6 +30,7 @@ export const Workspace: React.FC<WorkspaceProps> = ({
2830
handleRetry,
2931
handleUpdate,
3032
workspaceStatus,
33+
builds,
3134
}) => {
3235
const styles = useStyles()
3336

@@ -56,13 +59,8 @@ export const Workspace: React.FC<WorkspaceProps> = ({
5659
</WorkspaceSection>
5760
</div>
5861
<div className={styles.timelineContainer}>
59-
<WorkspaceSection title="Timeline">
60-
<div
61-
className={styles.vertical}
62-
style={{ justifyContent: "center", alignItems: "center", height: "300px" }}
63-
>
64-
<Placeholder />
65-
</div>
62+
<WorkspaceSection title="Timeline" contentsProps={{ className: styles.timelineContents }}>
63+
<BuildsTable builds={builds} className={styles.timelineTable} />
6664
</WorkspaceSection>
6765
</div>
6866
</div>
@@ -105,5 +103,11 @@ export const useStyles = makeStyles(() => {
105103
timelineContainer: {
106104
flex: 1,
107105
},
106+
timelineContents: {
107+
margin: 0,
108+
},
109+
timelineTable: {
110+
border: 0,
111+
},
108112
}
109113
})

site/src/components/WorkspaceSection/WorkspaceSection.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
import Paper from "@material-ui/core/Paper"
22
import { makeStyles } from "@material-ui/core/styles"
33
import Typography from "@material-ui/core/Typography"
4-
import React from "react"
4+
import React, { HTMLProps } from "react"
55
import { CardPadding, CardRadius } from "../../theme/constants"
6+
import { combineClasses } from "../../util/combineClasses"
67

78
export interface WorkspaceSectionProps {
89
title?: string
10+
contentsProps?: HTMLProps<HTMLDivElement>
911
}
1012

11-
export const WorkspaceSection: React.FC<WorkspaceSectionProps> = ({ title, children }) => {
13+
export const WorkspaceSection: React.FC<WorkspaceSectionProps> = ({ title, children, contentsProps }) => {
1214
const styles = useStyles()
1315

1416
return (
@@ -21,7 +23,9 @@ export const WorkspaceSection: React.FC<WorkspaceSectionProps> = ({ title, child
2123
</div>
2224
)}
2325

24-
<div className={styles.contents}>{children}</div>
26+
<div {...contentsProps} className={combineClasses([styles.contents, contentsProps?.className])}>
27+
{children}
28+
</div>
2529
</Paper>
2630
)
2731
}

0 commit comments

Comments
 (0)