From 98b583e6448526652be3e424730770f797e30ba2 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Fri, 6 Oct 2023 11:41:49 +0200 Subject: [PATCH 1/6] WIP we have chart --- .../src/components/ActiveUserChart/ActiveUserChart.tsx | 10 ++++++++++ .../GeneralSettingsPage/GeneralSettingsPage.tsx | 3 +++ .../GeneralSettingsPage/GeneralSettingsPageView.tsx | 6 ++++-- .../TemplateInsightsPage/TemplateInsightsPage.tsx | 10 ++++++++++ 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/site/src/components/ActiveUserChart/ActiveUserChart.tsx b/site/src/components/ActiveUserChart/ActiveUserChart.tsx index c4471356fcb11..921f06b363722 100644 --- a/site/src/components/ActiveUserChart/ActiveUserChart.tsx +++ b/site/src/components/ActiveUserChart/ActiveUserChart.tsx @@ -40,11 +40,13 @@ ChartJS.register( export interface ActiveUserChartProps { data: { date: string; amount: number }[]; interval: "day" | "week"; + userLimit: number | undefined; } export const ActiveUserChart: FC = ({ data, interval, + userLimit, }) => { const theme: Theme = useTheme(); @@ -106,6 +108,14 @@ export const ActiveUserChart: FC = ({ backgroundColor: theme.palette.info.dark, fill: "origin", }, + { + label: "User limit", + data: data.map((_) => userLimit), + pointBackgroundColor: theme.palette.warning.light, + pointBorderColor: theme.palette.warning.light, + borderColor: theme.palette.warning.light, + fill: false, + } ], }} options={options} diff --git a/site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPage.tsx b/site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPage.tsx index e7684b15babe0..4cf7c2cfa260b 100644 --- a/site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPage.tsx +++ b/site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPage.tsx @@ -5,10 +5,12 @@ import { pageTitle } from "utils/page"; import { GeneralSettingsPageView } from "./GeneralSettingsPageView"; import { useQuery } from "@tanstack/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} /> ); diff --git a/site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx b/site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx index 03c4e44a93195..856ac962d132d 100644 --- a/site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx +++ b/site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx @@ -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,7 @@ export const GeneralSettingsPageView = ({ {deploymentDAUs && ( }> - + )} diff --git a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx index 5b58c5d25aac7..a7fe6265f3e0c 100644 --- a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx +++ b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx @@ -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,7 @@ export const TemplateInsightsPageView = ({ @@ -198,10 +205,12 @@ export const TemplateInsightsPageView = ({ const ActiveUsersPanel = ({ data, interval, + userLimit, ...panelProps }: PanelProps & { data: TemplateInsightsResponse["interval_reports"] | undefined; interval: InsightsInterval; + userLimit: number | undefined; }) => { return ( @@ -216,6 +225,7 @@ const ActiveUsersPanel = ({ {data && data.length > 0 && ( ({ amount: d.active_users, date: d.start_time, From 0c14170c1fde685a0e36237952493251172ea221 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Fri, 6 Oct 2023 12:07:09 +0200 Subject: [PATCH 2/6] Use annotation plugin --- site/package.json | 1 + site/pnpm-lock.yaml | 11 ++++++++ .../ActiveUserChart/ActiveUserChart.tsx | 27 +++++++++++++------ .../GeneralSettingsPageView.tsx | 6 ++++- .../TemplateInsightsPage.tsx | 2 +- 5 files changed, 37 insertions(+), 10 deletions(-) diff --git a/site/package.json b/site/package.json index f95d9b0a9acf8..0f23fdf785bbc 100644 --- a/site/package.json +++ b/site/package.json @@ -55,6 +55,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", diff --git a/site/pnpm-lock.yaml b/site/pnpm-lock.yaml index d4b34d74f9ba6..df0e8290744ff 100644 --- a/site/pnpm-lock.yaml +++ b/site/pnpm-lock.yaml @@ -81,6 +81,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'} diff --git a/site/src/components/ActiveUserChart/ActiveUserChart.tsx b/site/src/components/ActiveUserChart/ActiveUserChart.tsx index 921f06b363722..1524c99cdd786 100644 --- a/site/src/components/ActiveUserChart/ActiveUserChart.tsx +++ b/site/src/components/ActiveUserChart/ActiveUserChart.tsx @@ -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,6 +36,7 @@ ChartJS.register( Title, Tooltip, Legend, + annotationPlugin, ); export interface ActiveUserChartProps { @@ -59,6 +61,23 @@ export const ActiveUserChart: FC = ({ const options: ChartOptions<"line"> = { responsive: true, plugins: { + annotation: { + annotations: [ + { + type: "line", + scaleID: "y", + value: userLimit, + borderColor: theme.palette.warning.light, + borderWidth: 5, + label: { + content: "User limit", + color: theme.palette.primary.contrastText, + display: true, + font: { weight: "normal" }, + }, + }, + ], + }, legend: { display: false, }, @@ -108,14 +127,6 @@ export const ActiveUserChart: FC = ({ backgroundColor: theme.palette.info.dark, fill: "origin", }, - { - label: "User limit", - data: data.map((_) => userLimit), - pointBackgroundColor: theme.palette.warning.light, - pointBorderColor: theme.palette.warning.light, - borderColor: theme.palette.warning.light, - fill: false, - } ], }} options={options} diff --git a/site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx b/site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx index 856ac962d132d..094c025a0d1c8 100644 --- a/site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx +++ b/site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx @@ -38,7 +38,11 @@ export const GeneralSettingsPageView = ({ {deploymentDAUs && ( }> - + )} diff --git a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx index a7fe6265f3e0c..fe206188169e9 100644 --- a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx +++ b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx @@ -77,7 +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()); + const { data: entitlementsQuery } = useQuery(entitlements()); return ( <> From d1263e6fbf9cbc5994e0ee667c1904e3b7cfdb5a Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Fri, 6 Oct 2023 13:11:04 +0200 Subject: [PATCH 3/6] function --- .../ActiveUserChart/ActiveUserChart.tsx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/site/src/components/ActiveUserChart/ActiveUserChart.tsx b/site/src/components/ActiveUserChart/ActiveUserChart.tsx index 1524c99cdd786..0c9f145848f20 100644 --- a/site/src/components/ActiveUserChart/ActiveUserChart.tsx +++ b/site/src/components/ActiveUserChart/ActiveUserChart.tsx @@ -39,6 +39,8 @@ ChartJS.register( annotationPlugin, ); +const USER_LIMIT_DISPLAY_THRESHOLD = 60; + export interface ActiveUserChartProps { data: { date: string; amount: number }[]; interval: "day" | "week"; @@ -66,6 +68,7 @@ export const ActiveUserChart: FC = ({ { type: "line", scaleID: "y", + display: shouldDisplayUserLimit(userLimit, chartData), value: userLimit, borderColor: theme.palette.warning.light, borderWidth: 5, @@ -148,3 +151,15 @@ export const ActiveUsersTitle = () => { ); }; + +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 + ); +} From b82bd7f25333b7b53f33b33e94de5d9f90194c59 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Fri, 6 Oct 2023 13:26:33 +0200 Subject: [PATCH 4/6] Storybook --- .../GeneralSettingsPageView.stories.tsx | 13 ++++++++++- .../TemplateInsightsPage.stories.tsx | 11 +++++++++- site/src/testHelpers/entities.ts | 22 ++++++++++++++++--- 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPageView.stories.tsx b/site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPageView.stories.tsx index 549e72f1767ae..8f6726dbe7243 100644 --- a/site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPageView.stories.tsx +++ b/site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPageView.stories.tsx @@ -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 = { @@ -44,6 +48,13 @@ type Story = StoryObj; export const Page: Story = {}; +export const WithUserLimit: Story = { + args: { + deploymentDAUs: MockDeploymentDAUResponse, + entitlements: MockEntitlementsWithUserLimit, + }, +}; + export const NoDAUs: Story = { args: { deploymentDAUs: undefined, diff --git a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.stories.tsx b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.stories.tsx index 88ac7f34a5892..28d686468ce5a 100644 --- a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.stories.tsx +++ b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.stories.tsx @@ -1,5 +1,6 @@ import type { Meta, StoryObj } from "@storybook/react"; import { TemplateInsightsPageView } from "./TemplateInsightsPage"; +import { MockEntitlementsWithUserLimit } from "testHelpers/entities"; const meta: Meta = { 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, + }, +}; diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index f9341889a9c54..5eea42c4d5e01 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -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 = { @@ -1924,6 +1924,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 = { From c4c828b6bc3f46dcf1af0aaf98845ba1c6770b8a Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Fri, 6 Oct 2023 13:45:15 +0200 Subject: [PATCH 5/6] Check if entitlement enabled --- .../GeneralSettingsPage/GeneralSettingsPageView.tsx | 6 +++++- .../TemplateInsightsPage/TemplateInsightsPage.tsx | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx b/site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx index 094c025a0d1c8..21623d813ee50 100644 --- a/site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx +++ b/site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx @@ -41,7 +41,11 @@ export const GeneralSettingsPageView = ({ diff --git a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx index fe206188169e9..b0a7aa7630ca7 100644 --- a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx +++ b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx @@ -184,7 +184,11 @@ export const TemplateInsightsPageView = ({ From 93a55628e7268dd861e7b6a8f69069a903415a20 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Mon, 9 Oct 2023 13:28:59 +0200 Subject: [PATCH 6/6] Grey line --- site/src/components/ActiveUserChart/ActiveUserChart.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/components/ActiveUserChart/ActiveUserChart.tsx b/site/src/components/ActiveUserChart/ActiveUserChart.tsx index 0c9f145848f20..15ff04864ccb1 100644 --- a/site/src/components/ActiveUserChart/ActiveUserChart.tsx +++ b/site/src/components/ActiveUserChart/ActiveUserChart.tsx @@ -70,7 +70,7 @@ export const ActiveUserChart: FC = ({ scaleID: "y", display: shouldDisplayUserLimit(userLimit, chartData), value: userLimit, - borderColor: theme.palette.warning.light, + borderColor: theme.palette.secondary.contrastText, borderWidth: 5, label: { content: "User limit",