feat: show user limit on active users chart (#10101)

This commit is contained in:
Marcin Tojek
2023-10-09 13:38:41 +02:00
committed by GitHub
parent 38bb854c8b
commit c6cf719f6c
9 changed files with 118 additions and 7 deletions
+1
View File
@@ -54,6 +54,7 @@
"canvas": "2.11.0",
"chart.js": "4.4.0",
"chartjs-adapter-date-fns": "3.0.0",
"chartjs-plugin-annotation": "3.0.1",
"chroma-js": "2.4.2",
"color-convert": "2.0.1",
"cron-parser": "4.9.0",
+11
View File
@@ -78,6 +78,9 @@ dependencies:
chartjs-adapter-date-fns:
specifier: 3.0.0
version: 3.0.0(chart.js@4.4.0)(date-fns@2.30.0)
chartjs-plugin-annotation:
specifier: 3.0.1
version: 3.0.1(chart.js@4.4.0)
chroma-js:
specifier: 2.4.2
version: 2.4.2
@@ -7179,6 +7182,14 @@ packages:
date-fns: 2.30.0
dev: false
/chartjs-plugin-annotation@3.0.1(chart.js@4.4.0):
resolution: {integrity: sha512-hlIrXXKqSDgb+ZjVYHefmlZUXK8KbkCPiynSVrTb/HjTMkT62cOInaT1NTQCKtxKKOm9oHp958DY3RTAFKtkHg==}
peerDependencies:
chart.js: '>=4.0.0'
dependencies:
chart.js: 4.4.0
dev: false
/chokidar@3.5.3:
resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
engines: {node: '>= 8.10.0'}
@@ -24,6 +24,7 @@ import {
import dayjs from "dayjs";
import { FC } from "react";
import { Line } from "react-chartjs-2";
import annotationPlugin from "chartjs-plugin-annotation";
ChartJS.register(
CategoryScale,
@@ -35,16 +36,21 @@ ChartJS.register(
Title,
Tooltip,
Legend,
annotationPlugin,
);
const USER_LIMIT_DISPLAY_THRESHOLD = 60;
export interface ActiveUserChartProps {
data: { date: string; amount: number }[];
interval: "day" | "week";
userLimit: number | undefined;
}
export const ActiveUserChart: FC<ActiveUserChartProps> = ({
data,
interval,
userLimit,
}) => {
const theme: Theme = useTheme();
@@ -57,6 +63,24 @@ export const ActiveUserChart: FC<ActiveUserChartProps> = ({
const options: ChartOptions<"line"> = {
responsive: true,
plugins: {
annotation: {
annotations: [
{
type: "line",
scaleID: "y",
display: shouldDisplayUserLimit(userLimit, chartData),
value: userLimit,
borderColor: theme.palette.secondary.contrastText,
borderWidth: 5,
label: {
content: "User limit",
color: theme.palette.primary.contrastText,
display: true,
font: { weight: "normal" },
},
},
],
},
legend: {
display: false,
},
@@ -127,3 +151,15 @@ export const ActiveUsersTitle = () => {
</Box>
);
};
function shouldDisplayUserLimit(
userLimit: number | undefined,
activeUsers: number[],
): boolean {
if (!userLimit || activeUsers.length === 0) {
return false;
}
return (
Math.max(...activeUsers) >= (userLimit * USER_LIMIT_DISPLAY_THRESHOLD) / 100
);
}
@@ -5,10 +5,12 @@ import { pageTitle } from "utils/page";
import { GeneralSettingsPageView } from "./GeneralSettingsPageView";
import { useQuery } from "react-query";
import { deploymentDAUs } from "api/queries/deployment";
import { entitlements } from "api/queries/entitlements";
const GeneralSettingsPage: FC = () => {
const { deploymentValues } = useDeploySettings();
const deploymentDAUsQuery = useQuery(deploymentDAUs());
const entitlementsQuery = useQuery(entitlements());
return (
<>
@@ -19,6 +21,7 @@ const GeneralSettingsPage: FC = () => {
deploymentOptions={deploymentValues.options}
deploymentDAUs={deploymentDAUsQuery.data}
deploymentDAUsError={deploymentDAUsQuery.error}
entitlements={entitlementsQuery.data}
/>
</>
);
@@ -1,5 +1,9 @@
import { Meta, StoryObj } from "@storybook/react";
import { mockApiError, MockDeploymentDAUResponse } from "testHelpers/entities";
import {
mockApiError,
MockDeploymentDAUResponse,
MockEntitlementsWithUserLimit,
} from "testHelpers/entities";
import { GeneralSettingsPageView } from "./GeneralSettingsPageView";
const meta: Meta<typeof GeneralSettingsPageView> = {
@@ -44,6 +48,13 @@ type Story = StoryObj<typeof GeneralSettingsPageView>;
export const Page: Story = {};
export const WithUserLimit: Story = {
args: {
deploymentDAUs: MockDeploymentDAUResponse,
entitlements: MockEntitlementsWithUserLimit,
},
};
export const NoDAUs: Story = {
args: {
deploymentDAUs: undefined,
@@ -1,5 +1,5 @@
import Box from "@mui/material/Box";
import { ClibaseOption, DAUsResponse } from "api/typesGenerated";
import { ClibaseOption, DAUsResponse, Entitlements } from "api/typesGenerated";
import { ErrorAlert } from "components/Alert/ErrorAlert";
import {
ActiveUserChart,
@@ -16,11 +16,13 @@ export type GeneralSettingsPageViewProps = {
deploymentOptions: ClibaseOption[];
deploymentDAUs?: DAUsResponse;
deploymentDAUsError: unknown;
entitlements: Entitlements | undefined;
};
export const GeneralSettingsPageView = ({
deploymentOptions,
deploymentDAUs,
deploymentDAUsError,
entitlements,
}: GeneralSettingsPageViewProps): JSX.Element => {
return (
<>
@@ -36,7 +38,15 @@ export const GeneralSettingsPageView = ({
{deploymentDAUs && (
<Box height={200} sx={{ mb: 3 }}>
<ChartSection title={<ActiveUsersTitle />}>
<ActiveUserChart data={deploymentDAUs.entries} interval="day" />
<ActiveUserChart
data={deploymentDAUs.entries}
interval="day"
userLimit={
entitlements?.features.user_limit.enabled
? entitlements?.features.user_limit.limit
: undefined
}
/>
</ChartSection>
</Box>
)}
@@ -1,5 +1,6 @@
import type { Meta, StoryObj } from "@storybook/react";
import { TemplateInsightsPageView } from "./TemplateInsightsPage";
import { MockEntitlementsWithUserLimit } from "testHelpers/entities";
const meta: Meta<typeof TemplateInsightsPageView> = {
title: "pages/TemplateInsightsPageView",
@@ -515,7 +516,7 @@ export const Loaded: Story = {
end_time: "2023-07-25T00:00:00Z",
template_ids: ["0d286645-29aa-4eaf-9b52-cc5d2740c90b"],
interval: "day",
active_users: 11,
active_users: 16,
},
],
},
@@ -861,3 +862,11 @@ export const Loaded: Story = {
},
},
};
export const LoadedWithUserLimit: Story = {
...Loaded,
args: {
...Loaded.args,
entitlements: MockEntitlementsWithUserLimit,
},
};
@@ -21,6 +21,7 @@ import { Helmet } from "react-helmet-async";
import { getTemplatePageTitle } from "../utils";
import { Loader } from "components/Loader/Loader";
import {
Entitlements,
Template,
TemplateAppUsage,
TemplateInsightsResponse,
@@ -48,6 +49,7 @@ import {
insightsUserLatency,
} from "api/queries/insights";
import { useSearchParams } from "react-router-dom";
import { entitlements } from "api/queries/entitlements";
const DEFAULT_NUMBER_OF_WEEKS = numberOfWeeksOptions[0];
@@ -75,6 +77,7 @@ export default function TemplateInsightsPage() {
const { data: templateInsights } = useQuery(insightsTemplate(insightsFilter));
const { data: userLatency } = useQuery(insightsUserLatency(commonFilters));
const { data: userActivity } = useQuery(insightsUserActivity(commonFilters));
const { data: entitlementsQuery } = useQuery(entitlements());
return (
<>
@@ -106,6 +109,7 @@ export default function TemplateInsightsPage() {
userLatency={userLatency}
userActivity={userActivity}
interval={interval}
entitlements={entitlementsQuery}
/>
</>
);
@@ -146,12 +150,14 @@ export const TemplateInsightsPageView = ({
templateInsights,
userLatency,
userActivity,
entitlements,
controls,
interval,
}: {
templateInsights: TemplateInsightsResponse | undefined;
userLatency: UserLatencyInsightsResponse | undefined;
userActivity: UserActivityInsightsResponse | undefined;
entitlements: Entitlements | undefined;
controls: ReactNode;
interval: InsightsInterval;
}) => {
@@ -178,6 +184,11 @@ export const TemplateInsightsPageView = ({
<ActiveUsersPanel
sx={{ gridColumn: "span 2" }}
interval={interval}
userLimit={
entitlements?.features.user_limit.enabled
? entitlements?.features.user_limit.limit
: undefined
}
data={templateInsights?.interval_reports}
/>
<UsersLatencyPanel data={userLatency} />
@@ -198,10 +209,12 @@ export const TemplateInsightsPageView = ({
const ActiveUsersPanel = ({
data,
interval,
userLimit,
...panelProps
}: PanelProps & {
data: TemplateInsightsResponse["interval_reports"] | undefined;
interval: InsightsInterval;
userLimit: number | undefined;
}) => {
return (
<Panel {...panelProps}>
@@ -216,6 +229,7 @@ const ActiveUsersPanel = ({
{data && data.length > 0 && (
<ActiveUserChart
interval={interval}
userLimit={userLimit}
data={data.map((d) => ({
amount: d.active_users,
date: d.start_time,
+19 -3
View File
@@ -30,9 +30,9 @@ export const MockTemplateDAUResponse: TypesGen.DAUsResponse = {
export const MockDeploymentDAUResponse: TypesGen.DAUsResponse = {
tz_hour_offset: 0,
entries: [
{ date: "2022-08-27T00:00:00Z", amount: 1 },
{ date: "2022-08-29T00:00:00Z", amount: 2 },
{ date: "2022-08-30T00:00:00Z", amount: 1 },
{ date: "2022-08-27T00:00:00Z", amount: 10 },
{ date: "2022-08-29T00:00:00Z", amount: 22 },
{ date: "2022-08-30T00:00:00Z", amount: 14 },
],
};
export const MockSessionToken: TypesGen.LoginWithPasswordResponse = {
@@ -1925,6 +1925,22 @@ export const MockEntitlementsWithScheduling: TypesGen.Entitlements = {
}),
};
export const MockEntitlementsWithUserLimit: TypesGen.Entitlements = {
errors: [],
warnings: [],
has_license: true,
require_telemetry: false,
trial: false,
refreshed_at: "2022-05-20T16:45:57.122Z",
features: withDefaultFeatures({
user_limit: {
enabled: true,
entitlement: "entitled",
limit: 25,
},
}),
};
export const MockExperiments: TypesGen.Experiment[] = ["moons"];
export const MockAuditLog: TypesGen.AuditLog = {