Skip to content

Commit c729580

Browse files
committed
feat(site): add user activity on template insights
1 parent 6d33f90 commit c729580

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
@@ -1485,6 +1485,14 @@ export const getInsightsUserLatency = async (
14851485
return response.data;
14861486
};
14871487

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

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
@@ -25,6 +25,7 @@ import {
2525
TemplateInsightsResponse,
2626
TemplateParameterUsage,
2727
TemplateParameterValue,
28+
UserActivityInsightsResponse,
2829
UserLatencyInsightsResponse,
2930
} from "api/typesGenerated";
3031
import { ComponentProps, ReactNode } from "react";
@@ -48,7 +49,11 @@ import Tooltip from "@mui/material/Tooltip";
4849
import LinkOutlined from "@mui/icons-material/LinkOutlined";
4950
import { InsightsInterval, IntervalMenu } from "./IntervalMenu";
5051
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";
5257
import { useSearchParams } from "react-router-dom";
5358

5459
export default function TemplateInsightsPage() {
@@ -93,9 +98,11 @@ export default function TemplateInsightsPage() {
9398
template_ids: template.id,
9499
...getDateRangeFilter(dateRange),
95100
};
101+
96102
const insightsFilter = { ...commonFilters, interval };
97103
const { data: templateInsights } = useQuery(insightsTemplate(insightsFilter));
98104
const { data: userLatency } = useQuery(insightsUserLatency(commonFilters));
105+
const { data: userActivity } = useQuery(insightsUserActivity(commonFilters));
99106

100107
return (
101108
<>
@@ -130,6 +137,7 @@ export default function TemplateInsightsPage() {
130137
}
131138
templateInsights={templateInsights}
132139
userLatency={userLatency}
140+
userActivity={userActivity}
133141
interval={interval}
134142
/>
135143
</>
@@ -146,11 +154,13 @@ const getDefaultInterval = (template: Template) => {
146154
export const TemplateInsightsPageView = ({
147155
templateInsights,
148156
userLatency,
157+
userActivity,
149158
controls,
150159
interval,
151160
}: {
152161
templateInsights: TemplateInsightsResponse | undefined;
153162
userLatency: UserLatencyInsightsResponse | undefined;
163+
userActivity: UserActivityInsightsResponse | undefined;
154164
controls: ReactNode;
155165
interval: InsightsInterval;
156166
}) => {
@@ -170,7 +180,7 @@ export const TemplateInsightsPageView = ({
170180
sx={{
171181
display: "grid",
172182
gridTemplateColumns: "repeat(3, minmax(0, 1fr))",
173-
gridTemplateRows: "440px auto",
183+
gridTemplateRows: "440px 440px auto",
174184
gap: (theme) => theme.spacing(3),
175185
}}
176186
>
@@ -179,11 +189,12 @@ export const TemplateInsightsPageView = ({
179189
interval={interval}
180190
data={templateInsights?.interval_reports}
181191
/>
182-
<UserLatencyPanel data={userLatency} />
192+
<UsersLatencyPanel data={userLatency} />
183193
<TemplateUsagePanel
184-
sx={{ gridColumn: "span 3" }}
194+
sx={{ gridColumn: "span 2" }}
185195
data={templateInsights?.report.apps_usage}
186196
/>
197+
<UsersActivityPanel data={userActivity} />
187198
<TemplateParametersUsagePanel
188199
sx={{ gridColumn: "span 3" }}
189200
data={templateInsights?.report.parameters_usage}
@@ -227,7 +238,7 @@ const ActiveUsersPanel = ({
227238
);
228239
};
229240

230-
const UserLatencyPanel = ({
241+
const UsersLatencyPanel = ({
231242
data,
232243
...panelProps
233244
}: PanelProps & { data: UserLatencyInsightsResponse | undefined }) => {
@@ -287,6 +298,65 @@ const UserLatencyPanel = ({
287298
);
288299
};
289300

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&apos;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+
290360
const TemplateUsagePanel = ({
291361
data,
292362
...panelProps
@@ -303,15 +373,21 @@ const TemplateUsagePanel = ({
303373
// The API returns a row for each app, even if the user didn't use it.
304374
const hasDataAvailable = validUsage && validUsage.length > 0;
305375
return (
306-
<Panel {...panelProps}>
376+
<Panel {...panelProps} css={{ overflowY: "auto" }}>
307377
<PanelHeader>
308378
<PanelTitle>App & IDE Usage</PanelTitle>
309379
</PanelHeader>
310380
<PanelContent>
311-
{!data && <Loader sx={{ height: 200 }} />}
381+
{!data && <Loader sx={{ height: "100%" }} />}
312382
{data && !hasDataAvailable && <NoDataAvailable sx={{ height: 200 }} />}
313383
{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+
>
315391
{validUsage
316392
.sort((a, b) => b.seconds - a.seconds)
317393
.map((usage, i) => {

0 commit comments

Comments
 (0)