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