Skip to content

Commit 8b73c2c

Browse files
committed
Update timeline and components
1 parent 48bb588 commit 8b73c2c

File tree

2 files changed

+169
-114
lines changed

2 files changed

+169
-114
lines changed

site/components/Timeline/index.tsx

Lines changed: 145 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
1-
import { Avatar, Box, SvgIcon, Typography } from "@material-ui/core"
2-
import makeStyles from "@material-ui/styles/makeStyles";
1+
import { Avatar, Box, CircularProgress, SvgIcon, Typography } from "@material-ui/core"
2+
import makeStyles from "@material-ui/styles/makeStyles"
33
import React, { useState } from "react"
4-
import { TerminalOutput } from "./TerminalOutput";
4+
import { TerminalOutput } from "./TerminalOutput"
5+
import StageCompleteIcon from "@material-ui/icons/Done"
6+
import StageExpandedIcon from "@material-ui/icons/KeyboardArrowDown"
7+
import StageErrorIcon from "@material-ui/icons/Warning"
8+
9+
export type BuildLogStatus = "success" | "failed" | "pending"
510

611
export interface TimelineEntry {
712
date: Date
813
title: string
914
description?: string
15+
status: BuildLogStatus
16+
buildSummary: string
17+
buildLogs: string[]
1018
}
1119

12-
const today = new Date();
20+
const today = new Date()
1321
const yesterday = new Date()
1422
yesterday.setHours(-24)
1523
const weekAgo = new Date()
@@ -36,24 +44,40 @@ Pulling image "gcr.io/coder-enterprise-nightlies/coder/envbox:1.27.0-rc.0-145-g8
3644
Successfully pulled image "gcr.io/coder-enterprise-nightlies/coder/envbox:1.27.0-rc.0-145-g8d4ee2e9e-20220131" in 7.423772294s
3745
`.split("\n")
3846

39-
export const mockEntries: TimelineEntry[] = [{
40-
date: weekAgo,
41-
description: "Created Workspace",
42-
title: "Admin",
43-
}, {
44-
date: yesterday,
45-
description: "Modified Workspace",
46-
title: "Admin"
47-
}, {
48-
date: today,
49-
description: "Modified Workspace",
50-
title: "Admin"
51-
}, {
52-
date: today,
53-
description: "Restarted Workspace",
54-
title: "Admin"
55-
56-
}]
47+
export const mockEntries: TimelineEntry[] = [
48+
{
49+
date: weekAgo,
50+
description: "Created Workspace",
51+
title: "Admin",
52+
status: "success",
53+
buildLogs: sampleOutput,
54+
buildSummary: "Succeeded in 82s",
55+
},
56+
{
57+
date: yesterday,
58+
description: "Modified Workspace",
59+
title: "Admin",
60+
status: "failed",
61+
buildLogs: sampleOutput,
62+
buildSummary: "Encountered error after 49s",
63+
},
64+
{
65+
date: today,
66+
description: "Modified Workspace",
67+
title: "Admin",
68+
status: "pending",
69+
buildLogs: sampleOutput,
70+
buildSummary: "Operation in progress...",
71+
},
72+
{
73+
date: today,
74+
description: "Restarted Workspace",
75+
title: "Admin",
76+
status: "success",
77+
buildLogs: sampleOutput,
78+
buildSummary: "Succeeded in 15s",
79+
},
80+
]
5781

5882
export interface TimelineEntryProps {
5983
entries: TimelineEntry[]
@@ -69,42 +93,39 @@ const getDateWithoutTime = (date: Date) => {
6993
}
7094

7195
export const groupByDate = (entries: TimelineEntry[]): Record<string, TimelineEntry[]> => {
72-
const initial: Record<string, TimelineEntry[]> = {};
96+
const initial: Record<string, TimelineEntry[]> = {}
7397
return entries.reduce<Record<string, TimelineEntry[]>>((acc, curr) => {
74-
const dateWithoutTime = getDateWithoutTime(curr.date);
98+
const dateWithoutTime = getDateWithoutTime(curr.date)
7599
const key = dateWithoutTime.getTime().toString()
76-
const currentEntry = acc[key];
100+
const currentEntry = acc[key]
77101
if (currentEntry) {
78102
return {
79103
...acc,
80-
[key]: [...currentEntry, curr]
104+
[key]: [...currentEntry, curr],
81105
}
82106
} else {
83107
return {
84108
...acc,
85-
[key]: [curr]
109+
[key]: [curr],
86110
}
87111
}
88112
}, initial)
89-
90113
}
91114

92115
const formatDate = (date: Date) => {
93116
let formatter = new Intl.DateTimeFormat("en", {
94-
dateStyle: "long"
95-
});
117+
dateStyle: "long",
118+
})
96119
return formatter.format(date)
97120
}
98121

99122
const formatTime = (date: Date) => {
100123
let formatter = new Intl.DateTimeFormat("en", {
101-
timeStyle: "short"
102-
});
124+
timeStyle: "short",
125+
})
103126
return formatter.format(date)
104127
}
105128

106-
107-
108129
export interface EntryProps {
109130
entry: TimelineEntry
110131
}
@@ -117,110 +138,134 @@ export const Entry: React.FC<EntryProps> = ({ entry }) => {
117138
setExpanded((prev: boolean) => !prev)
118139
}
119140

120-
return <Box display={"flex"} flexDirection={"column"} onClick={toggleExpanded}>
121-
<Box display={"flex"} flexDirection={"row"} justifyContent={"flex-start"} alignItems={"center"}>
122-
<Box display={"flex"} flexDirection={"column"} justifyContent={"flex-start"} alignItems={"center"} mb={"auto"}>
123-
<Avatar>{"A"}</Avatar>
124-
</Box>
125-
<Box m={"0em 1em"} flexDirection={"column"} flex={"1"}>
126-
127-
<Box display={"flex"} flexDirection={"row"} alignItems={"center"}>
128-
<Typography variant={"h6"}>{entry.title}</Typography>
129-
<Typography variant={"caption"} style={{ marginLeft: "1em" }}>{formatTime(entry.date)}</Typography>
141+
return (
142+
<Box display={"flex"} flexDirection={"column"} onClick={toggleExpanded}>
143+
<Box display={"flex"} flexDirection={"row"} justifyContent={"flex-start"} alignItems={"center"}>
144+
<Box display={"flex"} flexDirection={"column"} justifyContent={"flex-start"} alignItems={"center"} mb={"auto"}>
145+
<Avatar>{"A"}</Avatar>
130146
</Box>
131-
<Typography variant={"body2"}>{entry.description}</Typography>
132-
<Box>
133-
<BuildLog summary={"testing"} status={"success"} expanded={expanded} onToggleClicked={toggleExpanded} />
147+
<Box m={"0em 1em"} flexDirection={"column"} flex={"1"}>
148+
<Box display={"flex"} flexDirection={"row"} alignItems={"center"}>
149+
<Typography variant={"h6"}>{entry.title}</Typography>
150+
<Typography variant={"caption"} style={{ marginLeft: "1em" }}>
151+
{formatTime(entry.date)}
152+
</Typography>
153+
</Box>
154+
<Typography variant={"body2"}>{entry.description}</Typography>
155+
<Box>
156+
<BuildLog
157+
summary={entry.buildSummary}
158+
status={entry.status}
159+
expanded={expanded}
160+
onToggleClicked={toggleExpanded}
161+
/>
162+
</Box>
134163
</Box>
135164
</Box>
136165
</Box>
137-
138-
</Box >
166+
)
139167
}
140168

141-
export const useEntryStyles = makeStyles((theme) => ({
142-
143-
}))
144-
145-
export type BuildLogStatus = "success" | "failure" | "pending"
169+
export const useEntryStyles = makeStyles((theme) => ({}))
146170

147171
export interface BuildLogProps {
148172
summary: string
149173
status: BuildLogStatus
150174
expanded?: boolean
151175
}
152-
176+
const STATUS_ICON_SIZE = 18
177+
const LOADING_SPINNER_SIZE = 14
153178
export const BuildLog: React.FC<BuildLogProps> = ({ summary, status, expanded }) => {
154179
const styles = useBuildLogStyles(status)()
180+
let icon: JSX.Element
181+
if (status === "failed") {
182+
icon = <StageErrorIcon className={`${styles.statusIcon} ${styles.statusIconError}`} />
183+
} else if (status === "pending") {
184+
icon = <CircularProgress size={LOADING_SPINNER_SIZE} />
185+
} else {
186+
icon = <StageCompleteIcon className={`${styles.statusIcon} ${styles.statusIconSuccess}`} />
187+
}
155188

156-
return <div className={styles.container}>
157-
<button className={styles.collapseButton}>
158-
<Box m={"0.25em 0em"}>
159-
<Typography variant={"caption"}>{summary}</Typography>
160-
{expanded && <TerminalOutput output={sampleOutput} />}
161-
</Box>
162-
</button>
163-
</div>
164-
189+
return (
190+
<div className={styles.container}>
191+
<button className={styles.collapseButton}>
192+
<Box m={"0.25em 0em"} display={"flex"} flexDirection={"row"} alignItems={"center"}>
193+
<Typography variant={"caption"}>{summary}</Typography>
194+
<Box m={"0.25em"}>{icon}</Box>
195+
</Box>
196+
</button>
197+
{expanded && <TerminalOutput output={sampleOutput} />}
198+
</div>
199+
)
165200
}
166201

167-
const useBuildLogStyles = (status: BuildLogStatus) => makeStyles((theme) => ({
168-
container: {
169-
borderLeft: `2px solid ${status === "failure" ? theme.palette.error.main : theme.palette.info.main}`,
170-
margin: "1em 0em",
171-
},
172-
collapseButton: {
173-
color: "inherit",
174-
textAlign: "left",
175-
width: "100%",
176-
background: "none",
177-
border: 0,
178-
alignItems: "center",
179-
borderRadius: theme.spacing(0.5),
180-
cursor: "pointer",
181-
"&:disabled": {
202+
const useBuildLogStyles = (status: BuildLogStatus) =>
203+
makeStyles((theme) => ({
204+
container: {
205+
borderLeft: `2px solid ${theme.palette.info.main}`,
206+
margin: "1em 0em",
207+
},
208+
collapseButton: {
182209
color: "inherit",
183-
cursor: "initial",
210+
textAlign: "left",
211+
width: "100%",
212+
background: "none",
213+
border: 0,
214+
alignItems: "center",
215+
borderRadius: theme.spacing(0.5),
216+
cursor: "pointer",
217+
"&:disabled": {
218+
color: "inherit",
219+
cursor: "initial",
220+
},
221+
"&:hover:not(:disabled)": {
222+
backgroundColor: theme.palette.type === "dark" ? theme.palette.grey[800] : theme.palette.grey[100],
223+
},
184224
},
185-
"&:hover:not(:disabled)": {
186-
backgroundColor: theme.palette.type === "dark" ? theme.palette.grey[800] : theme.palette.grey[100],
225+
statusIcon: {
226+
width: STATUS_ICON_SIZE,
227+
height: STATUS_ICON_SIZE,
228+
color: theme.palette.text.secondary,
187229
},
188-
},
189-
}))
230+
statusIconError: {
231+
color: theme.palette.error.main,
232+
},
233+
statusIconSuccess: {
234+
color: theme.palette.success.main,
235+
},
236+
}))
190237

191238
export const Timeline: React.FC = () => {
192239
const styles = useStyles()
193240

194241
const entries = mockEntries
195242
const groupedByDate = groupByDate(entries)
196-
const allDates = Object.keys(groupedByDate);
243+
const allDates = Object.keys(groupedByDate)
197244
const sortedDates = allDates.sort((a, b) => b.localeCompare(a))
198245

199246
const days = sortedDates.map((date) => {
200-
201-
const entriesForDay = groupedByDate[date];
247+
const entriesForDay = groupedByDate[date]
202248

203249
const entryElements = entriesForDay.map((entry) => <Entry entry={entry} isExpanded={false} />)
204250

205-
206-
return <div className={styles.root}>
207-
<Typography className={styles.header} variant="caption" color="textSecondary">{formatDate(new Date(Number.parseInt(date)))}</Typography>
208-
{entryElements}
209-
210-
</div>
251+
return (
252+
<div className={styles.root}>
253+
<Typography className={styles.header} variant="caption" color="textSecondary">
254+
{formatDate(new Date(Number.parseInt(date)))}
255+
</Typography>
256+
{entryElements}
257+
</div>
258+
)
211259
})
212260

213-
return <div className={styles.root}>
214-
{days}
215-
</div>
216-
261+
return <div className={styles.root}>{days}</div>
217262
}
218263

219264
export const useStyles = makeStyles((theme) => ({
220265
root: {
221266
display: "flex",
222267
width: "100%",
223-
flexDirection: "column"
268+
flexDirection: "column",
224269
},
225270
container: {
226271
display: "flex",
@@ -231,5 +276,5 @@ export const useStyles = makeStyles((theme) => ({
231276
justifyContent: "center",
232277
alignItems: "center",
233278
//textTransform: "uppercase"
234-
}
235-
}))
279+
},
280+
}))

0 commit comments

Comments
 (0)