Skip to content

Commit 48bb588

Browse files
committed
Model workspace with static data
1 parent 07d5711 commit 48bb588

File tree

3 files changed

+297
-44
lines changed

3 files changed

+297
-44
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { makeStyles } from "@material-ui/core/styles"
2+
import React from "react"
3+
4+
interface Props {
5+
output: string[]
6+
className?: string
7+
}
8+
9+
export const TerminalOutput: React.FC<Props> = ({ className, output }) => {
10+
const styles = useStyles()
11+
12+
return (
13+
<div className={`${styles.root} ${className}`}>
14+
{output.map((line, idx) => (
15+
<div className={styles.line} key={idx}>
16+
{line}
17+
</div>
18+
))}
19+
</div>
20+
)
21+
}
22+
export const MONOSPACE_FONT_FAMILY =
23+
"'Fira Code', 'Lucida Console', 'Lucida Sans Typewriter', 'Liberation Mono', 'Monaco', 'Courier New', Courier, monospace"
24+
const useStyles = makeStyles((theme) => ({
25+
root: {
26+
minHeight: 156,
27+
background: theme.palette.background.default,
28+
//color: theme.palette.codeBlock.contrastText,
29+
fontFamily: MONOSPACE_FONT_FAMILY,
30+
fontSize: 13,
31+
wordBreak: "break-all",
32+
padding: theme.spacing(2),
33+
borderRadius: theme.shape.borderRadius,
34+
},
35+
line: {
36+
whiteSpace: "pre-wrap",
37+
},
38+
}))

site/components/Timeline/index.tsx

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
import { Avatar, Box, SvgIcon, Typography } from "@material-ui/core"
2+
import makeStyles from "@material-ui/styles/makeStyles";
3+
import React, { useState } from "react"
4+
import { TerminalOutput } from "./TerminalOutput";
5+
6+
export interface TimelineEntry {
7+
date: Date
8+
title: string
9+
description?: string
10+
}
11+
12+
const today = new Date();
13+
const yesterday = new Date()
14+
yesterday.setHours(-24)
15+
const weekAgo = new Date()
16+
weekAgo.setHours(-24 * 7)
17+
18+
const sampleOutput = `
19+
Successfully assigned coder/bryan-prototype-jppnd to gke-master-workspaces-1-ef039342-cybd
20+
Container image "gke.gcr.io/istio/proxyv2:1.4.10-gke.8" already present on machine
21+
Created container istio-init
22+
Started container istio-init
23+
Pulling image "gcr.io/coder-enterprise-nightlies/coder/envbox:1.27.0-rc.0-145-g8d4ee2e9e-20220131"
24+
Successfully pulled image "gcr.io/coder-enterprise-nightlies/coder/envbox:1.27.0-rc.0-145-g8d4ee2e9e-20220131" in 7.423772294s
25+
Successfully assigned coder/bryan-prototype-jppnd to gke-master-workspaces-1-ef039342-cybd
26+
Container image "gke.gcr.io/istio/proxyv2:1.4.10-gke.8" already present on machine
27+
Created container istio-init
28+
Started container istio-init
29+
Pulling image "gcr.io/coder-enterprise-nightlies/coder/envbox:1.27.0-rc.0-145-g8d4ee2e9e-20220131"
30+
Successfully pulled image "gcr.io/coder-enterprise-nightlies/coder/envbox:1.27.0-rc.0-145-g8d4ee2e9e-20220131" in 7.423772294s
31+
Successfully assigned coder/bryan-prototype-jppnd to gke-master-workspaces-1-ef039342-cybd
32+
Container image "gke.gcr.io/istio/proxyv2:1.4.10-gke.8" already present on machine
33+
Created container istio-init
34+
Started container istio-init
35+
Pulling image "gcr.io/coder-enterprise-nightlies/coder/envbox:1.27.0-rc.0-145-g8d4ee2e9e-20220131"
36+
Successfully pulled image "gcr.io/coder-enterprise-nightlies/coder/envbox:1.27.0-rc.0-145-g8d4ee2e9e-20220131" in 7.423772294s
37+
`.split("\n")
38+
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+
}]
57+
58+
export interface TimelineEntryProps {
59+
entries: TimelineEntry[]
60+
}
61+
62+
// Group timeline entry by date
63+
64+
const getDateWithoutTime = (date: Date) => {
65+
// TODO: Handle conversion to local time from UTC, as this may shift the actual day
66+
const dateWithoutTime = new Date(date.getTime())
67+
dateWithoutTime.setHours(0, 0, 0, 0)
68+
return dateWithoutTime
69+
}
70+
71+
export const groupByDate = (entries: TimelineEntry[]): Record<string, TimelineEntry[]> => {
72+
const initial: Record<string, TimelineEntry[]> = {};
73+
return entries.reduce<Record<string, TimelineEntry[]>>((acc, curr) => {
74+
const dateWithoutTime = getDateWithoutTime(curr.date);
75+
const key = dateWithoutTime.getTime().toString()
76+
const currentEntry = acc[key];
77+
if (currentEntry) {
78+
return {
79+
...acc,
80+
[key]: [...currentEntry, curr]
81+
}
82+
} else {
83+
return {
84+
...acc,
85+
[key]: [curr]
86+
}
87+
}
88+
}, initial)
89+
90+
}
91+
92+
const formatDate = (date: Date) => {
93+
let formatter = new Intl.DateTimeFormat("en", {
94+
dateStyle: "long"
95+
});
96+
return formatter.format(date)
97+
}
98+
99+
const formatTime = (date: Date) => {
100+
let formatter = new Intl.DateTimeFormat("en", {
101+
timeStyle: "short"
102+
});
103+
return formatter.format(date)
104+
}
105+
106+
107+
108+
export interface EntryProps {
109+
entry: TimelineEntry
110+
}
111+
112+
export const Entry: React.FC<EntryProps> = ({ entry }) => {
113+
const styles = useEntryStyles()
114+
const [expanded, setExpanded] = useState(false)
115+
116+
const toggleExpanded = () => {
117+
setExpanded((prev: boolean) => !prev)
118+
}
119+
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>
130+
</Box>
131+
<Typography variant={"body2"}>{entry.description}</Typography>
132+
<Box>
133+
<BuildLog summary={"testing"} status={"success"} expanded={expanded} onToggleClicked={toggleExpanded} />
134+
</Box>
135+
</Box>
136+
</Box>
137+
138+
</Box >
139+
}
140+
141+
export const useEntryStyles = makeStyles((theme) => ({
142+
143+
}))
144+
145+
export type BuildLogStatus = "success" | "failure" | "pending"
146+
147+
export interface BuildLogProps {
148+
summary: string
149+
status: BuildLogStatus
150+
expanded?: boolean
151+
}
152+
153+
export const BuildLog: React.FC<BuildLogProps> = ({ summary, status, expanded }) => {
154+
const styles = useBuildLogStyles(status)()
155+
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+
165+
}
166+
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": {
182+
color: "inherit",
183+
cursor: "initial",
184+
},
185+
"&:hover:not(:disabled)": {
186+
backgroundColor: theme.palette.type === "dark" ? theme.palette.grey[800] : theme.palette.grey[100],
187+
},
188+
},
189+
}))
190+
191+
export const Timeline: React.FC = () => {
192+
const styles = useStyles()
193+
194+
const entries = mockEntries
195+
const groupedByDate = groupByDate(entries)
196+
const allDates = Object.keys(groupedByDate);
197+
const sortedDates = allDates.sort((a, b) => b.localeCompare(a))
198+
199+
const days = sortedDates.map((date) => {
200+
201+
const entriesForDay = groupedByDate[date];
202+
203+
const entryElements = entriesForDay.map((entry) => <Entry entry={entry} isExpanded={false} />)
204+
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>
211+
})
212+
213+
return <div className={styles.root}>
214+
{days}
215+
</div>
216+
217+
}
218+
219+
export const useStyles = makeStyles((theme) => ({
220+
root: {
221+
display: "flex",
222+
width: "100%",
223+
flexDirection: "column"
224+
},
225+
container: {
226+
display: "flex",
227+
flexDirection: "column",
228+
},
229+
header: {
230+
display: "flex",
231+
justifyContent: "center",
232+
alignItems: "center",
233+
//textTransform: "uppercase"
234+
}
235+
}))

0 commit comments

Comments
 (0)