@@ -25,6 +25,7 @@ import {
25
25
TemplateInsightsResponse ,
26
26
TemplateParameterUsage ,
27
27
TemplateParameterValue ,
28
+ UserActivityInsightsResponse ,
28
29
UserLatencyInsightsResponse ,
29
30
} from "api/typesGenerated" ;
30
31
import { ComponentProps , ReactNode } from "react" ;
@@ -48,7 +49,11 @@ import Tooltip from "@mui/material/Tooltip";
48
49
import LinkOutlined from "@mui/icons-material/LinkOutlined" ;
49
50
import { InsightsInterval , IntervalMenu } from "./IntervalMenu" ;
50
51
import { WeeklyPreset , WeeklyPresetsMenu } from "./WeeklyPresetsMenu" ;
51
- import { insightsTemplate , insightsUserLatency } from "api/queries/insights" ;
52
+ import {
53
+ insightsTemplate ,
54
+ insightsUserActivity ,
55
+ insightsUserLatency ,
56
+ } from "api/queries/insights" ;
52
57
import { useSearchParams } from "react-router-dom" ;
53
58
54
59
export default function TemplateInsightsPage ( ) {
@@ -93,9 +98,11 @@ export default function TemplateInsightsPage() {
93
98
template_ids : template . id ,
94
99
...getDateRangeFilter ( dateRange ) ,
95
100
} ;
101
+
96
102
const insightsFilter = { ...commonFilters , interval } ;
97
103
const { data : templateInsights } = useQuery ( insightsTemplate ( insightsFilter ) ) ;
98
104
const { data : userLatency } = useQuery ( insightsUserLatency ( commonFilters ) ) ;
105
+ const { data : userActivity } = useQuery ( insightsUserActivity ( commonFilters ) ) ;
99
106
100
107
return (
101
108
< >
@@ -130,6 +137,7 @@ export default function TemplateInsightsPage() {
130
137
}
131
138
templateInsights = { templateInsights }
132
139
userLatency = { userLatency }
140
+ userActivity = { userActivity }
133
141
interval = { interval }
134
142
/>
135
143
</ >
@@ -146,11 +154,13 @@ const getDefaultInterval = (template: Template) => {
146
154
export const TemplateInsightsPageView = ( {
147
155
templateInsights,
148
156
userLatency,
157
+ userActivity,
149
158
controls,
150
159
interval,
151
160
} : {
152
161
templateInsights : TemplateInsightsResponse | undefined ;
153
162
userLatency : UserLatencyInsightsResponse | undefined ;
163
+ userActivity : UserActivityInsightsResponse | undefined ;
154
164
controls : ReactNode ;
155
165
interval : InsightsInterval ;
156
166
} ) => {
@@ -170,7 +180,7 @@ export const TemplateInsightsPageView = ({
170
180
sx = { {
171
181
display : "grid" ,
172
182
gridTemplateColumns : "repeat(3, minmax(0, 1fr))" ,
173
- gridTemplateRows : "440px auto" ,
183
+ gridTemplateRows : "440px 440px auto" ,
174
184
gap : ( theme ) => theme . spacing ( 3 ) ,
175
185
} }
176
186
>
@@ -179,11 +189,12 @@ export const TemplateInsightsPageView = ({
179
189
interval = { interval }
180
190
data = { templateInsights ?. interval_reports }
181
191
/>
182
- < UserLatencyPanel data = { userLatency } />
192
+ < UsersLatencyPanel data = { userLatency } />
183
193
< TemplateUsagePanel
184
- sx = { { gridColumn : "span 3 " } }
194
+ sx = { { gridColumn : "span 2 " } }
185
195
data = { templateInsights ?. report . apps_usage }
186
196
/>
197
+ < UsersActivityPanel data = { userActivity } />
187
198
< TemplateParametersUsagePanel
188
199
sx = { { gridColumn : "span 3" } }
189
200
data = { templateInsights ?. report . parameters_usage }
@@ -227,7 +238,7 @@ const ActiveUsersPanel = ({
227
238
) ;
228
239
} ;
229
240
230
- const UserLatencyPanel = ( {
241
+ const UsersLatencyPanel = ( {
231
242
data,
232
243
...panelProps
233
244
} : PanelProps & { data : UserLatencyInsightsResponse | undefined } ) => {
@@ -287,6 +298,65 @@ const UserLatencyPanel = ({
287
298
) ;
288
299
} ;
289
300
301
+ const UsersActivityPanel = ( {
302
+ data,
303
+ ...panelProps
304
+ } : PanelProps & { data : UserActivityInsightsResponse | undefined } ) => {
305
+ const users = data ?. report . users ;
306
+
307
+ return (
308
+ < Panel { ...panelProps } sx = { { overflowY : "auto" , ...panelProps . sx } } >
309
+ < PanelHeader >
310
+ < PanelTitle sx = { { display : "flex" , alignItems : "center" , gap : 1 } } >
311
+ Activity by user
312
+ < HelpTooltip size = "small" >
313
+ < HelpTooltipTitle > How is activity calculated?</ HelpTooltipTitle >
314
+ < HelpTooltipText >
315
+ When a connection is initiated to a user's workspace they are
316
+ considered an active user. e.g. apps, web terminal, SSH
317
+ </ HelpTooltipText >
318
+ </ HelpTooltip >
319
+ </ PanelTitle >
320
+ </ PanelHeader >
321
+ < PanelContent >
322
+ { ! data && < Loader sx = { { height : "100%" } } /> }
323
+ { users && users . length === 0 && < NoDataAvailable /> }
324
+ { users &&
325
+ users
326
+ . sort ( ( a , b ) => b . seconds - a . seconds )
327
+ . map ( ( row ) => (
328
+ < Box
329
+ key = { row . user_id }
330
+ sx = { {
331
+ display : "flex" ,
332
+ justifyContent : "space-between" ,
333
+ fontSize : 14 ,
334
+ py : 1 ,
335
+ } }
336
+ >
337
+ < Box sx = { { display : "flex" , alignItems : "center" , gap : 1.5 } } >
338
+ < UserAvatar
339
+ username = { row . username }
340
+ avatarURL = { row . avatar_url }
341
+ />
342
+ < Box sx = { { fontWeight : 500 } } > { row . username } </ Box >
343
+ </ Box >
344
+ < Box
345
+ css = { ( theme ) => ( {
346
+ color : theme . palette . text . secondary ,
347
+ fontSize : 13 ,
348
+ textAlign : "right" ,
349
+ } ) }
350
+ >
351
+ { formatTime ( row . seconds ) }
352
+ </ Box >
353
+ </ Box >
354
+ ) ) }
355
+ </ PanelContent >
356
+ </ Panel >
357
+ ) ;
358
+ } ;
359
+
290
360
const TemplateUsagePanel = ( {
291
361
data,
292
362
...panelProps
@@ -303,15 +373,21 @@ const TemplateUsagePanel = ({
303
373
// The API returns a row for each app, even if the user didn't use it.
304
374
const hasDataAvailable = validUsage && validUsage . length > 0 ;
305
375
return (
306
- < Panel { ...panelProps } >
376
+ < Panel { ...panelProps } css = { { overflowY : "auto" } } >
307
377
< PanelHeader >
308
378
< PanelTitle > App & IDE Usage </ PanelTitle >
309
379
</ PanelHeader >
310
380
< PanelContent >
311
- { ! data && < Loader sx = { { height : 200 } } /> }
381
+ { ! data && < Loader sx = { { height : "100%" } } /> }
312
382
{ data && ! hasDataAvailable && < NoDataAvailable sx = { { height : 200 } } /> }
313
383
{ data && hasDataAvailable && (
314
- < Box sx = { { display : "flex" , flexDirection : "column" , gap : 3 } } >
384
+ < Box
385
+ sx = { {
386
+ display : "flex" ,
387
+ flexDirection : "column" ,
388
+ gap : 3 ,
389
+ } }
390
+ >
315
391
{ validUsage
316
392
. sort ( ( a , b ) => b . seconds - a . seconds )
317
393
. map ( ( usage , i ) => {
0 commit comments