Skip to content

Commit 786f437

Browse files
committed
feat(site): add user activity on template insights
1 parent bdb9954 commit 786f437

File tree

3 files changed

+99
-8
lines changed

3 files changed

+99
-8
lines changed

site/src/api/api.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1488,6 +1488,14 @@ export const getInsightsUserLatency = async (
14881488
return response.data;
14891489
};
14901490

1491+
export const getInsightsUserActivity = async (
1492+
filters: InsightsParams,
1493+
): Promise<TypesGen.UserActivityInsightsResponse> => {
1494+
const params = new URLSearchParams(filters);
1495+
const response = await axios.get(`/api/v2/insights/user-activity?${params}`);
1496+
return response.data;
1497+
};
1498+
14911499
export type InsightsTemplateParams = InsightsParams & {
14921500
interval: "day" | "week";
14931501
};

site/src/api/queries/insights.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,10 @@ export const insightsUserLatency = (params: API.InsightsParams) => {
1313
queryFn: () => API.getInsightsUserLatency(params),
1414
};
1515
};
16+
17+
export const insightsUserActivity = (params: API.InsightsParams) => {
18+
return {
19+
queryKey: ["insights", "userActivity", params.template_ids, params],
20+
queryFn: () => API.getInsightsUserActivity(params),
21+
};
22+
};

site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx

Lines changed: 84 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
TemplateInsightsResponse,
2727
TemplateParameterUsage,
2828
TemplateParameterValue,
29+
UserActivityInsightsResponse,
2930
UserLatencyInsightsResponse,
3031
} from "api/typesGenerated";
3132
import { ComponentProps, ReactNode } from "react";
@@ -41,7 +42,11 @@ import Tooltip from "@mui/material/Tooltip";
4142
import LinkOutlined from "@mui/icons-material/LinkOutlined";
4243
import { InsightsInterval, IntervalMenu } from "./IntervalMenu";
4344
import { WeekPicker, numberOfWeeksOptions } from "./WeekPicker";
44-
import { insightsTemplate, insightsUserLatency } from "api/queries/insights";
45+
import {
46+
insightsTemplate,
47+
insightsUserActivity,
48+
insightsUserLatency,
49+
} from "api/queries/insights";
4550
import { useSearchParams } from "react-router-dom";
4651

4752
const DEFAULT_NUMBER_OF_WEEKS = numberOfWeeksOptions[0];
@@ -65,9 +70,11 @@ export default function TemplateInsightsPage() {
6570
template_ids: template.id,
6671
...getDateRangeFilter(dateRange),
6772
};
73+
6874
const insightsFilter = { ...commonFilters, interval };
6975
const { data: templateInsights } = useQuery(insightsTemplate(insightsFilter));
7076
const { data: userLatency } = useQuery(insightsUserLatency(commonFilters));
77+
const { data: userActivity } = useQuery(insightsUserActivity(commonFilters));
7178

7279
return (
7380
<>
@@ -97,6 +104,7 @@ export default function TemplateInsightsPage() {
97104
}
98105
templateInsights={templateInsights}
99106
userLatency={userLatency}
107+
userActivity={userActivity}
100108
interval={interval}
101109
/>
102110
</>
@@ -137,11 +145,13 @@ const getDateRange = (
137145
export const TemplateInsightsPageView = ({
138146
templateInsights,
139147
userLatency,
148+
userActivity,
140149
controls,
141150
interval,
142151
}: {
143152
templateInsights: TemplateInsightsResponse | undefined;
144153
userLatency: UserLatencyInsightsResponse | undefined;
154+
userActivity: UserActivityInsightsResponse | undefined;
145155
controls: ReactNode;
146156
interval: InsightsInterval;
147157
}) => {
@@ -161,7 +171,7 @@ export const TemplateInsightsPageView = ({
161171
sx={{
162172
display: "grid",
163173
gridTemplateColumns: "repeat(3, minmax(0, 1fr))",
164-
gridTemplateRows: "440px auto",
174+
gridTemplateRows: "440px 440px auto",
165175
gap: (theme) => theme.spacing(3),
166176
}}
167177
>
@@ -170,11 +180,12 @@ export const TemplateInsightsPageView = ({
170180
interval={interval}
171181
data={templateInsights?.interval_reports}
172182
/>
173-
<UserLatencyPanel data={userLatency} />
183+
<UsersLatencyPanel data={userLatency} />
174184
<TemplateUsagePanel
175-
sx={{ gridColumn: "span 3" }}
185+
sx={{ gridColumn: "span 2" }}
176186
data={templateInsights?.report?.apps_usage}
177187
/>
188+
<UsersActivityPanel data={userActivity} />
178189
<TemplateParametersUsagePanel
179190
sx={{ gridColumn: "span 3" }}
180191
data={templateInsights?.report?.parameters_usage}
@@ -216,7 +227,7 @@ const ActiveUsersPanel = ({
216227
);
217228
};
218229

219-
const UserLatencyPanel = ({
230+
const UsersLatencyPanel = ({
220231
data,
221232
...panelProps
222233
}: PanelProps & { data: UserLatencyInsightsResponse | undefined }) => {
@@ -276,6 +287,65 @@ const UserLatencyPanel = ({
276287
);
277288
};
278289

290+
const UsersActivityPanel = ({
291+
data,
292+
...panelProps
293+
}: PanelProps & { data: UserActivityInsightsResponse | undefined }) => {
294+
const users = data?.report.users;
295+
296+
return (
297+
<Panel {...panelProps} sx={{ overflowY: "auto", ...panelProps.sx }}>
298+
<PanelHeader>
299+
<PanelTitle sx={{ display: "flex", alignItems: "center", gap: 1 }}>
300+
Activity by user
301+
<HelpTooltip size="small">
302+
<HelpTooltipTitle>How is activity calculated?</HelpTooltipTitle>
303+
<HelpTooltipText>
304+
When a connection is initiated to a user&apos;s workspace they are
305+
considered an active user. e.g. apps, web terminal, SSH
306+
</HelpTooltipText>
307+
</HelpTooltip>
308+
</PanelTitle>
309+
</PanelHeader>
310+
<PanelContent>
311+
{!data && <Loader sx={{ height: "100%" }} />}
312+
{users && users.length === 0 && <NoDataAvailable />}
313+
{users &&
314+
users
315+
.sort((a, b) => b.seconds - a.seconds)
316+
.map((row) => (
317+
<Box
318+
key={row.user_id}
319+
sx={{
320+
display: "flex",
321+
justifyContent: "space-between",
322+
fontSize: 14,
323+
py: 1,
324+
}}
325+
>
326+
<Box sx={{ display: "flex", alignItems: "center", gap: 1.5 }}>
327+
<UserAvatar
328+
username={row.username}
329+
avatarURL={row.avatar_url}
330+
/>
331+
<Box sx={{ fontWeight: 500 }}>{row.username}</Box>
332+
</Box>
333+
<Box
334+
css={(theme) => ({
335+
color: theme.palette.text.secondary,
336+
fontSize: 13,
337+
textAlign: "right",
338+
})}
339+
>
340+
{formatTime(row.seconds)}
341+
</Box>
342+
</Box>
343+
))}
344+
</PanelContent>
345+
</Panel>
346+
);
347+
};
348+
279349
const TemplateUsagePanel = ({
280350
data,
281351
...panelProps
@@ -292,15 +362,21 @@ const TemplateUsagePanel = ({
292362
// The API returns a row for each app, even if the user didn't use it.
293363
const hasDataAvailable = validUsage && validUsage.length > 0;
294364
return (
295-
<Panel {...panelProps}>
365+
<Panel {...panelProps} css={{ overflowY: "auto" }}>
296366
<PanelHeader>
297367
<PanelTitle>App & IDE Usage</PanelTitle>
298368
</PanelHeader>
299369
<PanelContent>
300-
{!data && <Loader sx={{ height: 200 }} />}
370+
{!data && <Loader sx={{ height: "100%" }} />}
301371
{data && !hasDataAvailable && <NoDataAvailable sx={{ height: 200 }} />}
302372
{data && hasDataAvailable && (
303-
<Box sx={{ display: "flex", flexDirection: "column", gap: 3 }}>
373+
<Box
374+
sx={{
375+
display: "flex",
376+
flexDirection: "column",
377+
gap: 3,
378+
}}
379+
>
304380
{validUsage
305381
.sort((a, b) => b.seconds - a.seconds)
306382
.map((usage, i) => {

0 commit comments

Comments
 (0)