Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 3217cb8

Browse files
feat: improve usage visibility (#16134)
- Refactor the DAUs chart for clarity by improving the description and updating its title to better reflect the data. - Add a license consumption chart to the licenses page.
1 parent 08ffcb7 commit 3217cb8

16 files changed

+629
-216
lines changed

site/src/api/api.ts

+13
Original file line numberDiff line numberDiff line change
@@ -2089,6 +2089,19 @@ class ApiMethods {
20892089
return response.data;
20902090
};
20912091

2092+
getInsightsUserStatusCounts = async (
2093+
offset = Math.trunc(new Date().getTimezoneOffset() / 60),
2094+
): Promise<TypesGen.GetUserStatusCountsResponse> => {
2095+
const searchParams = new URLSearchParams({
2096+
tz_offset: offset.toString(),
2097+
});
2098+
const response = await this.axios.get(
2099+
`/api/v2/insights/user-status-counts?${searchParams}`,
2100+
);
2101+
2102+
return response.data;
2103+
};
2104+
20922105
getInsightsTemplate = async (
20932106
params: InsightsTemplateParams,
20942107
): Promise<TypesGen.TemplateInsightsResponse> => {

site/src/api/queries/insights.ts

+14
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { API, type InsightsParams, type InsightsTemplateParams } from "api/api";
2+
import type { GetUserStatusCountsResponse } from "api/typesGenerated";
3+
import { type UseQueryOptions, UseQueryResult } from "react-query";
24

35
export const insightsTemplate = (params: InsightsTemplateParams) => {
46
return {
@@ -20,3 +22,15 @@ export const insightsUserActivity = (params: InsightsParams) => {
2022
queryFn: () => API.getInsightsUserActivity(params),
2123
};
2224
};
25+
26+
export const insightsUserStatusCounts = () => {
27+
return {
28+
queryKey: ["insights", "userStatusCounts"],
29+
queryFn: () => API.getInsightsUserStatusCounts(),
30+
select: (data) => data.status_counts,
31+
} satisfies UseQueryOptions<
32+
GetUserStatusCountsResponse,
33+
unknown,
34+
GetUserStatusCountsResponse["status_counts"]
35+
>;
36+
};

site/src/components/Chart/Chart.stories.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const chartData = [
1919
const chartConfig = {
2020
users: {
2121
label: "Users",
22-
color: "hsl(var(--chart-1))",
22+
color: "hsl(var(--highlight-purple))",
2323
},
2424
} satisfies ChartConfig;
2525

site/src/components/Chart/Chart.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ export const ChartContainer = React.forwardRef<
6666
"[&_.recharts-sector[stroke='#fff']]:stroke-transparent",
6767
"[&_.recharts-sector]:outline-none",
6868
"[&_.recharts-surface]:outline-none",
69+
"[&_.recharts-text]:fill-content-secondary [&_.recharts-text]:font-medium",
70+
"[&_.recharts-cartesian-axis-line]:stroke-[hsl(var(--border-default))]",
6971
className,
7072
)}
7173
{...props}
@@ -195,7 +197,7 @@ export const ChartTooltipContent = React.forwardRef<
195197
<div
196198
ref={ref}
197199
className={cn(
198-
"grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",
200+
"grid min-w-[8rem] items-start gap-1 rounded-lg border border-solid border-border bg-surface-primary px-3 py-2 text-xs shadow-xl",
199201
className,
200202
)}
201203
>

site/src/components/Link/Link.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Slot } from "@radix-ui/react-slot";
1+
import { Slot, Slottable } from "@radix-ui/react-slot";
22
import { type VariantProps, cva } from "class-variance-authority";
33
import { SquareArrowOutUpRightIcon } from "lucide-react";
44
import { forwardRef } from "react";
@@ -38,7 +38,7 @@ export const Link = forwardRef<HTMLAnchorElement, LinkProps>(
3838
ref={ref}
3939
{...props}
4040
>
41-
{children}
41+
<Slottable>{children}</Slottable>
4242
<SquareArrowOutUpRightIcon aria-hidden="true" />
4343
</Comp>
4444
);

site/src/index.css

+4-10
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,8 @@
2828
--border-success: 142 76% 36%;
2929
--border-destructive: 0 84% 60%;
3030
--radius: 0.5rem;
31-
--chart-1: 12 76% 61%;
32-
--chart-2: 173 58% 39%;
33-
--chart-3: 197 37% 24%;
34-
--chart-4: 43 74% 66%;
35-
--chart-5: 27 87% 67%;
31+
--highlight-purple: 262 83% 58%;
32+
--highlight-green: 143 64% 24%;
3633
--border: 240 5.9% 90%;
3734
--input: 240 5.9% 90%;
3835
--ring: 240 10% 3.9%;
@@ -59,11 +56,8 @@
5956
--border-default: 240 4% 16%;
6057
--border-success: 142 76% 36%;
6158
--border-destructive: 0 91% 71%;
62-
--chart-1: 220 70% 50%;
63-
--chart-2: 160 60% 45%;
64-
--chart-3: 30 80% 55%;
65-
--chart-4: 280 65% 60%;
66-
--chart-5: 340 75% 55%;
59+
--highlight-purple: 252 95% 85%;
60+
--highlight-green: 141 79% 85%;
6761
--border: 240 3.7% 15.9%;
6862
--input: 240 3.7% 15.9%;
6963
--ring: 240 4.9% 83.9%;

site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPage.tsx

+3-5
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,9 @@ import { GeneralSettingsPageView } from "./GeneralSettingsPageView";
1111

1212
const GeneralSettingsPage: FC = () => {
1313
const { deploymentConfig } = useDeploymentSettings();
14-
const deploymentDAUsQuery = useQuery(deploymentDAUs());
1514
const safeExperimentsQuery = useQuery(availableExperiments());
1615

1716
const { metadata } = useEmbeddedMetadata();
18-
const entitlementsQuery = useQuery(entitlements(metadata.entitlements));
1917
const enabledExperimentsQuery = useQuery(experiments(metadata.experiments));
2018

2119
const safeExperiments = safeExperimentsQuery.data?.safe ?? [];
@@ -24,16 +22,16 @@ const GeneralSettingsPage: FC = () => {
2422
return !safeExperiments.includes(exp);
2523
}) ?? [];
2624

25+
const { data: dailyActiveUsers } = useQuery(deploymentDAUs());
26+
2727
return (
2828
<>
2929
<Helmet>
3030
<title>{pageTitle("General Settings")}</title>
3131
</Helmet>
3232
<GeneralSettingsPageView
3333
deploymentOptions={deploymentConfig.options}
34-
deploymentDAUs={deploymentDAUsQuery.data}
35-
deploymentDAUsError={deploymentDAUsQuery.error}
36-
entitlements={entitlementsQuery.data}
34+
dailyActiveUsers={dailyActiveUsers}
3735
invalidExperiments={invalidExperiments}
3836
safeExperiments={safeExperiments}
3937
/>
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
import type { Meta, StoryObj } from "@storybook/react";
2-
import {
3-
MockDeploymentDAUResponse,
4-
MockEntitlementsWithUserLimit,
5-
mockApiError,
6-
} from "testHelpers/entities";
2+
import { MockDeploymentDAUResponse } from "testHelpers/entities";
73
import { GeneralSettingsPageView } from "./GeneralSettingsPageView";
84

95
const meta: Meta<typeof GeneralSettingsPageView> = {
@@ -39,10 +35,9 @@ const meta: Meta<typeof GeneralSettingsPageView> = {
3935
hidden: false,
4036
},
4137
],
42-
deploymentDAUs: MockDeploymentDAUResponse,
38+
dailyActiveUsers: MockDeploymentDAUResponse,
4339
invalidExperiments: [],
4440
safeExperiments: [],
45-
entitlements: undefined,
4641
},
4742
};
4843

@@ -51,21 +46,6 @@ type Story = StoryObj<typeof GeneralSettingsPageView>;
5146

5247
export const Page: Story = {};
5348

54-
export const NoDAUs: Story = {
55-
args: {
56-
deploymentDAUs: undefined,
57-
},
58-
};
59-
60-
export const DAUError: Story = {
61-
args: {
62-
deploymentDAUs: undefined,
63-
deploymentDAUsError: mockApiError({
64-
message: "Error fetching DAUs.",
65-
}),
66-
},
67-
};
68-
6949
export const allExperimentsEnabled: Story = {
7050
args: {
7151
deploymentOptions: [
@@ -137,74 +117,3 @@ export const invalidExperimentsEnabled: Story = {
137117
invalidExperiments: ["invalid"],
138118
},
139119
};
140-
141-
export const WithLicenseUtilization: Story = {
142-
args: {
143-
entitlements: {
144-
...MockEntitlementsWithUserLimit,
145-
features: {
146-
...MockEntitlementsWithUserLimit.features,
147-
user_limit: {
148-
...MockEntitlementsWithUserLimit.features.user_limit,
149-
enabled: true,
150-
actual: 75,
151-
limit: 100,
152-
entitlement: "entitled",
153-
},
154-
},
155-
},
156-
},
157-
};
158-
159-
export const HighLicenseUtilization: Story = {
160-
args: {
161-
entitlements: {
162-
...MockEntitlementsWithUserLimit,
163-
features: {
164-
...MockEntitlementsWithUserLimit.features,
165-
user_limit: {
166-
...MockEntitlementsWithUserLimit.features.user_limit,
167-
enabled: true,
168-
actual: 95,
169-
limit: 100,
170-
entitlement: "entitled",
171-
},
172-
},
173-
},
174-
},
175-
};
176-
177-
export const ExceedsLicenseUtilization: Story = {
178-
args: {
179-
entitlements: {
180-
...MockEntitlementsWithUserLimit,
181-
features: {
182-
...MockEntitlementsWithUserLimit.features,
183-
user_limit: {
184-
...MockEntitlementsWithUserLimit.features.user_limit,
185-
enabled: true,
186-
actual: 100,
187-
limit: 95,
188-
entitlement: "entitled",
189-
},
190-
},
191-
},
192-
},
193-
};
194-
export const NoLicenseLimit: Story = {
195-
args: {
196-
entitlements: {
197-
...MockEntitlementsWithUserLimit,
198-
features: {
199-
...MockEntitlementsWithUserLimit.features,
200-
user_limit: {
201-
...MockEntitlementsWithUserLimit.features.user_limit,
202-
enabled: false,
203-
actual: 0,
204-
limit: 0,
205-
entitlement: "entitled",
206-
},
207-
},
208-
},
209-
},
210-
};

site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx

+9-60
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,32 @@
11
import AlertTitle from "@mui/material/AlertTitle";
2-
import LinearProgress from "@mui/material/LinearProgress";
32
import type {
43
DAUsResponse,
54
Entitlements,
65
Experiments,
76
SerpentOption,
87
} from "api/typesGenerated";
9-
import {
10-
ActiveUserChart,
11-
ActiveUsersTitle,
12-
} from "components/ActiveUserChart/ActiveUserChart";
13-
import { ErrorAlert } from "components/Alert/ErrorAlert";
148
import { SettingsHeader } from "components/SettingsHeader/SettingsHeader";
159
import { Stack } from "components/Stack/Stack";
1610
import type { FC } from "react";
1711
import { useDeploymentOptions } from "utils/deployOptions";
1812
import { docs } from "utils/docs";
1913
import { Alert } from "../../../components/Alert/Alert";
2014
import OptionsTable from "../OptionsTable";
21-
import { ChartSection } from "./ChartSection";
15+
import { UserEngagementChart } from "./UserEngagementChart";
2216

2317
export type GeneralSettingsPageViewProps = {
2418
deploymentOptions: SerpentOption[];
25-
deploymentDAUs?: DAUsResponse;
26-
deploymentDAUsError: unknown;
27-
entitlements: Entitlements | undefined;
19+
dailyActiveUsers: DAUsResponse | undefined;
2820
readonly invalidExperiments: Experiments | string[];
2921
readonly safeExperiments: Experiments | string[];
3022
};
3123

3224
export const GeneralSettingsPageView: FC<GeneralSettingsPageViewProps> = ({
3325
deploymentOptions,
34-
deploymentDAUs,
35-
deploymentDAUsError,
36-
entitlements,
26+
dailyActiveUsers,
3727
safeExperiments,
3828
invalidExperiments,
3929
}) => {
40-
const licenseUtilizationPercentage =
41-
entitlements?.features?.user_limit?.actual &&
42-
entitlements?.features?.user_limit?.limit
43-
? entitlements.features.user_limit.actual /
44-
entitlements.features.user_limit.limit
45-
: undefined;
4630
return (
4731
<>
4832
<SettingsHeader
@@ -51,47 +35,12 @@ export const GeneralSettingsPageView: FC<GeneralSettingsPageViewProps> = ({
5135
docsHref={docs("/admin/setup")}
5236
/>
5337
<Stack spacing={4}>
54-
{Boolean(deploymentDAUsError) && (
55-
<ErrorAlert error={deploymentDAUsError} />
56-
)}
57-
{deploymentDAUs && (
58-
<div css={{ marginBottom: 24, height: 200 }}>
59-
<ChartSection title={<ActiveUsersTitle interval="day" />}>
60-
<ActiveUserChart data={deploymentDAUs.entries} interval="day" />
61-
</ChartSection>
62-
</div>
63-
)}
64-
{licenseUtilizationPercentage && (
65-
<ChartSection title="License Utilization">
66-
<LinearProgress
67-
variant="determinate"
68-
value={Math.min(licenseUtilizationPercentage * 100, 100)}
69-
color={
70-
licenseUtilizationPercentage < 0.9
71-
? "primary"
72-
: licenseUtilizationPercentage < 1
73-
? "warning"
74-
: "error"
75-
}
76-
css={{
77-
height: 24,
78-
borderRadius: 4,
79-
marginBottom: 8,
80-
}}
81-
/>
82-
<span
83-
css={{
84-
fontSize: "0.75rem",
85-
display: "block",
86-
textAlign: "right",
87-
}}
88-
>
89-
{Math.round(licenseUtilizationPercentage * 100)}% used (
90-
{entitlements!.features.user_limit.actual}/
91-
{entitlements!.features.user_limit.limit} users)
92-
</span>
93-
</ChartSection>
94-
)}
38+
<UserEngagementChart
39+
data={dailyActiveUsers?.entries.map((i) => ({
40+
date: i.date,
41+
users: i.amount,
42+
}))}
43+
/>
9544
{invalidExperiments.length > 0 && (
9645
<Alert severity="warning">
9746
<AlertTitle>Invalid experiments in use:</AlertTitle>

0 commit comments

Comments
 (0)